Compare commits
5 commits
3ef17fcd46
...
a8a4ed27c4
| Author | SHA1 | Date | |
|---|---|---|---|
| a8a4ed27c4 | |||
| 1e4a271b8d | |||
| 85552f39c1 | |||
| 162341fb5f | |||
| c8f2ef57c8 |
15 changed files with 184 additions and 106 deletions
31
README.md
31
README.md
|
|
@ -3,7 +3,6 @@
|
||||||
|
|
||||||
Inspired by [Last.fm](https://last.fm), [Maloja](https://github.com/krateng/maloja), and [Lastfmstats.com](https://www.lastfmstats.com).
|
Inspired by [Last.fm](https://last.fm), [Maloja](https://github.com/krateng/maloja), and [Lastfmstats.com](https://www.lastfmstats.com).
|
||||||
|
|
||||||
|
|
||||||
**Z**uletzt is written with [**Z**ig](https://github.com/ziglang/zig) and [Jetzig](https://github.com/jetzig-framework/jetzig) as a means of learning the
|
**Z**uletzt is written with [**Z**ig](https://github.com/ziglang/zig) and [Jetzig](https://github.com/jetzig-framework/jetzig) as a means of learning the
|
||||||
language, reintroducing myself to programming, and combining
|
language, reintroducing myself to programming, and combining
|
||||||
the functionality of the aforementioned inspirations.
|
the functionality of the aforementioned inspirations.
|
||||||
|
|
@ -12,6 +11,35 @@ Zuletzt means "last" in German.
|
||||||
|
|
||||||
Licensed under MIT.
|
Licensed under MIT.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
Zuletzt allows uploads of Scrobbles at the `/upload` page, where you
|
||||||
|
can import Scrobbles from a Spotify data export, Last.FM data export (a `.json`
|
||||||
|
file from lastfmstats.com), or by providing a Last.FM username and connecting
|
||||||
|
to Last.FM directly.
|
||||||
|
|
||||||
|
Zuletzt will not make any assumptions about the data, and only change metadata when asked to by a rule. Two albums will be considered the same if:
|
||||||
|
- They share the same title (case/diacritic sensitive)
|
||||||
|
- The album artist(s) are the same
|
||||||
|
|
||||||
|
Zuletzt allows you to list multiple artists under an album using rules, but
|
||||||
|
does not try to automatically split artists along common delimiters. For
|
||||||
|
example, there's no way to know that "Mermaid Avenue" by "Billy Bragg, Wilco"
|
||||||
|
is performed by two artists, while "Ants From Up There" by "Black Country, New
|
||||||
|
Road" is performed by one artist. Thus, a rule needs to be made to tell Zuletzt
|
||||||
|
"Mermaid Avenue" is performed by "Billy Bragg" and "Wilco".
|
||||||
|
|
||||||
|
Two songs will only be considered the same if:
|
||||||
|
- They share the same title (case/diacritic sensitive)
|
||||||
|
- They appear on the same album
|
||||||
|
|
||||||
|
If two or more songs with the same spelling appear on an album, they are
|
||||||
|
necessarily grouped under the same name, as there is no way to differentiate
|
||||||
|
them (see "Once In Royal David's City" on Sufjan Stevens's "Songs For
|
||||||
|
Christmas", for example). Every artist that performs on those songs with
|
||||||
|
receive attribution for the combined song.
|
||||||
|
|
||||||
|
If two artists have the same name, they are necessarily listed as the same artist, but can be separated with a rule, or after the fact, with a disambiguation string.
|
||||||
|
|
||||||
## To-Do List:
|
## To-Do List:
|
||||||
- [ ] Entity statistics
|
- [ ] Entity statistics
|
||||||
- [x] See all artists under "/artists"
|
- [x] See all artists under "/artists"
|
||||||
|
|
@ -60,6 +88,7 @@ Licensed under MIT.
|
||||||
- [ ] Rank songs
|
- [ ] Rank songs
|
||||||
- [ ] Custom statistics[^7]
|
- [ ] Custom statistics[^7]
|
||||||
- [ ] "Playlists"[^8]
|
- [ ] "Playlists"[^8]
|
||||||
|
- [ ] First launch setup
|
||||||
|
|
||||||
[^1]: I do not intend to exactly replicate all the statistics Lastfmstats.com provides, but I would at least like to give the user the option to see those kinds of statistics, or generate them themselves (see 7).
|
[^1]: I do not intend to exactly replicate all the statistics Lastfmstats.com provides, but I would at least like to give the user the option to see those kinds of statistics, or generate them themselves (see 7).
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ pub const Album = jetquery.Model(
|
||||||
@This(),
|
@This(),
|
||||||
"albums",
|
"albums",
|
||||||
struct {
|
struct {
|
||||||
id: i32,
|
id: i64,
|
||||||
name: []const u8,
|
name: []const u8,
|
||||||
length: ?f32,
|
length: ?f32,
|
||||||
created_at: jetquery.DateTime,
|
created_at: jetquery.DateTime,
|
||||||
|
|
@ -22,9 +22,9 @@ pub const Albumsong = jetquery.Model(
|
||||||
@This(),
|
@This(),
|
||||||
"albumsongs",
|
"albumsongs",
|
||||||
struct {
|
struct {
|
||||||
id: i32,
|
id: i64,
|
||||||
song_id: i32,
|
song_id: i64,
|
||||||
album_id: i32,
|
album_id: i64,
|
||||||
created_at: jetquery.DateTime,
|
created_at: jetquery.DateTime,
|
||||||
updated_at: jetquery.DateTime,
|
updated_at: jetquery.DateTime,
|
||||||
},
|
},
|
||||||
|
|
@ -32,9 +32,7 @@ pub const Albumsong = jetquery.Model(
|
||||||
.relations = .{
|
.relations = .{
|
||||||
.song = jetquery.belongsTo(.Song, .{}),
|
.song = jetquery.belongsTo(.Song, .{}),
|
||||||
.album = jetquery.belongsTo(.Album, .{}),
|
.album = jetquery.belongsTo(.Album, .{}),
|
||||||
.scrobbles = jetquery.hasMany(.Scrobble, .{
|
.scrobbles = jetquery.hasMany(.Scrobble, .{ .foreign_key = "albumsong" }),
|
||||||
.foreign_key = "albumsong",
|
|
||||||
}),
|
|
||||||
.albumsongsartists = jetquery.hasMany(.Albumsongsartist, .{}),
|
.albumsongsartists = jetquery.hasMany(.Albumsongsartist, .{}),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -44,9 +42,9 @@ pub const Albumsongsartist = jetquery.Model(
|
||||||
@This(),
|
@This(),
|
||||||
"albumsongsartists",
|
"albumsongsartists",
|
||||||
struct {
|
struct {
|
||||||
id: i32,
|
id: i64,
|
||||||
albumsong_id: i32,
|
albumsong_id: i64,
|
||||||
artist_id: i32,
|
artist_id: i64,
|
||||||
created_at: jetquery.DateTime,
|
created_at: jetquery.DateTime,
|
||||||
updated_at: jetquery.DateTime,
|
updated_at: jetquery.DateTime,
|
||||||
},
|
},
|
||||||
|
|
@ -62,9 +60,9 @@ pub const Artistalbum = jetquery.Model(
|
||||||
@This(),
|
@This(),
|
||||||
"artistalbums",
|
"artistalbums",
|
||||||
struct {
|
struct {
|
||||||
id: i32,
|
id: i64,
|
||||||
album_id: i32,
|
album_id: i64,
|
||||||
artist_id: i32,
|
artist_id: i64,
|
||||||
created_at: jetquery.DateTime,
|
created_at: jetquery.DateTime,
|
||||||
updated_at: jetquery.DateTime,
|
updated_at: jetquery.DateTime,
|
||||||
},
|
},
|
||||||
|
|
@ -80,7 +78,7 @@ pub const Artist = jetquery.Model(
|
||||||
@This(),
|
@This(),
|
||||||
"artists",
|
"artists",
|
||||||
struct {
|
struct {
|
||||||
id: i32,
|
id: i64,
|
||||||
name: []const u8,
|
name: []const u8,
|
||||||
disambiguation: ?[]const u8,
|
disambiguation: ?[]const u8,
|
||||||
created_at: jetquery.DateTime,
|
created_at: jetquery.DateTime,
|
||||||
|
|
@ -90,6 +88,7 @@ pub const Artist = jetquery.Model(
|
||||||
.relations = .{
|
.relations = .{
|
||||||
.albumsongsartists = jetquery.hasMany(.Albumsongsartist, .{}),
|
.albumsongsartists = jetquery.hasMany(.Albumsongsartist, .{}),
|
||||||
.artistalbums = jetquery.hasMany(.Artistalbum, .{}),
|
.artistalbums = jetquery.hasMany(.Artistalbum, .{}),
|
||||||
|
.artistsongs = jetquery.hasMany(.Artistsong, .{}),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
@ -98,17 +97,15 @@ pub const Scrobble = jetquery.Model(
|
||||||
@This(),
|
@This(),
|
||||||
"scrobbles",
|
"scrobbles",
|
||||||
struct {
|
struct {
|
||||||
id: i32,
|
id: i64,
|
||||||
albumsong: i32,
|
albumsong: i64,
|
||||||
datetime: jetquery.DateTime,
|
datetime: jetquery.DateTime,
|
||||||
created_at: jetquery.DateTime,
|
created_at: jetquery.DateTime,
|
||||||
updated_at: jetquery.DateTime,
|
updated_at: jetquery.DateTime,
|
||||||
},
|
},
|
||||||
.{
|
.{
|
||||||
.relations = .{
|
.relations = .{
|
||||||
.albumsong = jetquery.belongsTo(.Albumsong, .{
|
.albumsong = jetquery.belongsTo(.Albumsong, .{ .foreign_key = "albumsong" }),
|
||||||
.foreign_key = "albumsong",
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
@ -117,7 +114,7 @@ pub const Song = jetquery.Model(
|
||||||
@This(),
|
@This(),
|
||||||
"songs",
|
"songs",
|
||||||
struct {
|
struct {
|
||||||
id: i32,
|
id: i64,
|
||||||
name: []const u8,
|
name: []const u8,
|
||||||
length: ?f32,
|
length: ?f32,
|
||||||
hidden: bool,
|
hidden: bool,
|
||||||
|
|
@ -127,6 +124,25 @@ pub const Song = jetquery.Model(
|
||||||
.{
|
.{
|
||||||
.relations = .{
|
.relations = .{
|
||||||
.albumsongs = jetquery.hasMany(.Albumsong, .{}),
|
.albumsongs = jetquery.hasMany(.Albumsong, .{}),
|
||||||
|
.artistsongs = jetquery.hasMany(.Artistsong, .{}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
pub const Artistsong = jetquery.Model(
|
||||||
|
@This(),
|
||||||
|
"artistsongs",
|
||||||
|
struct {
|
||||||
|
id: i64,
|
||||||
|
artist_id: i64,
|
||||||
|
song_id: i64,
|
||||||
|
created_at: jetquery.DateTime,
|
||||||
|
updated_at: jetquery.DateTime,
|
||||||
|
},
|
||||||
|
.{
|
||||||
|
.relations = .{
|
||||||
|
.artist = jetquery.belongsTo(.Artist, .{}),
|
||||||
|
.song = jetquery.belongsTo(.Song, .{}),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ pub fn up(repo: anytype) !void {
|
||||||
try repo.createTable(
|
try repo.createTable(
|
||||||
"songs",
|
"songs",
|
||||||
&.{
|
&.{
|
||||||
t.primaryKey("id", .{}),
|
t.primaryKey("id", .{ .type = .bigint }),
|
||||||
t.column("name", .string, .{}),
|
t.column("name", .string, .{}),
|
||||||
t.column("length", .float, .{ .optional = true }),
|
t.column("length", .float, .{ .optional = true }),
|
||||||
t.column("hidden", .boolean, .{}),
|
t.column("hidden", .boolean, .{}),
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ pub fn up(repo: anytype) !void {
|
||||||
try repo.createTable(
|
try repo.createTable(
|
||||||
"albums",
|
"albums",
|
||||||
&.{
|
&.{
|
||||||
t.primaryKey("id", .{}),
|
t.primaryKey("id", .{ .type = .bigint }),
|
||||||
t.column("name", .string, .{}),
|
t.column("name", .string, .{}),
|
||||||
t.column("length", .float, .{ .optional = true }),
|
t.column("length", .float, .{ .optional = true }),
|
||||||
t.timestamps(.{}),
|
t.timestamps(.{}),
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,9 @@ pub fn up(repo: anytype) !void {
|
||||||
try repo.createTable(
|
try repo.createTable(
|
||||||
"albumsongs",
|
"albumsongs",
|
||||||
&.{
|
&.{
|
||||||
t.primaryKey("id", .{}),
|
t.primaryKey("id", .{ .type = .bigint }),
|
||||||
t.column("song_id", .integer, .{ .reference = .{ "songs", "id" } }),
|
t.column("song_id", .bigint, .{ .reference = .{ "songs", "id" } }),
|
||||||
t.column("album_id", .integer, .{ .reference = .{ "albums", "id" } }),
|
t.column("album_id", .bigint, .{ .reference = .{ "albums", "id" } }),
|
||||||
t.timestamps(.{}),
|
t.timestamps(.{}),
|
||||||
},
|
},
|
||||||
.{},
|
.{},
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,8 @@ pub fn up(repo: anytype) !void {
|
||||||
try repo.createTable(
|
try repo.createTable(
|
||||||
"scrobbles",
|
"scrobbles",
|
||||||
&.{
|
&.{
|
||||||
t.primaryKey("id", .{}),
|
t.primaryKey("id", .{ .type = .bigint }),
|
||||||
t.column("albumsong", .integer, .{ .reference = .{ "albumsongs", "id" } }),
|
t.column("albumsong", .bigint, .{ .reference = .{ "albumsongs", "id" } }),
|
||||||
t.column("datetime", .datetime, .{}),
|
t.column("datetime", .datetime, .{}),
|
||||||
t.timestamps(.{}),
|
t.timestamps(.{}),
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ pub fn up(repo: anytype) !void {
|
||||||
try repo.createTable(
|
try repo.createTable(
|
||||||
"artists",
|
"artists",
|
||||||
&.{
|
&.{
|
||||||
t.primaryKey("id", .{}),
|
t.primaryKey("id", .{ .type = .bigint }),
|
||||||
t.column("name", .string, .{}),
|
t.column("name", .string, .{}),
|
||||||
t.column("disambiguation", .string, .{ .optional = true }),
|
t.column("disambiguation", .string, .{ .optional = true }),
|
||||||
t.timestamps(.{}),
|
t.timestamps(.{}),
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,9 @@ pub fn up(repo: anytype) !void {
|
||||||
try repo.createTable(
|
try repo.createTable(
|
||||||
"albumsongsartists",
|
"albumsongsartists",
|
||||||
&.{
|
&.{
|
||||||
t.primaryKey("id", .{}),
|
t.primaryKey("id", .{ .type = .bigint }),
|
||||||
t.column("albumsong_id", .integer, .{ .reference = .{ "albumsongs", "id" } }),
|
t.column("albumsong_id", .bigint, .{ .reference = .{ "albumsongs", "id" } }),
|
||||||
t.column("artist_id", .integer, .{ .reference = .{ "artists", "id" } }),
|
t.column("artist_id", .bigint, .{ .reference = .{ "artists", "id" } }),
|
||||||
t.timestamps(.{}),
|
t.timestamps(.{}),
|
||||||
},
|
},
|
||||||
.{},
|
.{},
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,9 @@ pub fn up(repo: anytype) !void {
|
||||||
try repo.createTable(
|
try repo.createTable(
|
||||||
"artistalbums",
|
"artistalbums",
|
||||||
&.{
|
&.{
|
||||||
t.primaryKey("id", .{}),
|
t.primaryKey("id", .{ .type = .bigint }),
|
||||||
t.column("album_id", .integer, .{ .reference = .{ "albums", "id" } }),
|
t.column("album_id", .bigint, .{ .reference = .{ "albums", "id" } }),
|
||||||
t.column("artist_id", .integer, .{ .reference = .{ "artists", "id" } }),
|
t.column("artist_id", .bigint, .{ .reference = .{ "artists", "id" } }),
|
||||||
t.timestamps(.{}),
|
t.timestamps(.{}),
|
||||||
},
|
},
|
||||||
.{},
|
.{},
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const jetquery = @import("jetquery");
|
||||||
|
const t = jetquery.schema.table;
|
||||||
|
|
||||||
|
pub fn up(repo: anytype) !void {
|
||||||
|
try repo.createTable(
|
||||||
|
"artistsongs",
|
||||||
|
&.{
|
||||||
|
t.primaryKey("id", .{ .type = .bigint }),
|
||||||
|
t.column("artist_id", .bigint, .{ .reference = .{ "artists", "id" } }),
|
||||||
|
t.column("song_id", .bigint, .{ .reference = .{ "songs", "id" } }),
|
||||||
|
t.timestamps(.{}),
|
||||||
|
},
|
||||||
|
.{},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn down(repo: anytype) !void {
|
||||||
|
try repo.dropTable("artistsongs", .{});
|
||||||
|
}
|
||||||
|
|
@ -20,12 +20,13 @@ pub fn run(allocator: std.mem.Allocator, params: *jetzig.data.Value, env: jetzig
|
||||||
|
|
||||||
// Probably want to include artist name here, but not sure how to yet
|
// Probably want to include artist name here, but not sure how to yet
|
||||||
|
|
||||||
const track_artist_count = item.getT(.array, "artists_track").?.count();
|
const track_artists = item.getT(.array, "artists_track").?.items();
|
||||||
const album_artist_count = item.getT(.array, "artists_album").?.count();
|
const album_artists = item.getT(.array, "artists_album").?.items();
|
||||||
var track_artist_name_buffer = try allocator.alloc([]const u8, track_artist_count);
|
|
||||||
var album_artist_name_buffer = try allocator.alloc([]const u8, album_artist_count);
|
var track_artist_name_buffer = try allocator.alloc([]const u8, track_artists.len);
|
||||||
var track_artist_id_buffer = try allocator.alloc(i32, track_artist_count);
|
var album_artist_name_buffer = try allocator.alloc([]const u8, album_artists.len);
|
||||||
var album_artist_id_buffer = try allocator.alloc(i32, album_artist_count);
|
var track_artist_id_buffer = try allocator.alloc(i64, track_artists.len);
|
||||||
|
var album_artist_id_buffer = try allocator.alloc(i64, album_artists.len);
|
||||||
|
|
||||||
const scrobble: Data.Scrobble = .{
|
const scrobble: Data.Scrobble = .{
|
||||||
.track = item.getT(.string, "track").?,
|
.track = item.getT(.string, "track").?,
|
||||||
|
|
@ -35,95 +36,99 @@ pub fn run(allocator: std.mem.Allocator, params: *jetzig.data.Value, env: jetzig
|
||||||
.date = @as(i64, @truncate(item.getT(.integer, "date").?)),
|
.date = @as(i64, @truncate(item.getT(.integer, "date").?)),
|
||||||
};
|
};
|
||||||
|
|
||||||
var id_prehash = std.ArrayList(u8).init(allocator);
|
var album_hash_string = std.ArrayList(u8).init(allocator);
|
||||||
|
var track_hash_string = std.ArrayList(u8).init(allocator);
|
||||||
|
|
||||||
for (item.getT(.array, "artists_track").?.items(), 0..track_artist_count) |artist, i| {
|
// I theoretically don't need this for loop
|
||||||
|
for (track_artists, 0..track_artists.len) |artist, i| {
|
||||||
const artist_name = try artist.coerce([]const u8);
|
const artist_name = try artist.coerce([]const u8);
|
||||||
track_artist_name_buffer[i] = artist_name;
|
track_artist_name_buffer[i] = artist_name;
|
||||||
track_artist_id_buffer[i] = @as(i32, @bitCast(std.hash.Fnv1a_32.hash(artist_name)));
|
track_artist_id_buffer[i] = @as(i64, @bitCast(std.hash.Fnv1a_64.hash(artist_name)));
|
||||||
}
|
}
|
||||||
|
|
||||||
for (item.getT(.array, "artists_album").?.items(), 0..album_artist_count) |artist, i| {
|
for (album_artists, 0..album_artists.len) |artist, i| {
|
||||||
const artist_name = try artist.coerce([]const u8);
|
const artist_name = try artist.coerce([]const u8);
|
||||||
album_artist_name_buffer[i] = artist_name;
|
album_artist_name_buffer[i] = artist_name;
|
||||||
album_artist_id_buffer[i] = @as(i32, @bitCast(std.hash.Fnv1a_32.hash(artist_name)));
|
album_artist_id_buffer[i] = @as(i64, @bitCast(std.hash.Fnv1a_32.hash(artist_name)));
|
||||||
try id_prehash.appendSlice(artist_name);
|
try album_hash_string.appendSlice(artist_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
try id_prehash.appendSlice(scrobble.album);
|
try album_hash_string.appendSlice(scrobble.album);
|
||||||
const album_id = @as(i32, @bitCast(std.hash.Fnv1a_32.hash(id_prehash.items)));
|
try track_hash_string.appendSlice(scrobble.album);
|
||||||
try id_prehash.appendSlice(scrobble.track);
|
const album_hash = @as(i64, @bitCast(std.hash.Fnv1a_32.hash(album_hash_string.items)));
|
||||||
const song_id = @as(i32, @bitCast(std.hash.Fnv1a_32.hash(id_prehash.items)));
|
try track_hash_string.appendSlice(scrobble.track);
|
||||||
|
const track_hash = @as(i64, @bitCast(std.hash.Fnv1a_32.hash(track_hash_string.items)));
|
||||||
|
|
||||||
var albumsong = try jetzig.database.Query(.Albumsong)
|
var albumsong_id = try jetzig.database.Query(.Albumsong)
|
||||||
.findBy(.{ .album_id = album_id, .song_id = song_id })
|
.find(album_hash ^ track_hash)
|
||||||
.select(.{.id}).execute(env.repo);
|
.select(.{.id}).execute(env.repo);
|
||||||
|
|
||||||
var ins_album = try jetzig.database.Query(.Album)
|
var album_id = try jetzig.database.Query(.Album)
|
||||||
.find(album_id)
|
.find(album_hash)
|
||||||
.select(.{.id}).execute(env.repo);
|
.select(.{.id}).execute(env.repo);
|
||||||
|
|
||||||
for (track_artist_name_buffer, track_artist_id_buffer) |artist_name, artist_id| {
|
for (track_artist_name_buffer, track_artist_id_buffer) |scrobble_track_artist, track_artist_hash| {
|
||||||
var ins_artist = try jetzig.database.Query(.Artist)
|
var artist_id = try jetzig.database.Query(.Artist)
|
||||||
.find(artist_id)
|
.find(track_artist_hash)
|
||||||
.select(.{.id}).execute(env.repo);
|
.select(.{.id}).execute(env.repo);
|
||||||
|
|
||||||
if (ins_artist == null)
|
if (artist_id == null)
|
||||||
ins_artist = try jetzig.database.Query(.Artist)
|
artist_id = try jetzig.database.Query(.Artist)
|
||||||
.insert(.{ .id = artist_id, .name = artist_name, .disambiguation = null })
|
.insert(.{ .id = track_artist_hash, .name = scrobble_track_artist, .disambiguation = null })
|
||||||
.returning(.{.id}).execute(env.repo);
|
.returning(.{.id}).execute(env.repo);
|
||||||
|
|
||||||
if (albumsong == null) {
|
if (albumsong_id == null) {
|
||||||
var ins_song = try jetzig.database.Query(.Song)
|
var track_id = try jetzig.database.Query(.Song)
|
||||||
.find(song_id)
|
.find(track_hash)
|
||||||
.select(.{.id}).execute(env.repo);
|
.select(.{.id}).execute(env.repo);
|
||||||
|
|
||||||
if (ins_song == null)
|
if (track_id == null)
|
||||||
ins_song = try jetzig.database.Query(.Song)
|
track_id = try jetzig.database.Query(.Song)
|
||||||
.insert(.{ .id = song_id, .name = scrobble.track, .length = null, .hidden = false })
|
.insert(.{ .id = track_hash, .name = scrobble.track, .length = null, .hidden = false })
|
||||||
.returning(.{.id}).execute(env.repo);
|
.returning(.{.id}).execute(env.repo);
|
||||||
|
|
||||||
if (ins_album == null)
|
if (album_id == null)
|
||||||
ins_album = try jetzig.database.Query(.Album)
|
album_id = try jetzig.database.Query(.Album)
|
||||||
.insert(.{ .id = album_id, .name = scrobble.album, .length = null })
|
.insert(.{ .id = album_hash, .name = scrobble.album, .length = null })
|
||||||
.returning(.{.id}).execute(env.repo);
|
.returning(.{.id}).execute(env.repo);
|
||||||
|
|
||||||
albumsong = try jetzig.database.Query(.Albumsong)
|
albumsong_id = try jetzig.database.Query(.Albumsong)
|
||||||
.insert(.{ .song_id = ins_song.?.id, .album_id = ins_album.?.id })
|
.insert(.{ .song_id = track_id.?.id, .album_id = album_id.?.id })
|
||||||
.returning(.{.id}).execute(env.repo);
|
.returning(.{.id}).execute(env.repo);
|
||||||
|
|
||||||
try jetzig.database.Query(.Albumsongsartist)
|
try jetzig.database.Query(.Albumsongsartist)
|
||||||
.insert(.{ .albumsong_id = albumsong.?.id, .artist_id = ins_artist.?.id }).execute(env.repo);
|
.insert(.{ .albumsong_id = albumsong_id.?.id, .artist_id = artist_id.?.id }).execute(env.repo);
|
||||||
} else {
|
} else {
|
||||||
const ins_albumsongartist = try jetzig.database.Query(.Albumsongsartist)
|
const ins_albumsongartist = try jetzig.database.Query(.Albumsongsartist)
|
||||||
.findBy(.{ .albumsong_id = albumsong.?.id, .artist_id = ins_artist.?.id })
|
.findBy(.{ .albumsong_id = albumsong_id.?.id, .artist_id = artist_id.?.id })
|
||||||
.select(.{.id}).execute(env.repo);
|
.select(.{.id}).execute(env.repo);
|
||||||
|
|
||||||
if (ins_albumsongartist == null)
|
if (ins_albumsongartist == null)
|
||||||
try jetzig.database.Query(.Albumsongsartist)
|
try jetzig.database.Query(.Albumsongsartist)
|
||||||
.insert(.{ .albumsong_id = albumsong.?.id, .artist_id = ins_artist.?.id }).execute(env.repo);
|
.insert(.{ .albumsong_id = albumsong_id.?.id, .artist_id = artist_id.?.id }).execute(env.repo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (album_artist_name_buffer, album_artist_id_buffer) |artist_name, artist_id| {
|
|
||||||
const ins_artistalbum = try jetzig.database.Query(.Artistalbum)
|
for (album_artist_name_buffer, album_artist_id_buffer) |scrobble_album_artist, album_artist_hash| {
|
||||||
.findBy(.{ .album_id = ins_album.?.id, .artist_id = artist_id })
|
const artistalbum_id = try jetzig.database.Query(.Artistalbum)
|
||||||
|
.findBy(.{ .album_id = album_id.?.id, .artist_id = album_artist_hash })
|
||||||
.select(.{.id}).execute(env.repo);
|
.select(.{.id}).execute(env.repo);
|
||||||
|
|
||||||
if (ins_artistalbum == null) {
|
if (artistalbum_id == null) {
|
||||||
var ins_artist = try jetzig.database.Query(.Artist)
|
var artist_id = try jetzig.database.Query(.Artist)
|
||||||
.find(artist_id)
|
.find(album_artist_hash)
|
||||||
.select(.{.id}).execute(env.repo);
|
.select(.{.id}).execute(env.repo);
|
||||||
if (ins_artist == null)
|
if (artist_id == null)
|
||||||
ins_artist = try jetzig.database.Query(.Artist)
|
artist_id = try jetzig.database.Query(.Artist)
|
||||||
.insert(.{ .id = artist_id, .name = artist_name, .disambiguation = null })
|
.insert(.{ .id = artist_id, .name = scrobble_album_artist, .disambiguation = null })
|
||||||
.returning(.{.id}).execute(env.repo);
|
.returning(.{.id}).execute(env.repo);
|
||||||
try jetzig.database.Query(.Artistalbum)
|
try jetzig.database.Query(.Artistalbum)
|
||||||
.insert(.{ .album_id = ins_album.?.id, .artist_id = ins_artist.?.id }).execute(env.repo);
|
.insert(.{ .album_id = album_id.?.id, .artist_id = artist_id.?.id }).execute(env.repo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try jetzig.database.Query(.Scrobble)
|
try jetzig.database.Query(.Scrobble)
|
||||||
.insert(.{ .albumsong = albumsong.?.id, .datetime = scrobble.date }).execute(env.repo);
|
.insert(.{ .albumsong = albumsong_id.?.id, .datetime = scrobble.date }).execute(env.repo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
@args T: type, table_data: *ZmplValue, columns: T
|
@args T: type, table_data: *ZmplValue, columns: T
|
||||||
|
|
||||||
|
<div>
|
||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
|
@ -71,3 +72,4 @@
|
||||||
}
|
}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
</div>
|
||||||
|
|
@ -9,12 +9,24 @@
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@partial partials/header
|
@partial partials/header
|
||||||
|
<div style="text-align:center">
|
||||||
<h1>{{.song.song_name}}</h1>
|
<h1>{{.song.song_name}}</h1>
|
||||||
<div>{{.song.scrobbles}} scrobbles ({{.song.rank}} place)</div>
|
</div>
|
||||||
@partial partials/firstlast_listens(firstlast: .firstlast)
|
|
||||||
<h3>Yearly Performance</h3>
|
<div style="display:flex;flex-direction:row;justify-content:space-evenly">
|
||||||
@partial partials/timescale(range: .yearly)
|
<div style="display:flex;flex-direction:column;align-self:left">
|
||||||
<h2>Scrobbles</h2>
|
<div>{{.song.scrobbles}} scrobbles ({{.song.rank}} place)</div>
|
||||||
@partial partials/newtable(T: ColumnChoices, table_data: .scrobbles, columns: columns)
|
@partial partials/firstlast_listens(firstlast: .firstlast)
|
||||||
|
<h3>Yearly Performance</h3>
|
||||||
|
@partial partials/timescale(range: .yearly)
|
||||||
|
<h2>Scrobbles</h2>
|
||||||
|
@partial partials/newtable(T: ColumnChoices, table_data: .scrobbles, columns: columns)
|
||||||
|
</div>
|
||||||
|
<div style="display:flex;flex-direction:column;align-self:right">
|
||||||
|
<h2>Rating</h2>
|
||||||
|
<input type="text">
|
||||||
|
<input type="button">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
@ -23,12 +23,6 @@ pub fn entityQueryResult(request: *jetzig.Request, query: GeneratedQuery, args:
|
||||||
return out.get("entity_info").?;
|
return out.get("entity_info").?;
|
||||||
}
|
}
|
||||||
|
|
||||||
//if (query.query_type == .datestreak) {
|
|
||||||
// var out: *jetzig.Data.Value = try Data.object();
|
|
||||||
// const entity = try (try result.next()).?.to(struct { name: []const u8, id: i32, scrobbles: i64, rank: []const u8 }, .{ .dupe = true, .allocator = request.allocator });
|
|
||||||
//
|
|
||||||
//}
|
|
||||||
|
|
||||||
var out: *jetzig.Data.Value = try Data.array();
|
var out: *jetzig.Data.Value = try Data.array();
|
||||||
var mapper = result.mapper(UnifiedResult, .{ .dupe = true, .allocator = request.allocator });
|
var mapper = result.mapper(UnifiedResult, .{ .dupe = true, .allocator = request.allocator });
|
||||||
|
|
||||||
|
|
@ -65,22 +59,22 @@ const GeneratedQuery = struct {
|
||||||
|
|
||||||
const UnifiedResult = struct {
|
const UnifiedResult = struct {
|
||||||
album_name: ?[]const u8 = null,
|
album_name: ?[]const u8 = null,
|
||||||
album_id: ?i32 = null,
|
album_id: ?i64 = null,
|
||||||
song_name: ?[]const u8 = null,
|
song_name: ?[]const u8 = null,
|
||||||
song_id: ?i32 = null,
|
song_id: ?i64 = null,
|
||||||
artist_name: ?[]const u8 = null,
|
artist_name: ?[]const u8 = null,
|
||||||
artist_id: ?i32 = null,
|
artist_id: ?i64 = null,
|
||||||
scrobbles: ?i64 = null,
|
scrobbles: ?i64 = null,
|
||||||
date: ?[]const u8 = null,
|
date: ?[]const u8 = null,
|
||||||
};
|
};
|
||||||
|
|
||||||
const EntityInfoResult = struct {
|
const EntityInfoResult = struct {
|
||||||
album_name: ?[]const u8 = null,
|
album_name: ?[]const u8 = null,
|
||||||
album_id: ?i32 = null,
|
album_id: ?i64 = null,
|
||||||
song_name: ?[]const u8 = null,
|
song_name: ?[]const u8 = null,
|
||||||
song_id: ?i32 = null,
|
song_id: ?i64 = null,
|
||||||
artist_name: ?[]const u8 = null,
|
artist_name: ?[]const u8 = null,
|
||||||
artist_id: ?i32 = null,
|
artist_id: ?i64 = null,
|
||||||
scrobbles: ?i64 = null,
|
scrobbles: ?i64 = null,
|
||||||
date: ?[]const u8 = null,
|
date: ?[]const u8 = null,
|
||||||
rank: []const u8,
|
rank: []const u8,
|
||||||
|
|
|
||||||
|
|
@ -133,5 +133,5 @@ pub const TableRow = struct {
|
||||||
|
|
||||||
pub const HyperlinkData = struct {
|
pub const HyperlinkData = struct {
|
||||||
name: []const u8,
|
name: []const u8,
|
||||||
id: i32,
|
id: i64,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue