[bug][share] Кастомный адрес (/l/:alias) не обновляется при редактировании: Save создаёт дубликат вместо переименования #226

Closed
opened 2026-06-27 00:40:39 +03:00 by vvzvlad · 0 comments
Owner

Симптом

В модалке шаринга, блок «Custom address» (кастомный адрес /l/:alias): если у страницы уже есть адрес и его редактируют (например, меняют слаг teted) и жмут Save, отображаемая ссылка не обновляется — продолжает показывать старый адрес.

Воспроизведение

  1. Открыть шаринг публичной страницы, задать кастомный адрес, например te, нажать Save.
  2. Изменить слаг в поле ввода на ted, нажать Save.
  3. Верхняя read-only ссылка остаётся …/l/te (старая). Ожидалось …/l/ted.

Причина

Алиас — строка в таблице share_aliases с уникальностью только по паре (workspace_id, alias). Уникального ограничения на page_id нет (см. миграцию apps/server/src/database/migrations/20260626T130000-share-aliases.ts), поэтому у одной страницы может накопиться несколько строк-алиасов.

В apps/server/src/core/share/share-alias.service.ts метод setAlias() ищет строку по имени нового слага (findByAliasAndWorkspace). Если имя свободно — просто делает INSERT новой строки. При редактировании teted старая строка не заменяется, а добавляется вторая. У страницы оказывается две строки: te и ted.

Отображаемая в модалке ссылка берётся из getAliasForPage()ShareAliasRepo.findByPageId(), где executeTakeFirst() идёт без ORDER BY — Postgres возвращает произвольную (на практике — самую старую) строку, т.е. старый te. Отсюда «ссылка не обновляется».

Фронтенд здесь ни при чём: useSetShareAliasMutation корректно инвалидирует и перезапрашивает share-alias-for-page, но сервер отдаёт старый алиас.

Побочный эффект: каждое редактирование плодит «осиротевшие» строки-алиасы, которые навсегда остаются рабочими ссылками /l/<старое> (никогда не удаляются).

Ожидаемое поведение

Инвариант: у страницы ровно один кастомный адрес. setAlias() должен оставлять страницу с единственной строкой-алиасом, имя которой равно запрошенному:

  • редактирование на свободное имя → переименование существующей строки на месте (а не вставка новой);
  • первичная установка (адреса ещё нет) → INSERT (как сейчас);
  • имя уже указывает на эту же страницу → no-op (как сейчас);
  • имя занято другой страницей → текущая 409-логика ALIAS_REASSIGN_REQUIRED + retarget по подтверждению (как сейчас);
  • после любой успешной установки — удалить прочие строки-алиасы, всё ещё указывающие на эту страницу (предыдущее имя после переименования/retarget, плюс легаси-дубликаты — самолечение).

Затронутые файлы

  • apps/server/src/core/share/share-alias.service.tssetAlias() (корневая логика).
  • apps/server/src/database/repos/share-alias/share-alias.repo.ts — нет метода переименования строки и очистки дублей; findByPageId() недетерминирован (нет ORDER BY).
  • Тесты: apps/server/src/core/share/share-alias.service.spec.ts, apps/server/src/database/repos/share-alias/share-alias.repo.spec.ts.

Связано

Регрессия фичи #205 (кастомные адреса /l/:alias).

## Симптом В модалке шаринга, блок «Custom address» (кастомный адрес `/l/:alias`): если у страницы **уже есть** адрес и его **редактируют** (например, меняют слаг `te` → `ted`) и жмут **Save**, отображаемая ссылка не обновляется — продолжает показывать старый адрес. ## Воспроизведение 1. Открыть шаринг публичной страницы, задать кастомный адрес, например `te`, нажать **Save**. 2. Изменить слаг в поле ввода на `ted`, нажать **Save**. 3. Верхняя read-only ссылка остаётся `…/l/te` (старая). Ожидалось `…/l/ted`. ## Причина Алиас — строка в таблице `share_aliases` с уникальностью только по паре `(workspace_id, alias)`. Уникального ограничения на `page_id` **нет** (см. миграцию `apps/server/src/database/migrations/20260626T130000-share-aliases.ts`), поэтому у одной страницы может накопиться несколько строк-алиасов. В `apps/server/src/core/share/share-alias.service.ts` метод `setAlias()` ищет строку по имени **нового** слага (`findByAliasAndWorkspace`). Если имя свободно — просто делает `INSERT` новой строки. При редактировании `te` → `ted` старая строка не заменяется, а добавляется вторая. У страницы оказывается две строки: `te` и `ted`. Отображаемая в модалке ссылка берётся из `getAliasForPage()` → `ShareAliasRepo.findByPageId()`, где `executeTakeFirst()` идёт **без `ORDER BY`** — Postgres возвращает произвольную (на практике — самую старую) строку, т.е. старый `te`. Отсюда «ссылка не обновляется». Фронтенд здесь ни при чём: `useSetShareAliasMutation` корректно инвалидирует и перезапрашивает `share-alias-for-page`, но сервер отдаёт старый алиас. Побочный эффект: каждое редактирование плодит «осиротевшие» строки-алиасы, которые навсегда остаются рабочими ссылками `/l/<старое>` (никогда не удаляются). ## Ожидаемое поведение Инвариант: у страницы ровно **один** кастомный адрес. `setAlias()` должен оставлять страницу с единственной строкой-алиасом, имя которой равно запрошенному: - редактирование на свободное имя → переименование существующей строки **на месте** (а не вставка новой); - первичная установка (адреса ещё нет) → `INSERT` (как сейчас); - имя уже указывает на эту же страницу → no-op (как сейчас); - имя занято другой страницей → текущая 409-логика `ALIAS_REASSIGN_REQUIRED` + retarget по подтверждению (как сейчас); - после любой успешной установки — удалить прочие строки-алиасы, всё ещё указывающие на эту страницу (предыдущее имя после переименования/retarget, плюс легаси-дубликаты — самолечение). ## Затронутые файлы - `apps/server/src/core/share/share-alias.service.ts` — `setAlias()` (корневая логика). - `apps/server/src/database/repos/share-alias/share-alias.repo.ts` — нет метода переименования строки и очистки дублей; `findByPageId()` недетерминирован (нет `ORDER BY`). - Тесты: `apps/server/src/core/share/share-alias.service.spec.ts`, `apps/server/src/database/repos/share-alias/share-alias.repo.spec.ts`. ## Связано Регрессия фичи #205 (кастомные адреса `/l/:alias`).
vvzvlad added the bug label 2026-06-27 00:40:39 +03:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: vvzvlad/gitmost#226