180 lines
6.3 KiB
Markdown
180 lines
6.3 KiB
Markdown
# 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. |
|