diff --git a/src/app/views/artists.zig b/src/app/views/artists.zig index 9f707f0..9dacc08 100644 --- a/src/app/views/artists.zig +++ b/src/app/views/artists.zig @@ -1,6 +1,7 @@ const std = @import("std"); const jetzig = @import("jetzig"); const jetquery = @import("jetzig").jetquery; +const dateFmt = @import("../../date_fmt.zig").dateFmt; pub fn index(request: *jetzig.Request) !jetzig.View { var root = try request.data(.object); @@ -34,12 +35,34 @@ pub fn index(request: *jetzig.Request) !jetzig.View { pub fn get(id: []const u8, request: *jetzig.Request) !jetzig.View { var root = try request.data(.object); - var albums_view = try root.put("albums", .array); - var appears = try root.put("appears", .array); - const artist_name = try jetzig.database.Query(.Artist).find(id).select(.{ .id, .name }).execute(request.repo); - _ = try root.put("artist", artist_name.?.name); - const query = - \\SELECT albums.name, albums.id, COUNT(scrobbles) AS scrobbles + + const ArtistResult = struct { name: []const u8, id: i32, scrobbles: i64, rank: i64 }; + const AlbumsResult = struct { name: []const u8, id: i32, scrobbles: i64 }; + const ScrobbleResult = struct { name: []const u8, id: i32, date: i64 }; + + const artist_info_query = + \\SELECT * FROM + \\(SELECT *, ROW_NUMBER() OVER (ORDER BY scrobbles DESC) AS rank FROM + \\(SELECT artists.name AS name, artists.id AS id, COUNT(scrobbles) AS scrobbles + \\FROM albumsongsartists + \\INNER JOIN artists ON albumsongsartists.artist_id = artists.id + \\INNER JOIN albumsongs ON albumsongsartists.albumsong_id = albumsongs.id + \\INNER JOIN scrobbles ON scrobbles.albumsong = albumsongs.id + \\GROUP BY artists.id)) + \\WHERE id = $1 + ; + + var artist_jq_result = try request.repo.executeSql(artist_info_query, .{id}); + defer artist_jq_result.deinit(); + + if (try artist_jq_result.postgresql.result.next()) |artist_row| { + const artist = try artist_row.to(ArtistResult, .{ .dupe = true, .allocator = request.allocator }); + try root.put("artist", artist); + } + try artist_jq_result.drain(); + + const albums_query = + \\SELECT albums.name AS name, albums.id AS 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 @@ -49,8 +72,19 @@ pub fn get(id: []const u8, request: *jetzig.Request) !jetzig.View { \\ORDER BY scrobbles DESC ; + var albums_view = try root.put("albums", .array); + + var albums_jq_result = try request.repo.executeSql(albums_query, .{id}); + defer albums_jq_result.deinit(); + + while (try albums_jq_result.postgresql.result.next()) |album_row| { + const album = try album_row.to(AlbumsResult, .{ .dupe = true, .allocator = request.allocator }); + try albums_view.append(album); + } + //albums_jq_result.drain(); + const appears_query = - \\SELECT albums.name, albums.id, COUNT(scrobbles) AS scrobbles + \\SELECT albums.name AS name, albums.id AS 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 @@ -61,30 +95,60 @@ pub fn get(id: []const u8, request: *jetzig.Request) !jetzig.View { \\ORDER BY scrobbles DESC; ; - var albums_jq_result = try request.repo.executeSql(query, .{id}); - defer albums_jq_result.deinit(); - - const Album = struct { name: []const u8, id: i32, scrobbles: i64 }; - - while (try albums_jq_result.postgresql.result.next()) |album_row| { - const album = try album_row.to(Album, .{ .dupe = true, .allocator = request.allocator }); - var album_view = try albums_view.append(.object); - try album_view.put("name", album.name); - try album_view.put("url", album.id); - try album_view.put("scrobbles", album.scrobbles); - } + var appears_view = try root.put("appears", .array); var appears_jq_result = try request.repo.executeSql(appears_query, .{id}); defer appears_jq_result.deinit(); - while (try appears_jq_result.postgresql.result.next()) |album_row| { - const album = try album_row.to(Album, .{ .dupe = true, .allocator = request.allocator }); - var album_view = try appears.append(.object); - try album_view.put("name", album.name); - try album_view.put("url", album.id); - try album_view.put("scrobbles", album.scrobbles); + while (try appears_jq_result.postgresql.result.next()) |appears_row| { + const appears = try appears_row.to(AlbumsResult, .{ .dupe = true, .allocator = request.allocator }); + try appears_view.append(appears); } + //appears_jq_result.drain(); + + const first_last_songs_query = + \\(SELECT songs.name AS name, songs.id AS id, scrobbles.datetime 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 + \\ORDER BY scrobbles.datetime DESC + \\LIMIT 1) + \\ + \\UNION ALL + \\ + \\(SELECT songs.name AS name, songs.id AS id, scrobbles.datetime 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 + \\ORDER BY scrobbles.datetime ASC + \\LIMIT 1) + ; + + var first_last_songs_jq_result = try request.repo.executeSql(first_last_songs_query, .{id}); + defer first_last_songs_jq_result.deinit(); + + // These look backwards, but it's correct this way + if (try first_last_songs_jq_result.postgresql.result.next()) |last_song_row| { + const last_song = try last_song_row.to(ScrobbleResult, .{ .dupe = true, .allocator = request.allocator }); + try root.put("last_song_name", last_song.name); + try root.put("last_song_id", last_song.id); + try root.put("last_song_date", (try dateFmt(request.allocator, last_song.date))); + } + + if (try first_last_songs_jq_result.postgresql.result.next()) |first_song_row| { + const first_song = try first_song_row.to(ScrobbleResult, .{ .dupe = true, .allocator = request.allocator }); + try root.put("first_song_name", first_song.name); + try root.put("first_song_id", first_song.id); + try root.put("first_song_date", (try dateFmt(request.allocator, first_song.date))); + } + + try first_last_songs_jq_result.drain(); + return request.render(.ok); } diff --git a/src/app/views/artists/get.zmpl b/src/app/views/artists/get.zmpl index 2cc50cd..c272f79 100644 --- a/src/app/views/artists/get.zmpl +++ b/src/app/views/artists/get.zmpl @@ -5,7 +5,12 @@ @partial partials/header -

{{.artist}}

+

{{.artist.name}}

+{{.artist.scrobbles}} scrobbles ({{.artist.rank}}th place) +
+First listen: {{.first_song_name}} ({{.first_song_date}}) +
+Most recent listen: {{.last_song_name}} ({{.last_song_date}})

Albums

@@ -16,7 +21,7 @@ @for (.albums) |album| { - + } @@ -32,7 +37,7 @@ @for (.appears) |album| { - + } diff --git a/src/app/views/scrobbles.zig b/src/app/views/scrobbles.zig index f9ab102..6ee80d8 100644 --- a/src/app/views/scrobbles.zig +++ b/src/app/views/scrobbles.zig @@ -5,31 +5,42 @@ const zeit = @import("zeit"); pub fn index(request: *jetzig.Request) !jetzig.View { var root = try request.data(.object); var scrobbles_view = try root.put("scrobbles", .array); - //const query = jetzig.database.Query(.Scrobble) - // .select(.{ .id, .date }) - // .include(.song, .{ .select = .{ .id, .name } }) - // .include(.album, .{ .select = .{ .id, .name } }) - // .include(.scrobbleartists, .{ .select = .{.artist_id} }) - // .orderBy(.{ .date = .desc }); - //const scrobbles = try request.repo.all(query); const query = - \\SELECT songs.name, songs.id, albums.name, albums.id, scrobbles.datetime + \\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_js_result = try request.repo.executeSql(query, .{}); - defer scrobbles_js_result.deinit(); + 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, date: i64 }; + 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 }; - while (try scrobbles_js_result.postgresql.result.next()) |scrobble_row| { + var prev_s_id: ?i32 = null; + var prev_artist_infos: ?*jetzig.zmpl.Data.Value = null; + + 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) { + var artist_info = try prev_artist_infos.?.append(.object); + try artist_info.put("name", scrobble.artist_name); + try artist_info.put("url", scrobble.artist_id); + continue :blk; + } + // Not appending the scrobble directly because we don't want the unix timestamp or scrobble id var scrobble_view = try scrobbles_view.append(.object); + + var artist_infos = try scrobble_view.put("artist_info", .array); + var artist_info = try artist_infos.append(.object); + try artist_info.put("name", scrobble.artist_name); + try artist_info.put("url", scrobble.artist_id); + try scrobble_view.put("song_name", scrobble.song_name); try scrobble_view.put("song_id", scrobble.song_id); try scrobble_view.put("album_name", scrobble.album_name); @@ -37,28 +48,11 @@ pub fn index(request: *jetzig.Request) !jetzig.View { var date = std.ArrayList(u8).init(request.allocator); try (try zeit.instant(.{ .source = .{ .unix_timestamp = @divFloor(scrobble.date, 1_000) } })).time().strftime(date.writer(), "%d %b %Y, %H:%M"); try scrobble_view.put("date", date.items); + + prev_artist_infos = artist_infos; + prev_s_id = scrobble.s_id; } - //for (scrobbles) |scrobble| { - // var scrobble_view = try scrobbles_view.append(.object); - - // var artist_infos = try scrobble_view.put("artist_info", .array); - // for (scrobble.scrobbleartists) |artist| { - // var artist_info = try artist_infos.append(.object); - // const artist_data = try jetzig.database.Query(.Artist) - // .find(artist.artist_id) - // .select(.{ .id, .name }) - // .execute(request.repo); - // try artist_info.put("name", artist_data.?.name); - // try artist_info.put("id", artist_data.?.id); - // } - - // try scrobble_view.put("song_name", scrobble.song.name); - // try scrobble_view.put("song_id", scrobble.song.id); - // try scrobble_view.put("album_name", scrobble.album.name); - // try scrobble_view.put("album_id", scrobble.album.id); - // try scrobble_view.put("date", scrobble.date); - //} return request.render(.ok); } diff --git a/src/app/views/scrobbles/index.zmpl b/src/app/views/scrobbles/index.zmpl index c8e49be..b298cb6 100644 --- a/src/app/views/scrobbles/index.zmpl +++ b/src/app/views/scrobbles/index.zmpl @@ -10,6 +10,7 @@ + @@ -18,6 +19,11 @@ @for (.scrobbles) |scrobble| { +
{{album.name}}{{album.name}} {{album.scrobbles}}
{{album.name}}{{album.name}} {{album.scrobbles}}
SongArtist(s) Album Date
{{scrobble.song_name}} + @for (scrobble.get("artist_info").?) |ai| { + {{ai.name}} + } + {{scrobble.album_name}} {{scrobble.date}}