From aab61631a397012f581fc2b9fd078eb9e09650cf Mon Sep 17 00:00:00 2001 From: mitteneer Date: Sun, 25 May 2025 16:16:18 -0400 Subject: [PATCH 1/4] Directly append complete_scrobble in upload.zig Thanks bob :) --- build.zig.zon | 4 ++-- src/app/views/upload.zig | 15 +-------------- 2 files changed, 3 insertions(+), 16 deletions(-) diff --git a/build.zig.zon b/build.zig.zon index d7fb785..5db05e3 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -17,8 +17,8 @@ // internet connectivity. .dependencies = .{ .jetzig = .{ - .url = "https://github.com/jetzig-framework/jetzig/archive/7be1d137fcab5c422e05f12092f6e04a02900d6f.tar.gz", - .hash = "jetzig-0.0.0-IpAgLURbDwB1NlywUH7lnQ3zptNvSQWVosaA1k7l1cNz", + .url = "https://github.com/jetzig-framework/jetzig/archive/695664bc10e6e73389ee2fe7fbcaedc27fa8adc9.tar.gz", + .hash = "jetzig-0.0.0-IpAgLSFeDwBEhfEaDBo0JqhRbvQA3ScbWjssBqhJHFmb", }, .zeit = .{ .url = "https://github.com/rockorager/zeit/archive/refs/tags/v0.6.0.tar.gz", diff --git a/src/app/views/upload.zig b/src/app/views/upload.zig index af5d41d..f605982 100644 --- a/src/app/views/upload.zig +++ b/src/app/views/upload.zig @@ -138,20 +138,7 @@ pub fn post(request: *jetzig.Request) !jetzig.View { const row = try Utils.scrobbleToRow(request.allocator, complete_scrobble); try view_params.append(row); - var job_param = try job_params.append(.object); - try job_param.put("album", complete_scrobble.album); - try job_param.put("track", complete_scrobble.track); - try job_param.put("date", complete_scrobble.date); - - var track_artists_array = try job_param.put("artists_track", .array); - for (complete_scrobble.artists_track) |a| { - try track_artists_array.append(a); - } - - var album_artists_array = try job_param.put("artists_album", .array); - for (complete_scrobble.artists_album) |a| { - try album_artists_array.append(a); - } + try job_params.append(complete_scrobble); } }, } From f59eec79a82584bd7bcc88277b3a0b6f86ba0eb8 Mon Sep 17 00:00:00 2001 From: mitteneer Date: Mon, 26 May 2025 11:15:19 -0400 Subject: [PATCH 2/4] Removed inline else from upload.zig If I can figure out a way to get an array of a union instead of a union of arrays, we're in business to make this even better, but this is fine right now. The inline else was just a dumb way to keep the for on the outside --- src/app/views/upload.zig | 108 +++++++++++++++++++++------------------ 1 file changed, 59 insertions(+), 49 deletions(-) diff --git a/src/app/views/upload.zig b/src/app/views/upload.zig index f605982..bdb5cef 100644 --- a/src/app/views/upload.zig +++ b/src/app/views/upload.zig @@ -81,58 +81,68 @@ pub fn post(request: *jetzig.Request) !jetzig.View { // Not sure if I should be proud or feel sick switch (imported_scrobbles) { - inline else => |scrobbles| { + .LastFMStats => |scrobbles| { appends: for (scrobbles) |scrobble| { - const filtered_scrobble: Data.Scrobble = blk: switch (@TypeOf(scrobble)) { - Data.IgnorantScrobble => { - if (scrobble.date > latest_timestamp * 1_000 or scrobble.date < earliest_timestamp * 1_000) { - limited_tracks += 1; - continue :appends; - } - break :blk Data.Scrobble{ - .album = scrobble.album, - .artists_album = &[_][]const u8{scrobble.artist}, - .track = scrobble.track, - .artists_track = &[_][]const u8{scrobble.artist}, - .date = scrobble.date * 1_000, - }; - }, - Data.SpotifyScrobble => { - if (scrobble.ms_played < 30_000 and (scrobble.reason_end == null or !std.mem.eql(u8, scrobble.reason_end.?, "trackdone"))) { - skipped_tracks += 1; - continue :appends; - } - if (scrobble.master_metadata_album_artist_name == null or scrobble.master_metadata_track_name == null) { - skipped_tracks += 1; - continue :appends; - } - - const iso_ts = try zeit.Time.fromISO8601(scrobble.ts); - if ((iso_ts.after(latest_date) or iso_ts.before(earliest_date))) { - limited_tracks += 1; - continue :appends; - } - - break :blk Data.Scrobble{ - .album = scrobble.master_metadata_album_album_name.?, - .artists_album = &[_][]const u8{scrobble.master_metadata_album_artist_name.?}, - .track = scrobble.master_metadata_track_name.?, - .artists_track = &[_][]const u8{scrobble.master_metadata_album_artist_name.?}, - .date = (try zeit.instant(.{ .source = .{ .iso8601 = scrobble.ts } })).unixTimestamp() * 1_000_000, - }; - }, - Data.LastFMWebScrobble => { - break :blk Data.Scrobble{ - .album = if (scrobble.album) |album| album.@"#text" else "Not Provided", - .artists_album = &[_][]const u8{scrobble.artist.@"#text"}, - .track = scrobble.name, - .artists_track = &[_][]const u8{scrobble.artist.@"#text"}, - .date = try std.fmt.parseInt(i64, scrobble.date.?.uts, 10) * 1_000_000, - }; - }, - else => unreachable, + if (scrobble.date > latest_timestamp * 1_000 or scrobble.date < earliest_timestamp * 1_000) { + limited_tracks += 1; + continue :appends; + } + const filtered_scrobble = Data.Scrobble{ + .album = scrobble.album, + .artists_album = &[_][]const u8{scrobble.artist}, + .track = scrobble.track, + .artists_track = &[_][]const u8{scrobble.artist}, + .date = scrobble.date * 1_000, }; + const complete_scrobble = if (rule_list) |rl| try rules.applyScrobbleRule(request.allocator, filtered_scrobble, rl) else filtered_scrobble; + const row = try Utils.scrobbleToRow(request.allocator, complete_scrobble); + + try view_params.append(row); + try job_params.append(complete_scrobble); + } + }, + .LastFMWeb => |scrobbles| { + for (scrobbles) |scrobble| { + const filtered_scrobble = Data.Scrobble{ + .album = if (scrobble.album) |album| album.@"#text" else "Not Provided", + .artists_album = &[_][]const u8{scrobble.artist.@"#text"}, + .track = scrobble.name, + .artists_track = &[_][]const u8{scrobble.artist.@"#text"}, + .date = try std.fmt.parseInt(i64, scrobble.date.?.uts, 10) * 1_000_000, + }; + const complete_scrobble = if (rule_list) |rl| try rules.applyScrobbleRule(request.allocator, filtered_scrobble, rl) else filtered_scrobble; + + const row = try Utils.scrobbleToRow(request.allocator, complete_scrobble); + + try view_params.append(row); + try job_params.append(complete_scrobble); + } + }, + .Spotify => |scrobbles| { + appends: for (scrobbles) |scrobble| { + if (scrobble.ms_played < 30_000 and (scrobble.reason_end == null or !std.mem.eql(u8, scrobble.reason_end.?, "trackdone"))) { + skipped_tracks += 1; + continue :appends; + } + if (scrobble.master_metadata_album_artist_name == null or scrobble.master_metadata_track_name == null) { + skipped_tracks += 1; + continue :appends; + } + + const iso_ts = try zeit.Time.fromISO8601(scrobble.ts); + if ((iso_ts.after(latest_date) or iso_ts.before(earliest_date))) { + limited_tracks += 1; + continue :appends; + } + + const filtered_scrobble = Data.Scrobble{ + .album = scrobble.master_metadata_album_album_name.?, + .artists_album = &[_][]const u8{scrobble.master_metadata_album_artist_name.?}, + .track = scrobble.master_metadata_track_name.?, + .artists_track = &[_][]const u8{scrobble.master_metadata_album_artist_name.?}, + .date = (try zeit.instant(.{ .source = .{ .iso8601 = scrobble.ts } })).unixTimestamp() * 1_000_000, + }; const complete_scrobble = if (rule_list) |rl| try rules.applyScrobbleRule(request.allocator, filtered_scrobble, rl) else filtered_scrobble; const row = try Utils.scrobbleToRow(request.allocator, complete_scrobble); From 3ff973e193f331676c0470a6dfc4ff38330aff64 Mon Sep 17 00:00:00 2001 From: mitteneer Date: Mon, 26 May 2025 11:15:51 -0400 Subject: [PATCH 3/4] Use queries.zig in scrobbles view --- src/app/views/scrobbles.zig | 50 +++---------------------------------- src/queries.zig | 37 ++++++++++++++------------- 2 files changed, 23 insertions(+), 64 deletions(-) diff --git a/src/app/views/scrobbles.zig b/src/app/views/scrobbles.zig index 3fe946a..e61df4d 100644 --- a/src/app/views/scrobbles.zig +++ b/src/app/views/scrobbles.zig @@ -1,56 +1,12 @@ const std = @import("std"); const jetzig = @import("jetzig"); -const zeit = @import("zeit"); -const TableRow = @import("../../types.zig").TableRow; -const HyperlinkData = @import("../../types.zig").HyperlinkData; -const Utils = @import("../../date_fmt.zig"); +const queries = @import("../../queries.zig"); pub fn index(request: *jetzig.Request) !jetzig.View { var root = try request.data(.object); - var scrobbles_view = try root.put("scrobbles", .array); - const query = - \\SELECT songs.name, songs.id, albums.name, albums.id, artists.name, artists.id, scrobbles.id, scrobbles.datetime - \\FROM albumsongs - \\INNER JOIN songs ON songs.id = albumsongs.song_id - \\INNER JOIN albums ON albums.id = albumsongs.album_id - \\INNER JOIN scrobbles ON scrobbles.albumsong = albumsongs.id - \\INNER JOIN albumsongsartists ON albumsongsartists.albumsong_id = albumsongs.id - \\INNER JOIN artists ON artists.id = albumsongsartists.artist_id - \\ORDER BY scrobbles.datetime ASC - ; - - var scrobbles_jq_result = try request.repo.executeSql(query, .{}); - defer scrobbles_jq_result.deinit(); - - const Scrobble = struct { song_name: []const u8, song_id: i32, album_name: []const u8, album_id: i32, artist_name: []const u8, artist_id: i32, s_id: i32, date: i64 }; - - var prev_s_id: ?i32 = null; - - var row: ?TableRow = null; - var artistlist = std.ArrayList(HyperlinkData).init(request.allocator); - - blk: while (try scrobbles_jq_result.postgresql.result.next()) |scrobble_row| { - const scrobble = try scrobble_row.to(Scrobble, .{ .dupe = true, .allocator = request.allocator }); - if (scrobble.s_id == prev_s_id) { - try artistlist.append(.{ .name = scrobble.artist_name, .id = scrobble.artist_id }); - continue :blk; - } else { - const date = try Utils.dateFmt(request.allocator, scrobble.date); - - try artistlist.append(.{ .name = scrobble.artist_name, .id = scrobble.artist_id }); - - row = TableRow{ - .song = .{ .name = scrobble.song_name, .id = scrobble.song_id }, - .album = .{ .name = scrobble.album_name, .id = scrobble.album_id }, - .artistlist = try artistlist.toOwnedSlice(), - .date = date, - }; - - try scrobbles_view.append(row); - } - prev_s_id = scrobble.s_id; - } + const scrobbles = try queries.entityQueryResult(request, queries.generateQuery(.scrobble, .entities), .{}, .array); + try root.put("scrobbles", scrobbles); return request.render(.ok); } diff --git a/src/queries.zig b/src/queries.zig index cc5137b..faba14d 100644 --- a/src/queries.zig +++ b/src/queries.zig @@ -25,23 +25,19 @@ pub fn entityQueryResult(request: *jetzig.Request, query: GeneratedQuery, args: const item: ?TableRow = switch (query.ResultType) { EntitiesScrobbleResult, EntitiesSongResult, EntitiesAlbumResult, EntitiesArtistResult => switch (query.entity) { .artist => TableRow{ .artist = .{ .name = entity.name, .id = entity.id }, .scrobbles = entity.scrobbles }, - .album => album_entities: { + .scrobble, .song, .album => album_entities: { const last_artist = artist_list.getLastOrNull(); try artist_list.append(.{ .name = entity.artist_name, .id = entity.artist_id }); if (last_artist) |la| { if (la.id == entity.artist_id) continue :blk; } - break :album_entities TableRow{ .album = .{ .name = entity.name, .id = entity.id }, .artistlist = try artist_list.toOwnedSlice(), .scrobbles = entity.scrobbles }; + break :album_entities switch (query.entity) { + .scrobble => TableRow{ .song = .{ .name = entity.song_name, .id = entity.song_id }, .album = .{ .name = entity.album_name, .id = entity.album_id }, .artistlist = try artist_list.toOwnedSlice(), .date = entity.date }, + .song => TableRow{ .song = .{ .name = entity.name, .id = entity.id }, .artistlist = try artist_list.toOwnedSlice(), .scrobbles = entity.scrobbles }, + .album => TableRow{ .album = .{ .name = entity.name, .id = entity.id }, .artistlist = try artist_list.toOwnedSlice(), .scrobbles = entity.scrobbles }, + else => unreachable, + }; }, - .song => song_entities: { - const last_artist = artist_list.getLastOrNull(); - try artist_list.append(.{ .name = entity.artist_name, .id = entity.artist_id }); - if (last_artist) |la| { - if (la.id == entity.artist_id) continue :blk; - } - break :song_entities TableRow{ .song = .{ .name = entity.name, .id = entity.id }, .artistlist = try artist_list.toOwnedSlice(), .scrobbles = entity.scrobbles }; - }, - else => unreachable, }, EntityItemsResult => switch (query.entity) { .artist => TableRow{ .album = .{ .name = entity.name, .id = entity.id }, .scrobbles = entity.scrobbles }, @@ -92,7 +88,7 @@ const TimescaleResult = struct { year: []const u8, scrobbles: i64 }; const EntitiesArtistResult = struct { name: []const u8, id: i32, scrobbles: i64 }; const EntitiesAlbumResult = struct { name: []const u8, id: i32, artist_name: []const u8, artist_id: i32, scrobbles: i64 }; const EntitiesSongResult = struct { name: []const u8, id: i32, artist_name: []const u8, artist_id: i32, scrobbles: i64 }; -const EntitiesScrobbleResult = struct { song_name: []const u8, song_id: i32, album_name: []const u8, album_id: i32, artist_name: []const u8, artist_id: i32, s_id: i32, date: i64 }; +const EntitiesScrobbleResult = struct { song_name: []const u8, song_id: i32, album_name: []const u8, album_id: i32, artist_name: []const u8, artist_id: i32, date: []const u8 }; const EntityItemsResult = struct { name: []const u8, id: i32, scrobbles: i64 }; const AppearsResult = struct { name: []const u8, id: i32, scrobbles: i64 }; const EntityInfoResult = struct { name: []const u8, id: i32, scrobbles: i64, rank: []const u8 }; @@ -142,7 +138,7 @@ pub fn generateQuery(entity: EntityType, query_type: QueryTypeEnum) GeneratedQue , .album => - \\(SELECT songs.name AS name, songs.id AS id, TO_CHAR(scrobbles.datetime, 'YYYY-MM-DD HH24:MM:SS') AS date + \\(SELECT songs.name AS name, songs.id AS id, TO_CHAR(scrobbles.datetime, 'YYYY-MM-DD HH24:MI:SS') AS date \\FROM albumsongs \\INNER JOIN songs ON songs.id = albumsongs.song_id \\INNER JOIN scrobbles ON albumsongs.id = scrobbles.albumsong @@ -153,7 +149,7 @@ pub fn generateQuery(entity: EntityType, query_type: QueryTypeEnum) GeneratedQue \\ \\UNION ALL \\ - \\(SELECT songs.name AS name, songs.id AS id, TO_CHAR(scrobbles.datetime, 'YYYY-MM-DD HH24:MM:SS') AS date + \\(SELECT songs.name AS name, songs.id AS id, TO_CHAR(scrobbles.datetime, 'YYYY-MM-DD HH24:MI:SS') AS date \\FROM albumsongs \\INNER JOIN songs ON songs.id = albumsongs.song_id \\INNER JOIN scrobbles ON albumsongs.id = scrobbles.albumsong @@ -164,7 +160,7 @@ pub fn generateQuery(entity: EntityType, query_type: QueryTypeEnum) GeneratedQue , .artist => - \\(SELECT songs.name AS name, songs.id AS id, TO_CHAR(scrobbles.datetime, 'YYYY-MM-DD HH24:MM:SS') AS date + \\(SELECT songs.name AS name, songs.id AS id, TO_CHAR(scrobbles.datetime, 'YYYY-MM-DD HH24:MI:SS') AS date \\FROM albumsongs \\INNER JOIN songs ON songs.id = albumsongs.song_id \\INNER JOIN scrobbles ON albumsongs.id = scrobbles.albumsong @@ -175,7 +171,7 @@ pub fn generateQuery(entity: EntityType, query_type: QueryTypeEnum) GeneratedQue \\ \\UNION ALL \\ - \\(SELECT songs.name AS name, songs.id AS id, TO_CHAR(scrobbles.datetime, 'YYYY-MM-DD HH24:MM:SS') AS date + \\(SELECT songs.name AS name, songs.id AS id, TO_CHAR(scrobbles.datetime, 'YYYY-MM-DD HH24:MI:SS') AS date \\FROM albumsongs \\INNER JOIN songs ON songs.id = albumsongs.song_id \\INNER JOIN scrobbles ON albumsongs.id = scrobbles.albumsong @@ -227,7 +223,14 @@ pub fn generateQuery(entity: EntityType, query_type: QueryTypeEnum) GeneratedQue //.ResultType = EntitiesResult, switch (entity) { .scrobble => - \\none + \\SELECT songs.name AS song_name, songs.id AS song_id, albums.name AS album_name, albums.id AS album_id, artists.name AS artist_name, artists.id AS artist_id, TO_CHAR(scrobbles.datetime, 'YYYY-MM-DD HH24:MI:SS') AS date + \\FROM albumsongs + \\INNER JOIN songs ON songs.id = albumsongs.song_id + \\INNER JOIN albums ON albums.id = albumsongs.album_id + \\INNER JOIN scrobbles ON scrobbles.albumsong = albumsongs.id + \\INNER JOIN albumsongsartists ON albumsongsartists.albumsong_id = albumsongs.id + \\INNER JOIN artists ON artists.id = albumsongsartists.artist_id + \\ORDER BY scrobbles.datetime ASC , .song => \\SELECT songs.name, songs.id, artists.name, artists.id, COUNT(scrobbles) AS scrobbles From d638fa66c550e79ccd04101e093b06b5d56970a2 Mon Sep 17 00:00:00 2001 From: mitteneer Date: Thu, 29 May 2025 15:33:10 -0400 Subject: [PATCH 4/4] Create GET function for a song view --- src/app/views/songs.zig | 17 ++++++- src/app/views/songs/get.zmpl | 22 +++++++-- src/queries.zig | 88 +++++++++++++++++++++++++++++------- 3 files changed, 106 insertions(+), 21 deletions(-) diff --git a/src/app/views/songs.zig b/src/app/views/songs.zig index d5a7944..3143f01 100644 --- a/src/app/views/songs.zig +++ b/src/app/views/songs.zig @@ -12,6 +12,21 @@ pub fn index(request: *jetzig.Request) !jetzig.View { } pub fn get(id: []const u8, request: *jetzig.Request) !jetzig.View { - _ = id; + var root = try request.data(.object); + + const song = try queries.entityQueryResult(request, queries.generateQuery(.song, .entity_info), .{id}, .object); + try root.put("song", song.get("entity_info")); + + const scrobbles = try queries.entityQueryResult(request, queries.generateQuery(.song, .entity_items), .{id}, .array); + try root.put("scrobbles", scrobbles); + + const appears = try queries.entityQueryResult(request, queries.generateQuery(.song, .appears), .{id}, .array); + try root.put("appears", appears); + + const firstlast = try queries.entityQueryResult(request, queries.generateQuery(.song, .firstlast), .{id}, .array); + try root.put("firstlast", firstlast); + + const timescale = try queries.entityQueryResult(request, queries.generateQuery(.song, .timescale), .{id}, .array); + try root.put("yearly", timescale); return request.render(.ok); } diff --git a/src/app/views/songs/get.zmpl b/src/app/views/songs/get.zmpl index 76457d0..0a051a4 100644 --- a/src/app/views/songs/get.zmpl +++ b/src/app/views/songs/get.zmpl @@ -1,3 +1,19 @@ -
- Content goes here -
+@zig { + const ColumnChoices = []const enum{song, album, artist, artistlist, scrobbles, date}; + const columns: ColumnChoices = &.{.song, .artistlist, .album, .date}; +} + + + + + + +@partial partials/header +

