- fix card-stale-after-download-finish: карточка списка не обновляется после выхода задачи из downloading - goal bulk-download-management + feature bulk-delete-page: удаление раздач с файлами пачкой на отдельной странице
65 lines
5.3 KiB
Markdown
65 lines
5.3 KiB
Markdown
# ✨ Удалять выбранные загрузки с файлами на отдельной странице
|
||
|
||
- **Тип:** feature
|
||
- **Категория:** Ядро продукта
|
||
- **Зачем:** удаление с файлами живёт только в danger-секции страницы одной загрузки: чтобы снести десять раздач, надо десять раз пройти путь список → карточка → подтверждение
|
||
- **Теги:** goal:bulk-download-management
|
||
|
||
Отдельная страница веб-UI, доступная из шапки: список загрузок, у которых
|
||
удаление разрешено (`done`, `orphaned`, `target_missing` —
|
||
`internal/worker/review.go:631`), чекбокс у каждой строки и одна кнопка
|
||
«Удалить выбранные». Кнопка ведёт на подтверждение, где выбранные раздачи
|
||
названы поимённо, и только оттуда уходит удаление.
|
||
|
||
Страница отдельная, чтобы не вешать режим выбора на основной список: там
|
||
карточки живые, самополлятся и перерисовываются, а выбор пользователя такое
|
||
перерисовывание переживать не обязан.
|
||
|
||
Речь только о полном удалении (снять хардлинки + снести раздачу с файлами из
|
||
qBittorrent). Групповой `Dismiss` (смена статуса без файлов) в эту задачу не
|
||
входит.
|
||
|
||
## Затрагивает
|
||
|
||
- новая страница веб-UI и ссылка на неё в `web/templates/partials/header.html`;
|
||
новый шаблон страницы и её партиалы;
|
||
- новый POST-эндпоинт группового удаления в `internal/httpapi` (рядом с
|
||
`POST /ui/downloads/{id}/delete`, `internal/httpapi/httpapi.go:143`);
|
||
- `Reviewer.Delete` воркера (`internal/worker/review.go:619`) — зовётся по
|
||
каждой выбранной загрузке;
|
||
- qBittorrent `torrents/delete` с `deleteFiles=true` — внешний сервис,
|
||
необратимая операция;
|
||
- спека `web-ui`, требования «Страницы веб-UI» и «Действия соответствуют
|
||
состоянию»;
|
||
- спека `state-reconciliation` в части `Delete` — групповой вызов не меняет
|
||
условий поштучного, но подтверждение человека теперь одно на пачку.
|
||
|
||
## Критерии приёмки
|
||
|
||
- Страница открывается из шапки и показывает только те загрузки, для которых
|
||
удаление разрешено поштучно (оракул: тест `internal/httpapi` — подставной
|
||
читатель отдаёт задачи во всех состояниях, в разметке строки есть у
|
||
`done`/`orphaned`/`target_missing` и нет у остальных).
|
||
- Удаление уходит только после явного подтверждения, и подтверждение называет
|
||
каждую выбранную раздачу поимённо (оракул: тест — POST без признака
|
||
подтверждения отвечает отказом и не делает ни одного вызова `Delete` у
|
||
подставного воркера; ответ подтверждения содержит заголовки всех выбранных).
|
||
- Отказ на одной загрузке не отменяет остальных, а результат называет
|
||
удалённые и отказавшие поимённо с причиной (оракул: тест, где второй `Delete`
|
||
возвращает ошибку — первая и третья удалены, страница результата называет
|
||
вторую и её причину).
|
||
- Групповой путь не расширяет прав поштучного: попытка удалить задачу в
|
||
состоянии, где кнопка недоступна, отклоняется с тем же отказом
|
||
(оракул: тест — `Delete` для `downloading` возвращает `ErrConflict`, страница
|
||
показывает отказ, остальные выбранные не затронуты).
|
||
- Поведение страницы записано дельта-спекой и проходит валидацию (оракул:
|
||
`openspec validate --strict` и `task gate`).
|
||
|
||
## Рамки
|
||
|
||
Необратимое действие: `deleteFiles=true` сносит файлы раздачи, гард последней
|
||
копии в `Delete` выключен сознательно. Ослаблять подтверждение ради удобства
|
||
пачки нельзя — оно остаётся обязательным и поимённым. Групповой `Dismiss` и
|
||
автоматическая чистка по сроку хранения (`db-retention-cleanup`) — не эта
|
||
задача.
|