Delete Songs and Tracks
Type-to-confirm deletion for songs and individual tracks
Overview
Owners can delete a single track or an entire song. Both go through the same type-to-confirm modal — the user must type the literal word Delete before the action is enabled — rather than a plain "Are you sure?" dialog, to make an irreversible action harder to trigger by accident.
How it works
DeleteConfirmModal.tsxis generic: it takes atitle,description, optionaladvisorytext, and anonConfirmcallback — used for both the track-delete and song-delete flows with different copy.- The confirm button stays disabled until the typed text exactly matches
Delete(CONFIRM_WORD). - Track delete:
DELETE /api/songs/[id]/tracks/[trackName]→deleteSongTrack()insrc/lib/storage.ts, which branches per backend (GitHub file delete vs. Blob object delete). - Song delete:
DELETE /api/songs/[id]checkssong.ownerId === session.user.id(403 otherwise), callsdeleteSongObjects(), then deletes theSongrow from Postgres. - Delete buttons only render at all when the viewer is the owner (
MultiTrackPlayer.tsxpassesonDeleteClickconditionally), on top of the server-side ownership check.
Key files
| File | Role |
|---|---|
src/components/DeleteConfirmModal.tsx | Generic type-to-confirm modal |
src/app/api/songs/[id]/route.ts | DELETE — song deletion (ownership check, storage cleanup, Postgres delete) |
src/app/api/songs/[id]/tracks/[trackName]/route.ts | DELETE — single track deletion |
src/lib/storage.ts | deleteSongTrack(), deleteSongObjects() |
Limitations / notes
- Deleting a GITHUB-backed song only removes its Postgres row — the GitHub repo itself is left behind (see Storage Backends for why: no
delete_repoOAuth scope). Deleting a BLOB-backed song does remove every object under its prefix. - No undo/trash — deletion is immediate and final once confirmed.