Skip to content

Релиз 22#282

Merged
Magistrus merged 9 commits into
mainfrom
dev
Jun 26, 2026
Merged

Релиз 22#282
Magistrus merged 9 commits into
mainfrom
dev

Conversation

@Magistrus

Copy link
Copy Markdown
Contributor

No description provided.

Magistrus and others added 9 commits June 23, 2026 14:27
Добавлена модель подарочных подписок с регистрацией и активацией по коду.

VTTG-экспорт теперь ограничивает пользователей без активной подписки до SRD, включая временную роль VTTG.

Добавлена миграция таблицы подписок и тесты матрицы доступа.
- RedemptionCode: один код несёт подписку и/или тир наград, batch-выпуск
- RewardTier (6 кумулятивных тиров) и RewardPerk, выдача через redeem
- RewardResource: конфиг ссылок/готовности контента (приключение COMING_SOON)
- эндпоинты: /codes, /redeem, /subscriptions/all, /rewards/*
- миграция: redemption_code, user_reward, reward_resource + сид

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- система достижений: каталог, выдача (авто по триггеру/вручную/по коду)
- атомарное погашение кода (UPDATE ... WHERE redeemed_by IS NULL) против гонки
- гибкая привязка к коду: произвольные перки и достижения помимо тира
- роль SUBSCRIBER при активации подписки + ночное снятие по истечении
- доступ к контенту VTTG по сроку подписки из БД, а не по роли из JWT

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Поле projectiles на SpellEffect и POJO Projectiles (passthrough),
чтобы снарядный режим из редактора сохранялся в JSONB.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…и в VTTG

Магический предмет можно связать с несколькими немагическими (ManyToMany
magic_item_item). Связи принимаются/возвращаются в MagicItemRequest (items: url[])
и используются в экспорте VTTG: вес и стоимость берутся из связанных предметов
(приоритет над эвристикой по clarification, для любой категории), а несколько
связанных видов оружия/брони раскрываются в отдельные записи. При отсутствии
связей поведение прежнее.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… предметам

Экспорт магического предмета в VTTG строится по явно связанным немагическим:
- один связанный предмет: вес из него, стоимость = его стоимость + цена по редкости;
- несколько связанных: по записи на каждый с подменой слова в названии
  (Эльфийская кольчуга → Эльфийская кольчуга и Эльфийская кольчужная рубаха);
- шаблоны «… +1, +2 или +3» (Доспех/Щит/Оружие): на каждый связанный предмет три
  записи с бонусами +1/+2/+3, редкость по бонусу (броня дороже оружия/щита на уровень),
  стоимость = стоимость базы + цена по редкости бонуса.

Шаблонные «сборные» предметы теперь экспортируются, если у них есть связанные
предметы (size(mi.items) > 0); без связей поведение прежнее (путь по clarification).

Также исправлены тест-хелперы (не выставляли Item.category), из-за чего падал
derivesWeaponMechanicsFromBaseItemByClarification.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Изменение только связанных немагических предметов меняет join-таблицу
magic_item_item, но не строку самого предмета, поэтому @UpdateTimestamp не
срабатывает и инкрементальная выгрузка VTTG (/changes) пропускала правку.
При сохранении магического предмета теперь явно обновляется updatedAt
(MagicItemRepository.touchUpdatedAt).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat(subscriptions): список и удаление выпущенных промо-кодов

Добавлены два ADMIN-эндпоинта в SubscriptionController для управления
ранее выпущенными одноразовыми кодами.

GET /api/subscriptions/codes
- Возвращает список всех выпущенных кодов, отсортированных по дате
  создания (новые сверху), в виде List<RedemptionCodeResponse>.
- Ответ включает redeemedBy и redeemedAt, чтобы в админке был виден
  статус активации кода: активирован он или нет, а если активирован —
  кем и когда.

DELETE /api/subscriptions/codes/{id}
- Удаляет выпущенный код. Удалять разрешено только НЕпогашенные коды.
- Если код уже использован (redeemedBy != null) — возвращается 400:
  удаление не отзывает выданную подписку и награды, а у активированного
  кода нужно сохранить аудит «кто и когда активировал».
- Если код не найден — 404. При успешном удалении — 204 No Content.

Изменения по слоям:
- RedemptionCodeRepository: добавлен метод findAllByOrderByCreatedAtDesc().
- SubscriptionService: методы allCodes() (@transactional readOnly) и
  deleteCode(UUID) с проверкой redeemedBy; ошибки через ApiException
  (404 «Код не найден», 400 «Нельзя удалить уже использованный код»).
- SubscriptionController: эндпоинты GET /codes и DELETE /codes/{id}
  с @secured("ADMIN") и swagger-аннотациями @operation; для DELETE
  выставлен @ResponseStatus(NO_CONTENT).

Новый DTO не понадобился — переиспользован RedemptionCodeResponse.
Схема БД и миграции не изменялись.

* feat(subscriptions): убрать удаление выпущенных кодов

Убрана ранее добавленная возможность удаления выпущенных промо-кодов —
коды остаются неизменяемыми после выпуска.

Убрано:
- SubscriptionController: эндпоинт DELETE /api/subscriptions/codes/{id}
  и ставший после этого ненужным импорт DeleteMapping.
- SubscriptionService: метод deleteCode(UUID) с проверкой redeemedBy
  и соответствующими ошибками ApiException (404/400).

Оставлено без изменений:
- GET /api/subscriptions/codes — список всех выпущенных кодов для админки.
- RedemptionCodeRepository.findAllByOrderByCreatedAtDesc() — используется
  списком кодов.

Остальные эндпоинты подписок не затрагивались. Схема БД и миграции
не изменялись.

* feat(subscriptions): мягкая деактивация выпущенных кодов

Вместо удаления у кодов появилась обратимая деактивация: код не удаляется,
а помечается как деактивированный и больше не может быть погашен. Деактивацию
можно отменить (вернуть код в активные).

База данных:
- Новая Liquibase-миграция добавляет в таблицу redemption_code колонки:
  disabled (BOOLEAN NOT NULL DEFAULT false), disabled_at (TIMESTAMP WITH
  TIME ZONE), disabled_by (VARCHAR). Миграция аддитивная и безопасна для
  уже работающих экземпляров.

Сущность и DTO:
- RedemptionCode: поля disabled / disabledAt / disabledBy.
- RedemptionCodeResponse: те же поля добавлены в ответ, чтобы фронт мог
  показать статус «Деактивирован» и аудит «кем/когда».

Логика:
- SubscriptionService.setCodeDisabled(id, disabled): деактивирует или
  возвращает код в строй. При деактивации пишет disabledAt/disabledBy,
  при возврате — очищает их. Менять статус уже погашенного кода нельзя (400),
  отсутствующий код — 404.
- redeem(): при попытке погасить деактивированный код возвращается 409
  «Код деактивирован».

API (оба эндпоинта @secured("ADMIN"), возвращают обновлённый код):
- POST /api/subscriptions/codes/{id}/deactivate
- POST /api/subscriptions/codes/{id}/reactivate

Возможность удаления кодов (DELETE) была убрана ранее и не возвращается.

---------

Co-authored-by: Vitaliy Unguryan <gm.magistr@gmail.com>
@Magistrus Magistrus requested a review from peterkogit June 26, 2026 07:26
@Magistrus Magistrus self-assigned this Jun 26, 2026
@Magistrus Magistrus added the release Релизный тэг (не использовать просто так) label Jun 26, 2026
@Magistrus Magistrus merged commit 57cc511 into main Jun 26, 2026
5 of 7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

release Релизный тэг (не использовать просто так)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants