Все 9 задач закрыты; в design.md уточнено, что удалённые привязки не осиротевают объекты: отредактированная версия живёт вместе с фото.
8.4 KiB
Design
Context
См. proposal.md — Why. Ограничения, важные для подхода:
Store.Clone(Postgres) иSQLiteStore.Clone(SQLite) копируют привязкиphoto_objectsодной вставкойINSERT ... SELECTбез фильтра по ролям, поэтому копия наследует и обычные варианты, и edit-варианты.- Обычные варианты действительно должны разделяться: воркер создаёт их через
createObject(ctx, siblings, ...), гдеsiblings— все фото, делящие оригинал (PhotosByOriginalObject). Edit-варианты, наоборот, создаются с владельцем[]string{photoID}и привязаны только к одному фото. - Параметры правок лежат в колонке
photos.edit_paramsи при клонировании не переносятся:INSERT INTO photos (...)вCloneеё не включает. То есть рассинхрон возникает именно на уровне привязок объектов. ResetEditудаляет и параметры, и привязки edit-объектов (DeleteObjectBindings). Значит, у фото с пустымиedit_paramsedit-привязок быть не должно — кроме как последствия копирования.- Выдача URL смотрит только на привязки (
attachURLs), а фронтенд выбираетedit_thumb_url ?? thumb_url. Привязка — источник правды о том, какие варианты есть у фото; чинить выдачу URL вместо привязки нельзя. ClaimGarbageсобирает готовые объекты без привязок, поэтому удаление привязки само по себе освобождает объект — отдельная уборка не нужна.
Goals / Non-Goals
Goals:
- Копия фото не наследует отредактированную версию: показывается исходник, параметры правок пусты.
- Postgres и SQLite ведут себя одинаково, список исключаемых ролей задан один раз.
- Уже существующие копии в базе перестают показывать отредактированную версию без ручного вмешательства.
Non-Goals:
- Не переносим параметры правок на копию: правка принадлежит фото, а не копии.
- Не рендерим отредактированную версию заново для копии.
- Не меняем поведение переноса (
MoveBatch) и разделяемых обычных вариантов. - Не трогаем клиентов и HTTP API: формат ответа не меняется.
Decisions
1. Фильтровать роли в Clone, а не в выдаче URL
Копирование исключает edit-роли из переносимых привязок:
INSERT INTO photo_objects (photo_id, object_id, role)
SELECT $1, object_id, role FROM photo_objects
WHERE photo_id = $2 AND role <> ALL($3)
- Альтернатива: прятать edit-URL, если у фото нет
edit_params. Отклонено: привязка осталась бы в базе, GC считал бы объект живым, а модель и привязка расходились бы. Привязка — источник правды. - Альтернатива: копировать и параметры, и готовую версию. Отклонено: меняет семантику «правка принадлежит фото» и требует отдельного рендера для копии.
2. Список edit-ролей — в одном месте
Роли уже объявлены в backend/internal/photos/edit_render.go
(RoleEdit, RoleEditThumb, RoleEditMedium, RoleEditPreview), оба хранилища
находятся в том же пакете. Добавляется общий список ролей и хелпер проверки, чтобы
Postgres и SQLite не расходились и в SQL не попадали строковые литералы.
- Альтернатива: писать роли литералами в каждом
Clone. Отклонено: список размножится, добавление новой edit-роли забудет одно из хранилищ.
3. Разовая чистка существующих копий — миграцией
В базе могут остаться привязки edit* у фото с пустыми edit_params — это и есть
следы бага копирования. Чистка выполняется миграцией в обоих диалектах
(Postgres и SQLite нумеруются независимо):
DELETE FROM photo_objects
WHERE role IN ('edit', 'edit_thumb', 'edit_medium', 'edit_preview')
AND photo_id IN (SELECT id FROM photos WHERE edit_params IS NULL OR edit_params = '{}')
- Альтернатива: чистить лениво при чтении. Отклонено: правило прячется в двух местах и не отражается в данных.
- Альтернатива: ничего не чистить. Отклонено: старые копии продолжат показывать отредактированную версию, и расхождение останется в данных.
- Удалённые привязки не осиротевают объекты: отредактированная версия остаётся
привязана к своему фото, поэтому её объекты живут, пока живёт фото, и обычный
ClaimGarbageуберёт их вместе с ним.
4. Проверка — в существующем тесте копирования
backend/internal/photos/copy_test.go уже проверяет, что копия делит объекты с
оригиналом. Добавляется сценарий: после рендера правок копия получает обычные
варианты, но не edit-объекты, и её edit_params пусты.
Risks / Trade-offs
- [Копии, созданные до фикса, продолжат показывать правки до применения миграции] →
Миграция чистит привязки у фото с пустыми
edit_params; после неё поведение единообразно. - [Пользователь может потерять «неожиданно» отредактированную копию] → Копия без параметров правок всё равно не позволяла их править осмысленно: первая новая правка перезаписывала обработку с нуля.
- [Опечатка в списке ролей оставит edit-привязку в копии] → Список ролей берётся из общих констант, а тест проверяет конкретные роли.
- [Миграция удалит привязки у фото, которые потеряли
edit_paramsне из-за копирования] → Таких фото нет:ResetEditудаляет и параметры, и привязки одновременно.
Migration Plan
- Миграция Postgres (
000016_*) и SQLite (000008_*) удаляют «чужие» edit-привязки. Обе идемпотентны (DELETEбезIF EXISTSпо данным). - Изменения в
Clone— только код, схема не меняется. - Откат: миграции
downвозвращают удалённые привязки не могут — откат выполняется только откатом кода; правки фото при этом сохраняются у исходных фото.
Open Questions
Нет.