feat(e10): make "move a video to another list" one atomic request

The one-step move S2 needs has no honest client-side shape: "add to the target, then
remove from the source" leaves the video in BOTH lists whenever the second call fails,
and the reverse order loses it entirely. So it is one endpoint in one transaction —
POST /api/playlists/{id}/items/{video_id}/move — which re-parents the existing item row
(so `added_at` survives), recomputes `dirty` on both sides, and is idempotent when the
target already holds the video.

Also publishes `updated_at` on the playlist summary, and bumps it explicitly for the
edits that only write `playlist_items` (add / remove / reorder / move). UpdatedAtMixin's
`onupdate` fires on an UPDATE of the playlist row, so without `_touch` the rail's coming
"recently changed" sort would have seen renames and nothing else.
This commit is contained in:
2026-09-05 19:59:41 +02:00
parent 7e97559615
commit b28be682fe
4 changed files with 282 additions and 0 deletions
+12
View File
@@ -18,6 +18,7 @@ export interface Playlist {
item_count: number;
total_duration_seconds: number;
cover_thumbnail: string | null;
updated_at: string; // ISO; when the list last CHANGED (rename, add/remove/move/reorder)
has_video?: boolean; // only set when listed with ?contains=<video_id>
}
@@ -74,6 +75,17 @@ export const playlistsApi = {
}),
removeFromPlaylist: (id: number, videoId: string) =>
req(`/api/playlists/${id}/items/${videoId}`, { method: "DELETE" }),
// One request on purpose: add-then-remove from the client leaves the video in BOTH lists when
// the second call fails. The server does it in one transaction.
moveToPlaylist: (
id: number,
videoId: string,
targetPlaylistId: number,
): Promise<{ ok: true; from_playlist_id: number; to_playlist_id: number; video_id: string }> =>
req(`/api/playlists/${id}/items/${videoId}/move`, {
method: "POST",
body: JSON.stringify({ target_playlist_id: targetPlaylistId }),
}),
reorderPlaylist: (id: number, videoIds: string[]) =>
req(`/api/playlists/${id}/order`, {
method: "PUT",