feat(avatars): 27 готовых пресет-аватаров + UI выбора для всех ролей

- backend/uploads/avatars/preset_01..27.png — иллюстрированные персонажи
- POST /api/avatar/preset — мгновенная установка без модерации
- GET  /api/avatar/presets — список доступных пресетов
- profile.html: галерея пресетов в модалке аватара, доступна студенту/учителю/админу
- кастомная загрузка с модерацией остаётся только для студентов
This commit is contained in:
Maxim Dolgolyov
2026-05-29 14:30:24 +03:00
parent 717ad3d0f5
commit 19ce8728e5
30 changed files with 175 additions and 22 deletions
+41 -2
View File
@@ -98,10 +98,49 @@ function rejectAvatar(req, res) {
function removeAvatar(req, res) {
const user = db.prepare('SELECT avatar_url FROM users WHERE id=?').get(req.user.id);
if (user?.avatar_url) {
try { fs.unlinkSync(path.join(AVATARS_DIR, user.avatar_url)); } catch {}
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 });
}
module.exports = { requestAvatar, myStatus, getPending, approveAvatar, rejectAvatar, removeAvatar };
/* ── 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 };