feat(photos): fix trash permanent delete and add empty trash

Permanently deleting a photo whose album was deleted failed because
authorization resolved through the album. Fall back to uploader
ownership when the photo is albumless. Add a bulk DELETE /trash that
removes every trashed photo the caller may delete (uncapped listing)
and returns the deleted count, with an «Очистить корзину» action on the
trash screen.
This commit is contained in:
iu
2026-10-11 20:54:54 +03:00
parent d5d3f55db8
commit 3b745fa737
16 changed files with 643 additions and 13 deletions
@@ -0,0 +1,45 @@
# Proposal
## Why
Permanently deleting a photo from the trash fails when the photo has no album
(`album_id IS NULL`) — the normal result of deleting an album, whose photos move
to the trash unbound (`photos/spec.md`, «Удаление альбома»). `PermanentDelete`
authorizes through the album, so these photos can never be removed, and the UI
surfaces `album_not_found`. In the local database 4010 of 4011 trashed photos are
in this state. There is also no way to clear the trash in bulk.
## What Changes
- Fix `PermanentDelete` authorization so a photo without an album can be
permanently deleted by a user who may delete it (its uploader), instead of
failing on a missing album.
- Add a bulk permanent-delete operation that empties the trash for the caller,
skipping photos the caller is not allowed to remove, and reporting how many
were deleted.
- Add an «Очистить корзину» action on the trash screen with a confirmation
dialog, refreshing the list and the rail counter afterwards.
## Capabilities
### New Capabilities
<!-- none: this extends existing photo lifecycle and workspace behavior -->
### Modified Capabilities
- `photos`: «Окончательное удаление» also covers photos with no album
(deleted album); add an «Очистка корзины» requirement for bulk permanent
removal.
- `ui-workspace`: trash screen gains an «Очистить корзину» control with
confirmation and post-action refresh.
## Impact
- Backend: `backend/internal/photos/service.go` (`PermanentDelete`), the photos
repository/store (new bulk purge method), `handlers.go`, and a route in
`backend/internal/app/app.go`.
- Frontend: `frontend/src/lib/api.ts`, `frontend/src/lib/stores/trash.svelte.ts`,
`frontend/src/routes/(app)/trash/+page.svelte`.
- Tests: e2e/contract coverage for permanently deleting an albumless photo and
for the bulk empty-trash path.