feat: Settings modal with danger zone admin operations
Backend: - POST /api/admin/clear-waveforms — deletes all cached waveform SVGs - POST /api/admin/delete-all-clips — nuclear option: deletes all clips, MP3s, artwork, and waveforms. Returns deleted count. - database.py: delete_all_clips() for bulk DB wipe - Clip deletion cascade-removes waveform SVGs Frontend: - SettingsModal.tsx: center-stage modal with backdrop blur (bg-black/60 backdrop-blur-sm). Danger zone with red-styled border and header. - Two-step confirmation for Delete All (click once to arm, click again to confirm). Shows clip count dynamically. - Gear icon trigger in header bar - App.tsx: handleClearWaveforms + handleDeleteAll handlers Docs: - Architecture KB updated with album art, waveform, settings modal sections - Tasks KB updated with T-021 (waveform) and TASK-001 (album art) marked done - T-026 (settings modal) added to backlog
This commit is contained in:
1 parent
744f0ae6e5
commit
e74c89e1b0
5 files changed
+219
-209
No files matched your search
@@ -151,4 +151,9 @@ async def remove_artwork(clip_id: str):
|
||||
async def delete_clip(clip_id: str):
|
||||
async with aiosqlite.connect(DB_PATH) as db:
|
||||
await db.execute("DELETE FROM clips WHERE id = ?", (clip_id,))
|
||||
await db.commit()
|
||||
|
||||
async def delete_all_clips():
|
||||
async with aiosqlite.connect(DB_PATH) as db:
|
||||
await db.execute("DELETE FROM clips")
|
||||
await db.commit()
|
||||
+37
-1
@@ -10,7 +10,7 @@ from dotenv import load_dotenv
|
||||
load_dotenv(Path(__file__).parent.parent / ".env")
|
||||
|
||||
from models import GenerateRequest, GenerateResponse, ClipList, RenameRequest, LYRIA_MODELS, DEFAULT_MODEL
|
||||
from database import init_db, create_clip, get_clips, get_clip, delete_clip, update_clip_status, rename_clip, set_artwork, remove_artwork
|
||||
from database import init_db, create_clip, get_clips, get_clip, delete_clip, update_clip_status, rename_clip, set_artwork, remove_artwork, delete_all_clips
|
||||
from lyria_client import generate_clip, save_clip_file
|
||||
from waveform import generate_waveform_svg, get_waveform_path
|
||||
|
||||
@@ -226,6 +226,42 @@ async def rename_clip_endpoint(clip_id: str, request: RenameRequest):
|
||||
updated = await get_clip(clip_id)
|
||||
return updated
|
||||
|
||||
# ─── Admin Endpoints ──────────────────────────────────────────────
|
||||
|
||||
@app.post("/api/admin/clear-waveforms")
|
||||
async def clear_waveforms():
|
||||
"""Delete all cached waveform SVGs. They regenerate on next view."""
|
||||
from waveform import WAVEFORM_DIR
|
||||
count = 0
|
||||
if WAVEFORM_DIR.exists():
|
||||
for f in WAVEFORM_DIR.glob("*.svg"):
|
||||
f.unlink()
|
||||
count += 1
|
||||
return {"cleared": count}
|
||||
|
||||
@app.post("/api/admin/delete-all-clips")
|
||||
async def delete_all_clips_endpoint():
|
||||
"""Delete all clips, MP3s, artwork, and waveforms. Nuclear option."""
|
||||
# Get all clips to clean up files
|
||||
all_clips = await get_clips()
|
||||
for clip in all_clips:
|
||||
# Delete MP3
|
||||
mp3_path = CLIPS_DIR / clip["filename"]
|
||||
if mp3_path.exists():
|
||||
mp3_path.unlink()
|
||||
# Delete artwork
|
||||
if clip.get("artwork_filename"):
|
||||
art_path = ARTWORK_DIR / clip["artwork_filename"]
|
||||
if art_path.exists():
|
||||
art_path.unlink()
|
||||
# Delete waveform
|
||||
wf_path = get_waveform_path(clip["id"])
|
||||
if wf_path and wf_path.exists():
|
||||
wf_path.unlink()
|
||||
# Clear DB
|
||||
await delete_all_clips()
|
||||
return {"deleted": len(all_clips)}
|
||||
|
||||
if __name__ == "__main__":
|
||||
import uvicorn
|
||||
uvicorn.run(app, host="0.0.0.0", port=8000)
|
||||
Reference in new issue
Block a user