6.3 KiB
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
- Right-click on any anime series card in the library grid.
- Select "Delete Anime" from the context menu.
- 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
- Type
deletein the confirmation text field to enable the Delete button. - 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:
{
"delete_database": true,
"delete_folder": false,
"confirm_text": "delete"
}
Success response (200):
{
"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:
{
"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 forAnimeService.delete_series()tests/api/test_delete_anime_endpoint.py— API endpoint tests including auth, validation, error casestests/frontend/test_delete_modal.py— frontend modal logic and DOM validation teststests/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. |