added delete option
This commit is contained in:
65
Docs/API.md
65
Docs/API.md
@@ -368,6 +368,71 @@ Return detailed information about a specific series.
|
||||
|
||||
Source: [src/server/api/anime.py](../src/server/api/anime.py#L713-L793)
|
||||
|
||||
### DELETE /api/anime/{anime_key}
|
||||
|
||||
Delete an anime series from the database, filesystem, or both. Requires
|
||||
authentication and explicit typed confirmation.
|
||||
|
||||
**Authentication:** Required
|
||||
|
||||
**Path Parameters:**
|
||||
| Parameter | Description |
|
||||
|-----------|-------------|
|
||||
| `anime_key` | Series key (primary identifier) |
|
||||
|
||||
**Request Body:**
|
||||
```json
|
||||
{
|
||||
"delete_database": true,
|
||||
"delete_folder": false,
|
||||
"confirm_text": "delete"
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Default | Description |
|
||||
|-------|------|---------|-------------|
|
||||
| `delete_database` | bool | `true` | Remove series and episodes from SQLite |
|
||||
| `delete_folder` | bool | `false` | Delete the series folder and all files |
|
||||
| `confirm_text` | string | — | Must be exactly `"delete"` (case-sensitive) |
|
||||
|
||||
**Response (200 OK):**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"key": "attack-on-titan",
|
||||
"name": "Attack on Titan",
|
||||
"deleted_from_database": true,
|
||||
"deleted_folder": false,
|
||||
"folder_path": null,
|
||||
"database_error": null,
|
||||
"folder_error": null,
|
||||
"message": "Removed from database."
|
||||
}
|
||||
```
|
||||
|
||||
**Error Responses:**
|
||||
|
||||
| Status | Condition |
|
||||
|--------|-----------|
|
||||
| 400 | `confirm_text != "delete"` or neither flag is `true` |
|
||||
| 401 | Not authenticated |
|
||||
| 404 | Series `key` not found in database |
|
||||
| 500 | Unexpected server error |
|
||||
|
||||
**Deletion Modes:**
|
||||
|
||||
| Flags | Effect |
|
||||
|-------|--------|
|
||||
| `delete_database=true, delete_folder=false` | Removes series from SQLite. Folder on disk is preserved. |
|
||||
| `delete_database=false, delete_folder=true` | Deletes folder and all files. Database record preserved. |
|
||||
| `delete_database=true, delete_folder=true` | Full removal: database record deleted AND folder/files deleted. |
|
||||
|
||||
**Path Safety:** Folder deletion is blocked if the path is outside the configured anime base directory (path traversal protection via `is_safe_path`).
|
||||
|
||||
**WebSocket Broadcast:** On success, a `series_deleted` event is broadcast to all connected clients, causing the anime card to be removed from all browser sessions in real-time.
|
||||
|
||||
Source: [src/server/api/anime.py](../src/server/api/anime.py#L1759-L1840)
|
||||
|
||||
---
|
||||
|
||||
## 4. Download Queue Endpoints
|
||||
|
||||
@@ -41,6 +41,35 @@ This changelog follows [Keep a Changelog](https://keepachangelog.com/) principle
|
||||
|
||||
### Added
|
||||
|
||||
- **Delete Anime Feature** — Right-click on any anime card and select
|
||||
"Delete Anime" to remove a series. Three modes are available:
|
||||
database only, folder only, or both. A typed-confirmation
|
||||
(`delete`) is required to prevent accidental deletions. The
|
||||
operation is broadcast via WebSocket so all connected clients
|
||||
remove the card in real-time. Path traversal protection prevents
|
||||
folder deletion outside the anime base directory.
|
||||
- `DELETE /api/anime/{key}` endpoint (`src/server/api/anime.py`)
|
||||
- `AnimeService.delete_series()` orchestrator
|
||||
(`src/server/services/anime_service.py`)
|
||||
- `broadcast_series_deleted()` WebSocket broadcast
|
||||
(`src/server/services/websocket_service.py`)
|
||||
- `DeleteSeriesRequest` / `DeleteSeriesResult` Pydantic models
|
||||
(`src/server/models/anime.py`)
|
||||
- Frontend modal with typed confirmation
|
||||
(`src/server/web/static/js/index/delete-modal.js`)
|
||||
- Right-click "Delete Anime" context menu item
|
||||
(`src/server/web/static/js/index/context-menu.js`)
|
||||
- `SERIES_DELETED` WebSocket event handling
|
||||
(`src/server/web/static/js/index/socket-handler.js`)
|
||||
- `SeriesManager.removeSeries()` grid cleanup
|
||||
(`src/server/web/static/js/index/series-manager.js`)
|
||||
- Full test suite:
|
||||
`tests/unit/test_delete_anime_service.py`,
|
||||
`tests/api/test_delete_anime_endpoint.py`,
|
||||
`tests/frontend/test_delete_modal.py`,
|
||||
`tests/security/test_delete_anime_security.py`
|
||||
- Documentation: `Docs/DELETE_ANIME_FEATURE.md`
|
||||
|
||||
- **Anime Settings page** — renamed from "NFO Diagnostics". Right-click
|
||||
on any anime card → "Anime Settings" navigates to
|
||||
`/anime/settings?key=<series>`. The new page lets the user view and
|
||||
|
||||
179
Docs/DELETE_ANIME_FEATURE.md
Normal file
179
Docs/DELETE_ANIME_FEATURE.md
Normal file
@@ -0,0 +1,179 @@
|
||||
# Delete Anime Feature
|
||||
|
||||
## Overview
|
||||
|
||||
The Delete Anime feature allows authenticated users to remove an anime series from the Aniworld library. It supports three deletion modes: **database only**, **folder only**, or **both**. A mandatory typed-confirmation (`delete`) prevents accidental deletions.
|
||||
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
### How to Delete an Anime
|
||||
|
||||
1. **Right-click** on any anime series card in the library grid.
|
||||
2. Select **"Delete Anime"** from the context menu.
|
||||
3. A confirmation modal appears with two options:
|
||||
- ☑️ **Remove from database** (recommended) — removes series and episodes from SQLite
|
||||
- ☐ **Delete folder from filesystem** — deletes the folder and all files inside
|
||||
4. **Type `delete`** in the confirmation text field to enable the Delete button.
|
||||
5. Click **Delete** to proceed.
|
||||
|
||||
### What Gets Deleted
|
||||
|
||||
| Option | Effect |
|
||||
|--------|--------|
|
||||
| Database only | Series, episodes, and queue entries removed from SQLite. Folder on disk is preserved. Downloaded episode files remain. |
|
||||
| Folder only | Entire folder and all files inside deleted from filesystem. Database record preserved with `is_downloaded=True`. |
|
||||
| Both | Full removal: database record deleted AND folder/files deleted from disk. |
|
||||
|
||||
---
|
||||
|
||||
## Architecture
|
||||
|
||||
### Backend Components
|
||||
|
||||
| File | Role |
|
||||
|------|------|
|
||||
| `src/server/api/anime.py` | `DELETE /api/anime/{key}` endpoint |
|
||||
| `src/server/services/anime_service.py` | `AnimeService.delete_series()` orchestrator |
|
||||
| `src/server/services/websocket_service.py` | `broadcast_series_deleted()` for real-time UI updates |
|
||||
| `src/server/database/service.py` | `AnimeSeriesService.get_folder_path()` + existing `delete()` |
|
||||
| `src/server/models/anime.py` | `DeleteSeriesRequest` / `DeleteSeriesResult` Pydantic models |
|
||||
|
||||
### Frontend Components
|
||||
|
||||
| File | Role |
|
||||
|------|------|
|
||||
| `src/server/web/static/js/index/delete-modal.js` | Modal UI, confirm text validation, API calls |
|
||||
| `src/server/web/static/js/index/context-menu.js` | Right-click "Delete Anime" menu item |
|
||||
| `src/server/web/static/js/index/socket-handler.js` | `SERIES_DELETED` WebSocket event handler |
|
||||
| `src/server/web/static/js/index/series-manager.js` | `removeSeries(key)` — removes card from grid |
|
||||
| `src/server/web/static/js/index/app-init.js` | Initializes `DeleteModal` |
|
||||
| `src/server/web/static/css/components/modals.css` | Modal and context menu styles |
|
||||
| `src/server/web/templates/index.html` | Loads `delete-modal.js` before `app-init.js` |
|
||||
|
||||
### API Endpoint
|
||||
|
||||
```
|
||||
DELETE /api/anime/{key}
|
||||
```
|
||||
|
||||
**Request body:**
|
||||
```json
|
||||
{
|
||||
"delete_database": true,
|
||||
"delete_folder": false,
|
||||
"confirm_text": "delete"
|
||||
}
|
||||
```
|
||||
|
||||
**Success response (200):**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"key": "attack-on-titan",
|
||||
"name": "Attack on Titan",
|
||||
"deleted_from_database": true,
|
||||
"deleted_folder": false,
|
||||
"folder_path": null,
|
||||
"database_error": null,
|
||||
"folder_error": null,
|
||||
"message": "Removed from database."
|
||||
}
|
||||
```
|
||||
|
||||
**Error responses:**
|
||||
| Status | Condition |
|
||||
|--------|-----------|
|
||||
| 400 | `confirm_text != "delete"` or neither flag is `true` |
|
||||
| 401 | Not authenticated |
|
||||
| 404 | Series `key` not found in database |
|
||||
| 500 | Unexpected server error |
|
||||
|
||||
### WebSocket Event
|
||||
|
||||
After a successful delete, the server broadcasts a `series_deleted` event:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "series_deleted",
|
||||
"data": {
|
||||
"key": "attack-on-titan",
|
||||
"name": "Attack on Titan"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
All connected clients remove the card from their grid in real-time.
|
||||
|
||||
---
|
||||
|
||||
## Safety Mechanisms
|
||||
|
||||
### 1. Typed Confirmation
|
||||
Users must type exactly `delete` (case-sensitive) to unlock the Delete button. This prevents accidental clicks from triggering deletion.
|
||||
|
||||
### 2. Path Traversal Protection
|
||||
Before deleting a folder, `is_safe_path()` validates the path stays within the configured anime base directory. Paths outside this boundary are rejected with a `folder_error`.
|
||||
|
||||
### 3. Granular Options
|
||||
The two independent checkboxes ensure users consciously choose what to delete. Default is **database only** (recommended).
|
||||
|
||||
### 4. WebSocket Broadcast
|
||||
All clients are notified immediately when a series is deleted, keeping multiple browser sessions in sync.
|
||||
|
||||
### 5. No Shell Injection
|
||||
Series keys are never passed to shell commands. All file operations use `pathlib.Path`.
|
||||
|
||||
---
|
||||
|
||||
## Logging
|
||||
|
||||
### Backend Logs (Python/`logging`)
|
||||
|
||||
| Event | Level | Message |
|
||||
|-------|-------|---------|
|
||||
| Delete initiated | INFO | `Delete anime initiated: key={key} delete_db={x} delete_folder={x}` |
|
||||
| Series not found | WARNING | `Delete anime failed — series not found: key={key}` |
|
||||
| Path traversal attempt | WARNING | `Delete anime blocked — path traversal attempt: key={key} path={path}` |
|
||||
| DB error | ERROR | `Delete anime DB error: key={key} error={message}` |
|
||||
| Folder delete error | ERROR | `Delete anime folder error: key={key} error={message}` |
|
||||
| Delete succeeded | INFO | `Delete anime succeeded: key={key} deleted_db={x} deleted_folder={x}` |
|
||||
|
||||
### Frontend Logs (JS/`console`)
|
||||
|
||||
| Event | Method |
|
||||
|-------|--------|
|
||||
| Modal opened | `console.info('[DeleteModal] Opening for key:', key)` |
|
||||
| Delete confirmed | `console.info('[DeleteModal] Initiating delete:', {...})` |
|
||||
| Delete succeeded | `console.info('[DeleteModal] Delete succeeded:', result)` |
|
||||
| API/network error | `console.error('[DeleteModal] Delete request failed:', err)` |
|
||||
| Series removed from grid | `console.info('[SeriesManager] Removed series from local state:', key)` |
|
||||
| WS event received | `console.info('[SocketHandler] Series deleted:', data)` |
|
||||
|
||||
---
|
||||
|
||||
## Configuration
|
||||
|
||||
No new configuration options are required. The feature uses existing paths:
|
||||
- **Anime base directory**: `settings.anime_directory` (for path traversal validation)
|
||||
- **Database path**: `series_app.database_path` (for DB deletion)
|
||||
- **Queue cleanup**: `AnimeSeriesService.delete(series_key)` cascades to queue items
|
||||
|
||||
---
|
||||
|
||||
## Testing
|
||||
|
||||
See:
|
||||
- `tests/unit/test_delete_anime_service.py` — unit tests for `AnimeService.delete_series()`
|
||||
- `tests/api/test_delete_anime_endpoint.py` — API endpoint tests including auth, validation, error cases
|
||||
- `tests/frontend/test_delete_modal.py` — frontend modal logic and DOM validation tests
|
||||
- `tests/security/test_delete_anime_security.py` — security tests for path traversal, XSS, auth bypass
|
||||
|
||||
---
|
||||
|
||||
## Changelog
|
||||
|
||||
| Date | Change |
|
||||
|------|--------|
|
||||
| 2026-08-16 | Feature added. `DELETE /api/anime/{key}`, right-click context menu, typed-confirmation modal, WebSocket sync. |
|
||||
@@ -70,6 +70,7 @@ The application now features a comprehensive configuration system that allows us
|
||||
- **Library Scanning**: Automated scanning for missing episodes with database persistence
|
||||
- **Episode Tracking**: Missing episodes tracked in database, automatically updated during scans
|
||||
- **NFO Status Indicators**: Visual badges showing NFO and media file status for each series
|
||||
- **Delete Anime**: Right-click any anime card → "Delete Anime" to remove a series from the database, filesystem, or both. Type `delete` in the confirmation field to proceed. See [Delete Anime Feature](./DELETE_ANIME_FEATURE.md) for details.
|
||||
|
||||
## NFO Metadata Management
|
||||
|
||||
|
||||
Reference in New Issue
Block a user