Коротко и по делу — план и пример реализации функции «заменить питомца» (backend + frontend + правила), чтобы вы могли встроить в своё приложение. 1) Бизнес-правила (решите, какие применимы) - Замена доступна только владельцу питомца. - Можно ли заменять активного питомца? (обычно да, с деактивацией текущего). - Что переносится: опыт, уровень, экипировка, квесты, привязанная мебель/ячейки, привязка к аккаунту. - Ограничения: cooldown между заменами, проверка на блокировки/арены, невозможность заменить единичного/редкого без подтверждения. - Атомарность: операция должна быть транзакционной и обратимой (лог/undo). 2) Модель данных (пример, SQL) Добавьте поля для истории и связи: `pets`: - `id` - `owner_id` - `is_active` (bool) - `replaced_by_id` (nullable) - `replaced_at` (nullable timestamp) - `transferred_items` (json nullable) — опционально История замен: `pet_replacements`: - `id`, `owner_id`, `old_pet_id`, `new_pet_id`, `transferred_data` (json), `created_at` 3) API (пример) - Метод: `POST /api/pets/:oldPetId/replace` - Тело запроса: - `newPetId` — id заменяющего питомца (или полные данные нового питомца) - опции: `transferItems: true|false`, `transferXp: true|false` - Ответы: - `200` — успех + объект замены - `400` — неверные данные - `403` — не владелец / запрещено - `409` — конфликт (например, cooldown) 4) Пример серверного обработчика (Node.js / Express, псевдокод) ```js app.post('/api/pets/:oldPetId/replace', async (req, res) => { const userId = req.user.id; const oldPetId = req.params.oldPetId; const { newPetId, transferItems=true, transferXp=true } = req.body; await db.transaction(async (t) => { const oldPet = await Pet.findOne({ where:{id:oldPetId, owner_id:userId}, transaction: t }); const newPet = await Pet.findOne({ where:{id:newPetId, owner_id:userId}, transaction: t }); if (!oldPet || !newPet) return res.status(403).send({ error: 'no permission' }); if (oldPet.id === newPet.id) return res.status(400).send({ error: 'same pet' }); // дополнительные проверки: cooldown, locked, in-battle и т.д. const transferred = {}; if (transferXp) { transferred.xp = { from: oldPet.xp, to: newPet.xp }; newPet.xp = mergeXp(oldPet.xp, newPet.xp); oldPet.xp = 0; } if (transferItems) { const items = await Item.findAll({ where:{pet_id: oldPet.id}, transaction: t }); for (const it of items) { it.pet_id = newPet.id; await it.save({transaction: t}); } transferred.items = items.map(i=>i.id); } oldPet.is_active = false; oldPet.replaced_by_id = newPet.id; oldPet.replaced_at = new Date(); newPet.is_active = true; await oldPet.save({ transaction: t }); await newPet.save({ transaction: t }); await PetReplacement.create({ owner_id: userId, old_pet_id: oldPet.id, new_pet_id: newPet.id, transferred_data: transferred }, { transaction: t }); // emit event/notify res.status(200).send({ success: true, transferred }); }); }); ``` 5) Frontend (UI) - Кнопка «Заменить питомца» в карточке питомца. - Открывать подтверждающий диалог с чекбоксами: “перенести экипировку”, “перенести опыт”. - Показывать последствия (что будет обнулено/перемещено). - После подтверждения вызвать API и оптимистично обновить список питомцев / активного питомца. - Показывать уведомление о результате и ссылку на историю замен. 6) Тесты и миграции - Юнит-тесты на валидацию прав и сценарии переноса. - Интеграционные тесты: atomicity (часть операций не должна оставить систему в неконсистентном состоянии). - Миграция БД для новых полей и таблицы истории. 7) Нотификации и журнал - Отправить владельцу push/внутреннее уведомление о замене. - Сохранить запись в `pet_replacements` для аудита и возможности отката. 8) Дополнения - Реализуйте откат (undo) в течение N часов, если бизнес этого требует. - Учитывайте синхронизацию с кешем/Redis и внешними сервисами (мастер-статистика, аренда и т.д.). - Контролируйте производительность при переносе большого количества предметов (пакетные обновления). Если нужно, могу: - подготовить точное SQL-миграцию, - дать готовый код для вашей стека (укажите язык/фреймворк), - или составить тест-кейсы для QA.
1) Бизнес-правила (решите, какие применимы)
- Замена доступна только владельцу питомца.
- Можно ли заменять активного питомца? (обычно да, с деактивацией текущего).
- Что переносится: опыт, уровень, экипировка, квесты, привязанная мебель/ячейки, привязка к аккаунту.
- Ограничения: cooldown между заменами, проверка на блокировки/арены, невозможность заменить единичного/редкого без подтверждения.
- Атомарность: операция должна быть транзакционной и обратимой (лог/undo).
2) Модель данных (пример, SQL)
Добавьте поля для истории и связи:
`pets`:
- `id`
- `owner_id`
- `is_active` (bool)
- `replaced_by_id` (nullable)
- `replaced_at` (nullable timestamp)
- `transferred_items` (json nullable) — опционально
История замен:
`pet_replacements`:
- `id`, `owner_id`, `old_pet_id`, `new_pet_id`, `transferred_data` (json), `created_at`
3) API (пример)
- Метод: `POST /api/pets/:oldPetId/replace`
- Тело запроса:
- `newPetId` — id заменяющего питомца (или полные данные нового питомца)
- опции: `transferItems: true|false`, `transferXp: true|false`
- Ответы:
- `200` — успех + объект замены
- `400` — неверные данные
- `403` — не владелец / запрещено
- `409` — конфликт (например, cooldown)
4) Пример серверного обработчика (Node.js / Express, псевдокод)
```js
app.post('/api/pets/:oldPetId/replace', async (req, res) => {
const userId = req.user.id;
const oldPetId = req.params.oldPetId;
const { newPetId, transferItems=true, transferXp=true } = req.body;
await db.transaction(async (t) => {
const oldPet = await Pet.findOne({ where:{id:oldPetId, owner_id:userId}, transaction: t });
const newPet = await Pet.findOne({ where:{id:newPetId, owner_id:userId}, transaction: t });
if (!oldPet || !newPet) return res.status(403).send({ error: 'no permission' });
if (oldPet.id === newPet.id) return res.status(400).send({ error: 'same pet' });
// дополнительные проверки: cooldown, locked, in-battle и т.д.
const transferred = {};
if (transferXp) {
transferred.xp = { from: oldPet.xp, to: newPet.xp };
newPet.xp = mergeXp(oldPet.xp, newPet.xp);
oldPet.xp = 0;
}
if (transferItems) {
const items = await Item.findAll({ where:{pet_id: oldPet.id}, transaction: t });
for (const it of items) { it.pet_id = newPet.id; await it.save({transaction: t}); }
transferred.items = items.map(i=>i.id);
}
oldPet.is_active = false;
oldPet.replaced_by_id = newPet.id;
oldPet.replaced_at = new Date();
newPet.is_active = true;
await oldPet.save({ transaction: t });
await newPet.save({ transaction: t });
await PetReplacement.create({
owner_id: userId, old_pet_id: oldPet.id, new_pet_id: newPet.id,
transferred_data: transferred
}, { transaction: t });
// emit event/notify
res.status(200).send({ success: true, transferred });
});
});
```
5) Frontend (UI)
- Кнопка «Заменить питомца» в карточке питомца.
- Открывать подтверждающий диалог с чекбоксами: “перенести экипировку”, “перенести опыт”.
- Показывать последствия (что будет обнулено/перемещено).
- После подтверждения вызвать API и оптимистично обновить список питомцев / активного питомца.
- Показывать уведомление о результате и ссылку на историю замен.
6) Тесты и миграции
- Юнит-тесты на валидацию прав и сценарии переноса.
- Интеграционные тесты: atomicity (часть операций не должна оставить систему в неконсистентном состоянии).
- Миграция БД для новых полей и таблицы истории.
7) Нотификации и журнал
- Отправить владельцу push/внутреннее уведомление о замене.
- Сохранить запись в `pet_replacements` для аудита и возможности отката.
8) Дополнения
- Реализуйте откат (undo) в течение N часов, если бизнес этого требует.
- Учитывайте синхронизацию с кешем/Redis и внешними сервисами (мастер-статистика, аренда и т.д.).
- Контролируйте производительность при переносе большого количества предметов (пакетные обновления).
Если нужно, могу:
- подготовить точное SQL-миграцию,
- дать готовый код для вашей стека (укажите язык/фреймворк),
- или составить тест-кейсы для QA.