feat(imggen): фон питомца, обложки курсов, аватары и доска через ИИ

Питомец: кастомный фон (миграция 068 pet_bg_custom, POST /api/pet/bg/custom,
  карточка «Свой фон (ИИ)» в гардеробной, применение картинкой).
Курсы: обложка-картинка (миграция 069 cover_image, генерация в модалке
  редактирования, рендер вместо эмодзи).
Аватар: кнопка «Сгенерировать (ИИ)» в загрузке → кадрирование → модерация.
Доска (classroom): кнопка-инструмент «Сгенерировать картинку (ИИ)».

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Maxim Dolgolyov
2026-06-12 10:59:26 +03:00
parent d6faf6b22c
commit 6fcdafed50
9 changed files with 200 additions and 45 deletions
+19 -3
View File
@@ -178,7 +178,7 @@ function getPet(req, res) {
const user = db.prepare(
`SELECT xp, level, streak_current, streak_best, streak_date, coins,
pet_name, last_login, pet_color, pet_last_petted, pet_petting_streak,
pet_bg, pet_bg_owned, pet_last_fed, pet_equipped, pet_pattern
pet_bg, pet_bg_owned, pet_last_fed, pet_equipped, pet_pattern, pet_bg_custom
FROM users WHERE id = ?`
).get(req.user.id);
@@ -277,6 +277,7 @@ function getPet(req, res) {
feedCooldown,
petBg: user.pet_bg || 'default',
petBgOwned: _parseOwned(user.pet_bg_owned),
petBgCustom: user.pet_bg_custom || null,
});
}
@@ -391,7 +392,10 @@ function buyBg(req, res) {
/* ── PATCH /api/pet/bg ────────────────────────────────────────────────── */
function setBg(req, res) {
const { id } = req.body;
if (id !== 'default') {
if (id === 'custom') {
const url = db.prepare('SELECT pet_bg_custom FROM users WHERE id=?').get(req.user.id)?.pet_bg_custom;
if (!url) return res.status(400).json({ error: 'no_custom_bg' });
} else if (id !== 'default') {
const owned = _parseOwned(db.prepare('SELECT pet_bg_owned FROM users WHERE id=?').get(req.user.id)?.pet_bg_owned);
if (!owned.includes(id)) return res.status(403).json({ error: 'not owned' });
}
@@ -399,6 +403,18 @@ function setBg(req, res) {
res.json({ ok: true, bg: id });
}
/* ── POST /api/pet/bg/custom ──────────────────────────────────────────────
Сохранить сгенерированную ИИ картинку как кастомный фон и сделать активной.
URL принимается только из /uploads/generated/ (то, что отдаёт /api/imggen). */
function setCustomBg(req, res) {
const url = String(req.body && req.body.url || '');
if (!/^\/uploads\/generated\/[A-Za-z0-9._-]+\.(png|jpg|jpeg|webp)$/.test(url)) {
return res.status(400).json({ error: 'invalid_url' });
}
db.prepare('UPDATE users SET pet_bg_custom=?, pet_bg=? WHERE id=?').run(url, 'custom', req.user.id);
res.json({ ok: true, bg: 'custom', url });
}
/* ── POST /api/pet/star ───────────────────────────────────────────────── */
function starCatch(req, res) {
const user = db.prepare('SELECT pet_last_star FROM users WHERE id=?').get(req.user.id);
@@ -436,4 +452,4 @@ function feedPet(req, res) {
res.json({ ok: true, xpAwarded: 15, xp: updated.xp, coins: updated.coins });
}
module.exports = { getPet, renamePet, petAction, updateColor, starCatch, getShop, buyBg, setBg, feedPet, equipAccessories, updatePattern };
module.exports = { getPet, renamePet, petAction, updateColor, starCatch, getShop, buyBg, setBg, setCustomBg, feedPet, equipAccessories, updatePattern };