// Code generated by sqlc. DO NOT EDIT. // versions: // sqlc v1.31.1 // source: playlists.sql package dbq import ( "context" "github.com/jackc/pgx/v5/pgtype" ) const appendPlaylistTrack = `-- name: AppendPlaylistTrack :one INSERT INTO playlist_tracks (playlist_id, position, track_id, title, artist_name, album_title, duration_sec, pick_kind) SELECT $1::uuid, COALESCE((SELECT MAX(position) + 1 FROM playlist_tracks WHERE playlist_id = $1::uuid), 0), t.id, t.title, artists.name, albums.title, (t.duration_ms / 1000)::integer, $2::text FROM tracks t JOIN albums ON albums.id = t.album_id JOIN artists ON artists.id = t.artist_id WHERE t.id = $3::uuid RETURNING playlist_id, position, track_id, title, artist_name, album_title, duration_sec, added_at, pick_kind ` type AppendPlaylistTrackParams struct { PlaylistID pgtype.UUID PickKind *string TrackID pgtype.UUID } // Inserts at the next available position. Snapshot fields are copied // from the tracks/albums/artists join at insert time. tracks.duration_ms // is converted to seconds for the snapshot. pick_kind is NULL for manual // playlists; the For You builder stamps 'taste'/'fresh' (#1249). func (q *Queries) AppendPlaylistTrack(ctx context.Context, arg AppendPlaylistTrackParams) (PlaylistTrack, error) { row := q.db.QueryRow(ctx, appendPlaylistTrack, arg.PlaylistID, arg.PickKind, arg.TrackID) var i PlaylistTrack err := row.Scan( &i.PlaylistID, &i.Position, &i.TrackID, &i.Title, &i.ArtistName, &i.AlbumTitle, &i.DurationSec, &i.AddedAt, &i.PickKind, ) return i, err } const createPlaylist = `-- name: CreatePlaylist :one INSERT INTO playlists (user_id, name, description, is_public) VALUES ($1, $2, $3, $4) RETURNING id, user_id, name, description, is_public, cover_path, track_count, duration_sec, created_at, updated_at, kind, system_variant, seed_artist_id ` type CreatePlaylistParams struct { UserID pgtype.UUID Name string Description string IsPublic bool } func (q *Queries) CreatePlaylist(ctx context.Context, arg CreatePlaylistParams) (Playlist, error) { row := q.db.QueryRow(ctx, createPlaylist, arg.UserID, arg.Name, arg.Description, arg.IsPublic, ) var i Playlist err := row.Scan( &i.ID, &i.UserID, &i.Name, &i.Description, &i.IsPublic, &i.CoverPath, &i.TrackCount, &i.DurationSec, &i.CreatedAt, &i.UpdatedAt, &i.Kind, &i.SystemVariant, &i.SeedArtistID, ) return i, err } const deletePlaylist = `-- name: DeletePlaylist :one DELETE FROM playlists WHERE id = $1 RETURNING id, cover_path ` type DeletePlaylistRow struct { ID pgtype.UUID CoverPath *string } // Returns cover_path so the caller can clean up the cached collage on disk. func (q *Queries) DeletePlaylist(ctx context.Context, id pgtype.UUID) (DeletePlaylistRow, error) { row := q.db.QueryRow(ctx, deletePlaylist, id) var i DeletePlaylistRow err := row.Scan(&i.ID, &i.CoverPath) return i, err } const deletePlaylistTrack = `-- name: DeletePlaylistTrack :exec DELETE FROM playlist_tracks WHERE playlist_id = $1 AND position = $2 ` type DeletePlaylistTrackParams struct { PlaylistID pgtype.UUID Position int32 } // Two-step: delete the row at `position`, then renumber subsequent rows // to close the gap. The renumber is a single UPDATE; the service layer // runs both in one transaction. func (q *Queries) DeletePlaylistTrack(ctx context.Context, arg DeletePlaylistTrackParams) error { _, err := q.db.Exec(ctx, deletePlaylistTrack, arg.PlaylistID, arg.Position) return err } const getPlaylist = `-- name: GetPlaylist :one SELECT p.id, p.user_id, p.name, p.description, p.is_public, p.cover_path, p.track_count, p.duration_sec, p.created_at, p.updated_at, p.kind, p.system_variant, p.seed_artist_id, u.username AS owner_username FROM playlists p JOIN users u ON u.id = p.user_id WHERE p.id = $1 ` type GetPlaylistRow struct { ID pgtype.UUID UserID pgtype.UUID Name string Description string IsPublic bool CoverPath *string TrackCount int32 DurationSec int32 CreatedAt pgtype.Timestamptz UpdatedAt pgtype.Timestamptz Kind string SystemVariant *string SeedArtistID pgtype.UUID OwnerUsername string } func (q *Queries) GetPlaylist(ctx context.Context, id pgtype.UUID) (GetPlaylistRow, error) { row := q.db.QueryRow(ctx, getPlaylist, id) var i GetPlaylistRow err := row.Scan( &i.ID, &i.UserID, &i.Name, &i.Description, &i.IsPublic, &i.CoverPath, &i.TrackCount, &i.DurationSec, &i.CreatedAt, &i.UpdatedAt, &i.Kind, &i.SystemVariant, &i.SeedArtistID, &i.OwnerUsername, ) return i, err } const getPlaylistsByIDs = `-- name: GetPlaylistsByIDs :many SELECT id, user_id, name, description, is_public, cover_path, track_count, duration_sec, created_at, updated_at, kind, system_variant, seed_artist_id FROM playlists WHERE id = ANY($1::uuid[]) ` // Batched lookup used by /api/library/sync to hydrate upsert payloads // (#357). Mirror of GetArtistsByIDs. func (q *Queries) GetPlaylistsByIDs(ctx context.Context, dollar_1 []pgtype.UUID) ([]Playlist, error) { rows, err := q.db.Query(ctx, getPlaylistsByIDs, dollar_1) if err != nil { return nil, err } defer rows.Close() var items []Playlist for rows.Next() { var i Playlist if err := rows.Scan( &i.ID, &i.UserID, &i.Name, &i.Description, &i.IsPublic, &i.CoverPath, &i.TrackCount, &i.DurationSec, &i.CreatedAt, &i.UpdatedAt, &i.Kind, &i.SystemVariant, &i.SeedArtistID, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const listAllPlaylistTracksForCollage = `-- name: ListAllPlaylistTracksForCollage :many SELECT pt.position, albums.cover_art_path AS album_cover_path FROM playlist_tracks pt LEFT JOIN tracks t ON t.id = pt.track_id LEFT JOIN albums ON albums.id = t.album_id WHERE pt.playlist_id = $1 ORDER BY pt.position LIMIT $2 ` type ListAllPlaylistTracksForCollageParams struct { PlaylistID pgtype.UUID Limit int32 } type ListAllPlaylistTracksForCollageRow struct { Position int32 AlbumCoverPath *string } // First N tracks for the collage. Uses LEFT JOIN on albums for the // cover_path; rows with NULL cover_path get the glyph fallback. func (q *Queries) ListAllPlaylistTracksForCollage(ctx context.Context, arg ListAllPlaylistTracksForCollageParams) ([]ListAllPlaylistTracksForCollageRow, error) { rows, err := q.db.Query(ctx, listAllPlaylistTracksForCollage, arg.PlaylistID, arg.Limit) if err != nil { return nil, err } defer rows.Close() var items []ListAllPlaylistTracksForCollageRow for rows.Next() { var i ListAllPlaylistTracksForCollageRow if err := rows.Scan(&i.Position, &i.AlbumCoverPath); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const listPlaylistTracks = `-- name: ListPlaylistTracks :many SELECT pt.playlist_id, pt.position, pt.track_id, pt.title, pt.artist_name, pt.album_title, pt.duration_sec, pt.added_at, pt.pick_kind, t.id AS live_track_id, albums.id AS album_id, artists.id AS artist_id FROM playlist_tracks pt LEFT JOIN tracks t ON t.id = pt.track_id LEFT JOIN albums ON albums.id = t.album_id LEFT JOIN artists ON artists.id = t.artist_id WHERE pt.playlist_id = $1 ORDER BY pt.position ` type ListPlaylistTracksRow struct { PlaylistID pgtype.UUID Position int32 TrackID pgtype.UUID Title string ArtistName string AlbumTitle string DurationSec int32 AddedAt pgtype.Timestamptz PickKind *string LiveTrackID pgtype.UUID AlbumID pgtype.UUID ArtistID pgtype.UUID } // Joined to tracks for the live track id (the service layer derives the // stream URL from it); LEFT JOIN preserves the row when track_id is NULL // (track was removed from the library). The denormalized snapshot fields // on playlist_tracks remain authoritative for title/artist/album text. func (q *Queries) ListPlaylistTracks(ctx context.Context, playlistID pgtype.UUID) ([]ListPlaylistTracksRow, error) { rows, err := q.db.Query(ctx, listPlaylistTracks, playlistID) if err != nil { return nil, err } defer rows.Close() var items []ListPlaylistTracksRow for rows.Next() { var i ListPlaylistTracksRow if err := rows.Scan( &i.PlaylistID, &i.Position, &i.TrackID, &i.Title, &i.ArtistName, &i.AlbumTitle, &i.DurationSec, &i.AddedAt, &i.PickKind, &i.LiveTrackID, &i.AlbumID, &i.ArtistID, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const listPlaylistsForUser = `-- name: ListPlaylistsForUser :many SELECT p.id, p.user_id, p.name, p.description, p.is_public, p.cover_path, p.track_count, p.duration_sec, p.created_at, p.updated_at, p.kind, p.system_variant, p.seed_artist_id, u.username AS owner_username FROM playlists p JOIN users u ON u.id = p.user_id WHERE p.user_id = $1 OR p.is_public = true ORDER BY p.updated_at DESC ` type ListPlaylistsForUserRow struct { ID pgtype.UUID UserID pgtype.UUID Name string Description string IsPublic bool CoverPath *string TrackCount int32 DurationSec int32 CreatedAt pgtype.Timestamptz UpdatedAt pgtype.Timestamptz Kind string SystemVariant *string SeedArtistID pgtype.UUID OwnerUsername string } // Owner's playlists (any visibility) + other users' public playlists. // Ordered by updated_at desc so newly-edited ones float to the top. func (q *Queries) ListPlaylistsForUser(ctx context.Context, userID pgtype.UUID) ([]ListPlaylistsForUserRow, error) { rows, err := q.db.Query(ctx, listPlaylistsForUser, userID) if err != nil { return nil, err } defer rows.Close() var items []ListPlaylistsForUserRow for rows.Next() { var i ListPlaylistsForUserRow if err := rows.Scan( &i.ID, &i.UserID, &i.Name, &i.Description, &i.IsPublic, &i.CoverPath, &i.TrackCount, &i.DurationSec, &i.CreatedAt, &i.UpdatedAt, &i.Kind, &i.SystemVariant, &i.SeedArtistID, &i.OwnerUsername, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const renumberPlaylistTracksAfter = `-- name: RenumberPlaylistTracksAfter :exec UPDATE playlist_tracks SET position = position - 1 WHERE playlist_id = $1 AND position > $2 ` type RenumberPlaylistTracksAfterParams struct { PlaylistID pgtype.UUID Position int32 } // Used after DeletePlaylistTrack to close the gap. func (q *Queries) RenumberPlaylistTracksAfter(ctx context.Context, arg RenumberPlaylistTracksAfterParams) error { _, err := q.db.Exec(ctx, renumberPlaylistTracksAfter, arg.PlaylistID, arg.Position) return err } const setPlaylistCover = `-- name: SetPlaylistCover :exec UPDATE playlists SET cover_path = $2, updated_at = now() WHERE id = $1 ` type SetPlaylistCoverParams struct { ID pgtype.UUID CoverPath *string } func (q *Queries) SetPlaylistCover(ctx context.Context, arg SetPlaylistCoverParams) error { _, err := q.db.Exec(ctx, setPlaylistCover, arg.ID, arg.CoverPath) return err } const updatePlaylist = `-- name: UpdatePlaylist :one UPDATE playlists SET name = CASE WHEN $1::boolean THEN $2::text ELSE name END, description = CASE WHEN $3::boolean THEN $4::text ELSE description END, is_public = CASE WHEN $5::boolean THEN $6::boolean ELSE is_public END, updated_at = now() WHERE id = $7 RETURNING id, user_id, name, description, is_public, cover_path, track_count, duration_sec, created_at, updated_at, kind, system_variant, seed_artist_id ` type UpdatePlaylistParams struct { UpdateName bool Name string UpdateDescription bool Description string UpdateIsPublic bool IsPublic bool ID pgtype.UUID } // Updates only the fields whose corresponding `updateX` flag is true. // The flags let the service layer keep PATCH semantics (only-touch-what-the-caller-sent) // without writing N variants. func (q *Queries) UpdatePlaylist(ctx context.Context, arg UpdatePlaylistParams) (Playlist, error) { row := q.db.QueryRow(ctx, updatePlaylist, arg.UpdateName, arg.Name, arg.UpdateDescription, arg.Description, arg.UpdateIsPublic, arg.IsPublic, arg.ID, ) var i Playlist err := row.Scan( &i.ID, &i.UserID, &i.Name, &i.Description, &i.IsPublic, &i.CoverPath, &i.TrackCount, &i.DurationSec, &i.CreatedAt, &i.UpdatedAt, &i.Kind, &i.SystemVariant, &i.SeedArtistID, ) return i, err } const updatePlaylistRollups = `-- name: UpdatePlaylistRollups :exec UPDATE playlists SET track_count = (SELECT COUNT(*) FROM playlist_tracks pt WHERE pt.playlist_id = $1), duration_sec = (SELECT COALESCE(SUM(pt.duration_sec), 0) FROM playlist_tracks pt WHERE pt.playlist_id = $1), updated_at = now() WHERE id = $1 ` // Set track_count + duration_sec from a fresh aggregate. Called after // every mutation that touches playlist_tracks. Cheap; the table is small. func (q *Queries) UpdatePlaylistRollups(ctx context.Context, playlistID pgtype.UUID) error { _, err := q.db.Exec(ctx, updatePlaylistRollups, playlistID) return err }