const path = require('path'); const fs = require('fs'); const db = require('../db/db'); const { audit } = require('../utils/audit'); const AVATARS_DIR = path.join(__dirname, '../../uploads/avatars'); /* ── POST /api/avatar/request ── student submits a new avatar ───────────── */ function requestAvatar(req, res) { if (!req.file) return res.status(400).json({ error: 'Файл не загружен' }); // Cancel any previous pending request from this user (replace it) const prev = db.prepare( "SELECT filename FROM avatar_requests WHERE user_id=? AND status='pending'" ).get(req.user.id); if (prev) { // Delete old file try { fs.unlinkSync(path.join(AVATARS_DIR, prev.filename)); } catch {} db.prepare("DELETE FROM avatar_requests WHERE user_id=? AND status='pending'").run(req.user.id); } db.prepare(` INSERT INTO avatar_requests (user_id, filename, status, created_at) VALUES (?, ?, 'pending', datetime('now')) `).run(req.user.id, req.file.filename); res.json({ ok: true, filename: req.file.filename }); } /* ── GET /api/avatar/my-status ── student polls their request status ────── */ function myStatus(req, res) { const row = db.prepare(` SELECT ar.id, ar.filename, ar.status, ar.reject_msg, ar.created_at, ar.reviewed_at FROM avatar_requests ar WHERE ar.user_id = ? ORDER BY ar.created_at DESC LIMIT 1 `).get(req.user.id); const user = db.prepare('SELECT avatar_url FROM users WHERE id=?').get(req.user.id); res.json({ request: row || null, current_avatar: user?.avatar_url || null }); } /* ── GET /api/avatar/pending ── moderator sees all pending requests ──────── */ function getPending(req, res) { const rows = db.prepare(` SELECT ar.id, ar.filename, ar.status, ar.created_at, u.id AS user_id, u.name AS user_name, u.email AS user_email, u.avatar_url AS current_avatar FROM avatar_requests ar JOIN users u ON u.id = ar.user_id WHERE ar.status = 'pending' ORDER BY ar.created_at ASC `).all(); res.json(rows); } /* ── POST /api/avatar/:id/approve ── moderator approves ─────────────────── */ function approveAvatar(req, res) { const row = db.prepare('SELECT * FROM avatar_requests WHERE id=?').get(req.params.id); if (!row) return res.status(404).json({ error: 'Заявка не найдена' }); if (row.status !== 'pending') return res.status(400).json({ error: 'Заявка уже обработана' }); // Remove old avatar file if exists const user = db.prepare('SELECT avatar_url FROM users WHERE id=?').get(row.user_id); if (user?.avatar_url) { try { fs.unlinkSync(path.join(AVATARS_DIR, user.avatar_url)); } catch {} } db.prepare('UPDATE users SET avatar_url=? WHERE id=?').run(row.filename, row.user_id); db.prepare(` UPDATE avatar_requests SET status='approved', reviewer_id=?, reviewed_at=datetime('now') WHERE id=? `).run(req.user.id, row.id); audit(req, 'avatar.approve', `user:${row.user_id}`, row.filename); res.json({ ok: true }); } /* ── POST /api/avatar/:id/reject ── moderator rejects ───────────────────── */ function rejectAvatar(req, res) { const row = db.prepare('SELECT * FROM avatar_requests WHERE id=?').get(req.params.id); if (!row) return res.status(404).json({ error: 'Заявка не найдена' }); if (row.status !== 'pending') return res.status(400).json({ error: 'Заявка уже обработана' }); const msg = (req.body.reason || '').toString().slice(0, 200).trim(); // Delete uploaded file try { fs.unlinkSync(path.join(AVATARS_DIR, row.filename)); } catch {} db.prepare(` UPDATE avatar_requests SET status='rejected', reviewer_id=?, reject_msg=?, reviewed_at=datetime('now') WHERE id=? `).run(req.user.id, msg || null, row.id); audit(req, 'avatar.reject', `user:${row.user_id}`, msg || ''); res.json({ ok: true }); } /* ── DELETE /api/avatar/me ── student removes their approved avatar ──────── */ function removeAvatar(req, res) { const user = db.prepare('SELECT avatar_url FROM users WHERE id=?').get(req.user.id); if (user?.avatar_url) { if (!/^preset_\d{2}\.png$/.test(user.avatar_url)) { try { fs.unlinkSync(path.join(AVATARS_DIR, user.avatar_url)); } catch {} } db.prepare('UPDATE users SET avatar_url=NULL WHERE id=?').run(req.user.id); } res.json({ ok: true }); } /* ── GET /api/avatar/presets ── list available preset avatars ─────────────── */ function listPresets(_req, res) { const files = fs.readdirSync(AVATARS_DIR) .filter(f => /^preset_\d{2}\.png$/.test(f)) .sort(); res.json({ presets: files }); } /* ── POST /api/avatar/preset ── set avatar to a preset (no moderation) ────── */ function setPreset(req, res) { const filename = String(req.body.filename || ''); if (!/^preset_\d{2}\.png$/.test(filename)) { return res.status(400).json({ error: 'Некорректный пресет' }); } if (!fs.existsSync(path.join(AVATARS_DIR, filename))) { return res.status(404).json({ error: 'Пресет не найден' }); } // Remove old uploaded avatar file (but never delete preset files) const user = db.prepare('SELECT avatar_url FROM users WHERE id=?').get(req.user.id); if (user?.avatar_url && !/^preset_\d{2}\.png$/.test(user.avatar_url)) { try { fs.unlinkSync(path.join(AVATARS_DIR, user.avatar_url)); } catch {} } // Cancel any pending moderation request from this user const prev = db.prepare( "SELECT filename FROM avatar_requests WHERE user_id=? AND status='pending'" ).get(req.user.id); if (prev) { try { fs.unlinkSync(path.join(AVATARS_DIR, prev.filename)); } catch {} db.prepare("DELETE FROM avatar_requests WHERE user_id=? AND status='pending'").run(req.user.id); } db.prepare('UPDATE users SET avatar_url=? WHERE id=?').run(filename, req.user.id); res.json({ ok: true, avatar_url: filename }); } module.exports = { requestAvatar, myStatus, getPending, approveAvatar, rejectAvatar, removeAvatar, listPresets, setPreset };