{{.song}}

+@partial partials/firstlast_listens(scrobbles: .song.scrobbles, rank: .song.rank, firstlast: .firstlast) +

Yearly Performance

+@partial partials/timescale(range: .yearly) +

Scrobbles

+@partial partials/newtable(T: ColumnChoices, table_data: .scrobbles, columns: columns) + + \ No newline at end of file diff --git a/src/queries.zig b/src/queries.zig index faba14d..061b5b3 100644 --- a/src/queries.zig +++ b/src/queries.zig @@ -20,7 +20,7 @@ pub fn entityQueryResult(request: *jetzig.Request, query: GeneratedQuery, args: blk: while (try result.postgresql.result.next()) |entity_row| { switch (query.ResultType) { - FirstlastResult, TimescaleResult, EntitiesScrobbleResult, EntitiesSongResult, EntitiesAlbumResult, EntitiesArtistResult, EntityItemsResult, AppearsResult, EntityInfoResult => |T| { + FirstlastResult, TimescaleResult, EntitiesScrobbleResult, EntitiesSongResult, EntitiesAlbumResult, EntitiesArtistResult, EntityItemsResult, AppearsResult, EntityInfoResult, EntityItemsSongResult => |T| { const entity = try entity_row.to(T, .{ .dupe = true, .allocator = request.allocator }); const item: ?TableRow = switch (query.ResultType) { EntitiesScrobbleResult, EntitiesSongResult, EntitiesAlbumResult, EntitiesArtistResult => switch (query.entity) { @@ -39,7 +39,8 @@ pub fn entityQueryResult(request: *jetzig.Request, query: GeneratedQuery, args: }; }, }, - EntityItemsResult => switch (query.entity) { + EntityItemsResult, EntityItemsSongResult => switch (query.entity) { + .song => TableRow{ .song = .{ .name = entity.song_name, .id = entity.song_id }, .album = .{ .name = entity.album_name, .id = entity.album_id }, .date = entity.date }, .artist => TableRow{ .album = .{ .name = entity.name, .id = entity.id }, .scrobbles = entity.scrobbles }, .album => TableRow{ .song = .{ .name = entity.name, .id = entity.id }, .scrobbles = entity.scrobbles }, else => unreachable, @@ -92,6 +93,17 @@ const EntitiesScrobbleResult = struct { song_name: []const u8, song_id: i32, alb const EntityItemsResult = struct { name: []const u8, id: i32, scrobbles: i64 }; const AppearsResult = struct { name: []const u8, id: i32, scrobbles: i64 }; const EntityInfoResult = struct { name: []const u8, id: i32, scrobbles: i64, rank: []const u8 }; +const EntityItemsSongResult = struct { song_name: []const u8, song_id: i32, album_name: []const u8, album_id: i32, date: []const u8 }; +const UnifiedResult = struct { + album_name: ?[]const u8 = null, + album_id: ?i32 = null, + song_name: ?[]const u8 = null, + song_id: ?i32 = null, + artist_names: ?[]const []const u8 = null, + artist_ids: ?[]i32 = null, + scrobbles: ?i64 = null, + date: ?[]const u8 = null, +}; pub fn generateQuery(entity: EntityType, query_type: QueryTypeEnum) GeneratedQuery { return GeneratedQuery{ @@ -106,7 +118,11 @@ pub fn generateQuery(entity: EntityType, query_type: QueryTypeEnum) GeneratedQue .album => EntitiesAlbumResult, .artist => EntitiesArtistResult, }, - .entity_items => EntityItemsResult, + .entity_items => switch (entity) { + .scrobble => unreachable, + .song => EntityItemsSongResult, + else => EntityItemsResult, + }, .appears => AppearsResult, .entity_info => EntityInfoResult, }, @@ -116,23 +132,21 @@ pub fn generateQuery(entity: EntityType, query_type: QueryTypeEnum) GeneratedQue switch (entity) { .scrobble => unreachable, .song => - \\(SELECT songs.name AS name, songs.id AS id, scrobbles.datetime AS date + \\(SELECT songs.name AS name, songs.id AS id, TO_CHAR(scrobbles.datetime,'YYYY-MM-DD HH24:MI:SS') AS date \\FROM albumsongs \\INNER JOIN songs ON songs.id = albumsongs.song_id \\INNER JOIN scrobbles ON albumsongs.id = scrobbles.albumsong - \\INNER JOIN albumsongsartists ON albumsongsartists.albumsong_id = albumsongs.id - \\WHERE albumsongsartists.artist_id = $1 + \\WHERE albumsongs.song_id = $1 \\ORDER BY scrobbles.datetime ASC \\LIMIT 1) \\ \\UNION ALL \\ - \\(SELECT songs.name AS name, songs.id AS id, scrobbles.datetime AS date + \\(SELECT songs.name AS name, songs.id AS id, TO_CHAR(scrobbles.datetime, 'YYYY-MM-DD HH24:MI:SS') AS date \\FROM albumsongs \\INNER JOIN songs ON songs.id = albumsongs.song_id \\INNER JOIN scrobbles ON albumsongs.id = scrobbles.albumsong - \\INNER JOIN albumsongsartists ON albumsongsartists.albumsong_id = albumsongs.id - \\WHERE albumsongsartists.artist_id = $1 + \\WHERE albumsongs.song_id = $1 \\ORDER BY scrobbles.datetime DESC \\LIMIT 1) , @@ -190,9 +204,8 @@ pub fn generateQuery(entity: EntityType, query_type: QueryTypeEnum) GeneratedQue \\SELECT TO_CHAR(date_trunc('year', datetime), 'YYYY') AS year, COUNT(*) as scrobbles \\FROM scrobbles \\INNER JOIN albumsongs ON albumsongs.id = scrobbles.albumsong - \\INNER JOIN albumsongsartists ON albumsongsartists.albumsong_id = albumsongs.id - \\INNER JOIN artists ON artists.id = albumsongsartists.artist_id - \\WHERE artists.id = $1 + \\INNER JOIN songs ON songs.id = albumsongs.song_id + \\WHERE songs.id = $1 \\GROUP BY year \\ORDER BY year ASC; , @@ -268,13 +281,21 @@ pub fn generateQuery(entity: EntityType, query_type: QueryTypeEnum) GeneratedQue switch (entity) { .scrobble => unreachable, .song => - \\ NOTHING YET + \\SELECT albums.name AS album_name, albums.id AS album_id, COUNT(scrobbles) AS scrobbles + \\FROM artistalbums + \\INNER JOIN albums ON albums.id = artistalbums.album_id + \\INNER JOIN albumsongs ON albumsongs.album_id = albums.id + \\INNER JOIN scrobbles ON scrobbles.albumsong = albumsongs.id + \\INNER JOIN albumsongsartists ON albumsongsartists.albumsong_id = albumsongs.id + \\WHERE albumsongs.song_id = $1 + \\GROUP BY albums.id + \\ORDER BY scrobbles DESC; , .album => \\nope , .artist => - \\SELECT albums.name AS name, albums.id AS id, COUNT(scrobbles) AS scrobbles + \\SELECT albums.name AS album_name, albums.id AS album_id, COUNT(scrobbles) AS scrobbles \\FROM artistalbums \\INNER JOIN albums ON albums.id = artistalbums.album_id \\INNER JOIN albumsongs ON albumsongs.album_id = albums.id @@ -291,7 +312,13 @@ pub fn generateQuery(entity: EntityType, query_type: QueryTypeEnum) GeneratedQue switch (entity) { .scrobble => unreachable, .song => - \\get all scrobbles + \\SELECT songs.name, songs.id, albums.name, albums.id, TO_CHAR(scrobbles.datetime, 'YYYY-MM-DD HH24:MI:SS') AS date + \\FROM albumsongs + \\INNER JOIN albums ON albums.id = albumsongs.album_id + \\INNER JOIN songs ON songs.id = albumsongs.song_id + \\INNER JOIN scrobbles ON scrobbles.albumsong = albumsongs.id + \\WHERE songs.id = $1 + \\ORDER BY date ASC , .album => \\SELECT songs.name, songs.id, COUNT(scrobbles) AS scrobbles @@ -317,12 +344,19 @@ pub fn generateQuery(entity: EntityType, query_type: QueryTypeEnum) GeneratedQue .entity_info => switch (entity) { .scrobble => unreachable, .song => - \\lol idk + \\SELECT * FROM + \\(SELECT *, TO_CHAR(ROW_NUMBER() OVER (ORDER BY scrobbles DESC),'FM99999th') AS rank FROM + \\(SELECT songs.name AS name, songs.id AS id, COUNT(scrobbles) AS scrobbles + \\FROM albumsongs + \\INNER JOIN songs ON albumsongs.song_id = songs.id + \\INNER JOIN scrobbles ON scrobbles.albumsong = albumsongs.id + \\GROUP BY songs.id)) + \\WHERE id = $1 , .album => \\SELECT * FROM \\(SELECT *, TO_CHAR(ROW_NUMBER() OVER (ORDER BY scrobbles DESC),'FM9999th') AS rank FROM - \\(SELECT albums.name, albums.id, COUNT(scrobbles) AS scrobbles + \\(SELECT albums.name AS name, albums.id AS id, COUNT(scrobbles) AS scrobbles \\FROM albumsongs \\INNER JOIN albums ON albumsongs.album_id = albums.id \\INNER JOIN scrobbles ON scrobbles.albumsong = albumsongs.id @@ -341,6 +375,26 @@ pub fn generateQuery(entity: EntityType, query_type: QueryTypeEnum) GeneratedQue \\WHERE id = $1 , }, + + .datestreak => switch (entity) { + .song => + \\SELECT songs.name, albums.name, streak.maxseq, streak.ds, streak.de FROM (SELECT albumsong, MAX(numdays) AS maxseq, ds, de + \\FROM (SELECT albumsong, grp, MIN(datetime::date) AS ds, MAX(datetime::date) AS de, + \\COUNT(DISTINCT datetime::date) AS numdays + \\FROM (SELECT scrobbles.*, + \\((datetime::date - '1970-01-01'::date) - DENSE_RANK() OVER (PARTITION BY albumsong ORDER BY datetime::date)) AS grp + \\FROM scrobbles + \\) scrobbles + \\GROUP BY albumsong, grp + \\) scrobbles + \\GROUP BY albumsong, ds, de) AS streak + \\INNER JOIN albumsongs ON albumsongs.id = streak.albumsong + \\INNER JOIN albums ON albums.id = albumsongs.album_id + \\INNER JOIN songs ON songs.id = albumsongs.song_id + \\ORDER BY streak.maxseq DESC; + , + else => unreachable, + }, }, }; }