diff --git a/.gitignore b/.gitignore index 540d652..abb3b4e 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,6 @@ zig-out/ *.core static/ .jetzig +src/app/database/data.db-journal +src/app/database/old_migrations/ +src/lib/ diff --git a/README.md b/README.md index 31b4ffa..8a2e029 100644 --- a/README.md +++ b/README.md @@ -10,4 +10,47 @@ the functionality of the aforementioned inspirations. Zuletzt means "last" in German. -Licensed under MIT. \ No newline at end of file +Licensed under MIT. + +## To-Do List: +- [ ] Last.fm statistics +- [ ] Lastfmstats.com statistics[^1] +- [ ] Collections + - [ ] Import from Discogs[^2] +- [ ] Import listening history + - [ ] From Lastfmstats.com (.json file)[^3] + - [ ] From Last.fm (authentication) + - [ ] From Spotify (.json file) + - [ ] From other streaming services[^4] + - [ ] Import rules + - [ ] Simple find/replace + - [ ] User-defined regex +- [ ] Tags + - [ ] Genres +- [ ] MusicBrainz integration +- [ ] Concerts + - [ ] Import from Setlist.fm[^5] +- [ ] Ratings + - [ ] RYM integration[^6] + - [ ] Rank songs +- [ ] Custom statistics[^7] +- [ ] "Playlists"[^8] + +[^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). + +[^2]: I do not intend to provide the level of granularity that Discogs provides, but a simple toggle that means "I own some version of this release" is all that is necessary. + +[^3]: I have not investigated any other service for downloading your listening history from Last.fm, but providing the listening history as a JSON rather than a CSV is highly preferred. I may eventually provide my own way of downloading Last.fm data as a JSON, but I would prefer to allow users to enter their username, or authenticate, and avoid needing to upload a file altogether. + +[^4]: I only intend to allow imports from Last.fm and Spotify at the moment because those are the only data sources I currently rely on. To that extent, I imagine I could import from other sources as well fairly easily, although I do not know what their data dumps look like. + +[^5]: I only intend to allow imports from Setlist.fm at the moment because that is the only data source I currently rely on. + +[^6]: RYM has the most data, and once it has an API, will be the only user-driven review site that *has* an API. In this context, "integration" simply means displaying the critic score and user score next to the album. You will be able to write reviews and ranks songs/albums(/artists?), but not for them to be published to RYM. + +[^7]: I envision something akin to the Custom Reports from [Actual Budget](https://github.com/actualbudget/actual) that will allow users to create their own ways of rating/ranking songs/albums, and view their listening habits. + +[^8]: Misleading title, but same functionality as "Lists" on AlbumOfTheYear, although I would like to allow albums and songs to appear on the same list. + +## Contributing +I am a math student who is interested in programming. I will not be writing quality code. That said, Zuletzt is something that, at the moment, I am very excited about making, and using to relearn some things about programming. Unless contributions are given in the form of code review, or some kind of constructive criticism, it's not likely that I accept pull requests. The project is, however, licensed under the MIT License, so feel free to do what you like with it in your own way. diff --git a/build.zig b/build.zig index 36adc79..4ceba31 100644 --- a/build.zig +++ b/build.zig @@ -16,7 +16,7 @@ pub fn build(b: *std.Build) !void { // All dependencies **must** be added to imports above this line. - try jetzig.jetzigInit(b, exe, .{}); + try jetzig.jetzigInit(b, exe, .{ .zmpl_version = .v2 }); b.installArtifact(exe); diff --git a/build.zig.zon b/build.zig.zon index c4ddd34..a85ac4d 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -16,12 +16,8 @@ // internet connectivity. .dependencies = .{ .jetzig = .{ - .url = "https://github.com/jetzig-framework/jetzig/archive/dda433bb73000614482af10a277d47dc9d89600c.tar.gz", - .hash = "12202ce84b803a8b300c91d98afbc7c326298b55a23bf05cf603182e934b621008ec", - }, - .iguanas = .{ - .url = "https://github.com/jetzig-framework/iguanas/archive/89c2abf29de0bc31054a9a6feac5a6a83bab0459.tar.gz", - .hash = "12202fd319a5ab4e124b00e8ddea474d07c19c4e005d77b6c29fc44860904ea01a5c", + .url = "https://github.com/jetzig-framework/jetzig/archive/475ed269525624a67004594ddca44dc8ebea1919.tar.gz", + .hash = "1220bc060ba2320fa9fed8e554a8b692a93ef73fa3ab40617b9ed1d928d2029297fb", }, }, .paths = .{ diff --git a/config/database.zig b/config/database.zig new file mode 100644 index 0000000..361129d --- /dev/null +++ b/config/database.zig @@ -0,0 +1,31 @@ +pub const database = .{ + .testing = .{ + .adapter = .postgresql, + .hostname = "localhost", + .port = 5432, + .username = "postgres", + .password = "postgres", + .database = "zuletzt_testing", + .pool_size = 16, + }, + + .development = .{ + .adapter = .postgresql, + .hostname = "localhost", + .port = 5432, + .username = "postgres", + .password = "postgres", + .database = "zuletzt_dev", + .pool_size = 16, + }, + + .production = .{ + .adapter = .postgresql, + .hostname = "localhost", + .port = 5432, + .username = "postgres", + .password = "postgres", + .database = "zuletzt", + .pool_size = 16, + }, +}; diff --git a/public/styles.css b/public/styles.css index 1755d47..03416fd 100644 --- a/public/styles.css +++ b/public/styles.css @@ -3,8 +3,28 @@ * * */ + @import url('https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap'); + @import url('https://fonts.googleapis.com/css2?family=Noto+Sans:ital,wght@0,100..900;1,100..900&display=swap'); -.message { - font-weight: bold; +.title { + font-family: 'Roboto'; font-size: 3rem; + font-weight: 500; + margin-left: 20px; + margin-right: 40px; + margin-top: 20px; +} + +.header-link{ + font-family: 'Roboto'; + font-size: 1.5rem; + margin-right: 20px; +} + +.cell { + font-family: 'Noto Sans' +} + +#replaceMe{ + font-family:'Courier New'; } diff --git a/src/app/database/Schema.zig b/src/app/database/Schema.zig new file mode 100644 index 0000000..560ab3c --- /dev/null +++ b/src/app/database/Schema.zig @@ -0,0 +1,190 @@ +const jetquery = @import("jetzig").jetquery; + +pub const AlbumSong = jetquery.Model( + @This(), + "album_songs", + struct { + id: i32, + album_id: i32, + song_id: i32, + created_at: jetquery.DateTime, + updated_at: jetquery.DateTime, + }, + .{}, +); + +pub const Album = jetquery.Model( + @This(), + "albums", + struct { + id: i32, + title: []const u8, + song_num: i32, + length: f32, + play_count: i32, + holiday: bool, + compilation: bool, + deluxe: bool, + live: bool, + created_at: jetquery.DateTime, + updated_at: jetquery.DateTime, + }, + .{ + .relations = .{ + .scrobbles = jetquery.hasMany(.Scrobble, .{}), + .ratings = jetquery.hasMany(.Ratings, .{}), + .aliases = jetquery.hasMany(.Aliases, .{}), + .songs = jetquery.hasMany(.AlbumSongs, .{}), + .artists = jetquery.hasMany(.ArtistAlbums, .{}), + }, + }, +); + +pub const ArtistAlbum = jetquery.Model( + @This(), + "artist_albums", + struct { + id: i32, + artist_id: i32, + album_id: i32, + created_at: jetquery.DateTime, + updated_at: jetquery.DateTime, + }, + .{}, +); + +pub const ArtistSong = jetquery.Model( + @This(), + "artist_songs", + struct { + id: i32, + artist_id: i32, + song_id: i32, + created_at: jetquery.DateTime, + updated_at: jetquery.DateTime, + }, + .{}, +); + +pub const Artist = jetquery.Model( + @This(), + "artists", + struct { + id: i32, + name: []const u8, + album_num: i32, + song_num: i32, + play_count: i32, + created_at: jetquery.DateTime, + updated_at: jetquery.DateTime, + }, + .{ + .relations = .{ + .scrobbles = jetquery.hasMany(.Scrobble, .{}), + .aliases = jetquery.hasMany(.Aliases, .{}), + .concerts = jetquery.hasMany(.Concerts, .{}), + .songs = jetquery.hasMany(.ArtistSongs, .{}), + .albums = jetquery.hasMany(.ArtistAlbums, .{}), + }, + }, +); + +pub const Scrobble = jetquery.Model( + @This(), + "scrobbles", + struct { + id: i32, + date: jetquery.DateTime, + song_id: i32, + album_id: ?i32, + artist_id: i32, + created_at: jetquery.DateTime, + updated_at: jetquery.DateTime, + }, + .{ + .relations = .{ + .song = jetquery.belongsTo(.Song, .{}), + .album = jetquery.belongsTo(.Album, .{}), + .artist = jetquery.belongsTo(.Artist, .{}), + }, + }, +); + +pub const Song = jetquery.Model( + @This(), + "songs", + struct { + id: i32, + title: []const u8, + length: f32, + hidden: bool, + holiday: bool, + play_count: i32, + created_at: jetquery.DateTime, + updated_at: jetquery.DateTime, + }, + .{ + .relations = .{ + .scrobbles = jetquery.hasMany(.Scrobble, .{}), + .ratings = jetquery.hasMany(.Ratings, .{}), + .aliases = jetquery.hasMany(.Aliases, .{}), + .artists = jetquery.hasMany(.ArtistSongs, .{}), + .albums = jetquery.hasMany(.AlbumSongs, .{}), + }, + }, +); + +pub const Alias = jetquery.Model( + @This(), + "aliases", + struct { + id: i32, + reference_id: i32, + alias: []const u8, + created_at: jetquery.DateTime, + updated_at: jetquery.DateTime, + }, + .{}, +); + +pub const Concert = jetquery.Model( + @This(), + "concerts", + struct { + id: i32, + location: []const u8, + date: jetquery.DateTime, + created_at: jetquery.DateTime, + updated_at: jetquery.DateTime, + }, + .{}, +); + +pub const Rating = jetquery.Model( + @This(), + "ratings", + struct { + id: i32, + reference_id: i32, + score: f32, + date: jetquery.DateTime, + created_at: jetquery.DateTime, + updated_at: jetquery.DateTime, + }, + .{}, +); + +pub const RawScrobble = jetquery.Model( + @This(), + "raw_scrobbles", + struct { + id: i32, + track: []const u8, + artist: []const u8, + album: []const u8, + date: jetquery.DateTime, + created_at: jetquery.DateTime, + updated_at: jetquery.DateTime, + }, + .{}, +); diff --git a/src/app/database/migrations/2024-11-15_14-46-12_create_artists.zig b/src/app/database/migrations/2024-11-15_14-46-12_create_artists.zig new file mode 100644 index 0000000..db0ceae --- /dev/null +++ b/src/app/database/migrations/2024-11-15_14-46-12_create_artists.zig @@ -0,0 +1,22 @@ +const std = @import("std"); +const jetquery = @import("jetquery"); +const t = jetquery.schema.table; + +pub fn up(repo: anytype) !void { + try repo.createTable( + "artists", + &.{ + t.primaryKey("id", .{}), + t.column("name", .string, .{}), + t.column("album_num", .integer, .{}), + t.column("song_num", .integer, .{}), + t.column("play_count", .integer, .{}), + t.timestamps(.{}), + }, + .{}, + ); +} + +pub fn down(repo: anytype) !void { + try repo.dropTable("artists", .{}); +} diff --git a/src/app/database/migrations/2024-11-15_14-58-47_create_songs.zig b/src/app/database/migrations/2024-11-15_14-58-47_create_songs.zig new file mode 100644 index 0000000..d34bc30 --- /dev/null +++ b/src/app/database/migrations/2024-11-15_14-58-47_create_songs.zig @@ -0,0 +1,23 @@ +const std = @import("std"); +const jetquery = @import("jetquery"); +const t = jetquery.schema.table; + +pub fn up(repo: anytype) !void { + try repo.createTable( + "songs", + &.{ + t.primaryKey("id", .{}), + t.column("title", .string, .{}), + t.column("length", .float, .{}), + t.column("hidden", .boolean, .{}), + t.column("holiday", .boolean, .{}), + t.column("play_count", .integer, .{}), + t.timestamps(.{}), + }, + .{}, + ); +} + +pub fn down(repo: anytype) !void { + try repo.dropTable("songs", .{}); +} diff --git a/src/app/database/migrations/2024-11-15_17-52-04_create_artist_songs.zig b/src/app/database/migrations/2024-11-15_17-52-04_create_artist_songs.zig new file mode 100644 index 0000000..03f6ec4 --- /dev/null +++ b/src/app/database/migrations/2024-11-15_17-52-04_create_artist_songs.zig @@ -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( + "artist_songs", + &.{ + t.primaryKey("id", .{}), + t.column("artist_id", .integer, .{}), + t.column("song_id", .integer, .{}), + t.timestamps(.{}), + }, + .{}, + ); +} + +pub fn down(repo: anytype) !void { + try repo.dropTable("artist_songs", .{}); +} diff --git a/src/app/database/migrations/2024-11-15_17-52-28_create_artist_albums.zig b/src/app/database/migrations/2024-11-15_17-52-28_create_artist_albums.zig new file mode 100644 index 0000000..3e186ad --- /dev/null +++ b/src/app/database/migrations/2024-11-15_17-52-28_create_artist_albums.zig @@ -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( + "artist_albums", + &.{ + t.primaryKey("id", .{}), + t.column("artist_id", .integer, .{}), + t.column("album_id", .integer, .{}), + t.timestamps(.{}), + }, + .{}, + ); +} + +pub fn down(repo: anytype) !void { + try repo.dropTable("artist_albums", .{}); +} diff --git a/src/app/database/migrations/2024-11-15_17-52-54_create_album_songs.zig b/src/app/database/migrations/2024-11-15_17-52-54_create_album_songs.zig new file mode 100644 index 0000000..9462f82 --- /dev/null +++ b/src/app/database/migrations/2024-11-15_17-52-54_create_album_songs.zig @@ -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( + "album_songs", + &.{ + t.primaryKey("id", .{}), + t.column("album_id", .integer, .{}), + t.column("song_id", .integer, .{}), + t.timestamps(.{}), + }, + .{}, + ); +} + +pub fn down(repo: anytype) !void { + try repo.dropTable("album_songs", .{}); +} diff --git a/src/app/database/migrations/2024-11-20_19-08-02_create_albums.zig b/src/app/database/migrations/2024-11-20_19-08-02_create_albums.zig new file mode 100644 index 0000000..a18ca74 --- /dev/null +++ b/src/app/database/migrations/2024-11-20_19-08-02_create_albums.zig @@ -0,0 +1,26 @@ +const std = @import("std"); +const jetquery = @import("jetquery"); +const t = jetquery.schema.table; + +pub fn up(repo: anytype) !void { + try repo.createTable( + "albums", + &.{ + t.primaryKey("id", .{}), + t.column("title", .string, .{}), + t.column("song_num", .integer, .{}), + t.column("length", .float, .{}), + t.column("play_count", .integer, .{}), + t.column("holiday", .boolean, .{}), + t.column("compilation", .boolean, .{}), + t.column("deluxe", .boolean, .{}), + t.column("live", .boolean, .{}), + t.timestamps(.{}), + }, + .{}, + ); +} + +pub fn down(repo: anytype) !void { + try repo.dropTable("albums", .{}); +} diff --git a/src/app/database/migrations/2024-11-21_21-51-03_create_ratings.zig b/src/app/database/migrations/2024-11-21_21-51-03_create_ratings.zig new file mode 100644 index 0000000..d7ac939 --- /dev/null +++ b/src/app/database/migrations/2024-11-21_21-51-03_create_ratings.zig @@ -0,0 +1,21 @@ +const std = @import("std"); +const jetquery = @import("jetquery"); +const t = jetquery.schema.table; + +pub fn up(repo: anytype) !void { + try repo.createTable( + "ratings", + &.{ + t.primaryKey("id", .{}), + t.column("reference_id", .integer, .{}), + t.column("score", .float, .{}), + t.column("date", .datetime, .{}), + t.timestamps(.{}), + }, + .{}, + ); +} + +pub fn down(repo: anytype) !void { + try repo.dropTable("ratings", .{}); +} diff --git a/src/app/database/migrations/2024-11-21_21-51-38_create_aliases.zig b/src/app/database/migrations/2024-11-21_21-51-38_create_aliases.zig new file mode 100644 index 0000000..11cbb70 --- /dev/null +++ b/src/app/database/migrations/2024-11-21_21-51-38_create_aliases.zig @@ -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( + "aliases", + &.{ + t.primaryKey("id", .{}), + t.column("reference_id", .integer, .{}), + t.column("alias", .string, .{}), + t.timestamps(.{}), + }, + .{}, + ); +} + +pub fn down(repo: anytype) !void { + try repo.dropTable("aliases", .{}); +} diff --git a/src/app/database/migrations/2024-11-21_23-49-05_create_concerts.zig b/src/app/database/migrations/2024-11-21_23-49-05_create_concerts.zig new file mode 100644 index 0000000..8eb782e --- /dev/null +++ b/src/app/database/migrations/2024-11-21_23-49-05_create_concerts.zig @@ -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( + "concerts", + &.{ + t.primaryKey("id", .{}), + t.column("location", .string, .{}), + t.column("date", .datetime, .{}), + t.timestamps(.{}), + }, + .{}, + ); +} + +pub fn down(repo: anytype) !void { + try repo.dropTable("concerts", .{}); +} diff --git a/src/app/database/migrations/2024-11-25_15-32-40_create_raw_scrobbles.zig b/src/app/database/migrations/2024-11-25_15-32-40_create_raw_scrobbles.zig new file mode 100644 index 0000000..e4264d3 --- /dev/null +++ b/src/app/database/migrations/2024-11-25_15-32-40_create_raw_scrobbles.zig @@ -0,0 +1,22 @@ +const std = @import("std"); +const jetquery = @import("jetquery"); +const t = jetquery.schema.table; + +pub fn up(repo: anytype) !void { + try repo.createTable( + "raw_scrobbles", + &.{ + t.primaryKey("id", .{}), + t.column("track", .string, .{}), + t.column("artist", .string, .{}), + t.column("album", .string, .{}), + t.column("date", .datetime, .{}), + t.timestamps(.{}), + }, + .{}, + ); +} + +pub fn down(repo: anytype) !void { + try repo.dropTable("raw_scrobbles", .{}); +} diff --git a/src/app/database/migrations/2024-11-30_15-40-39_create_scrobbles.zig b/src/app/database/migrations/2024-11-30_15-40-39_create_scrobbles.zig new file mode 100644 index 0000000..ee4552f --- /dev/null +++ b/src/app/database/migrations/2024-11-30_15-40-39_create_scrobbles.zig @@ -0,0 +1,22 @@ +const std = @import("std"); +const jetquery = @import("jetquery"); +const t = jetquery.schema.table; + +pub fn up(repo: anytype) !void { + try repo.createTable( + "scrobbles", + &.{ + t.primaryKey("id", .{}), + t.column("date", .datetime, .{}), + t.column("song_id", .integer, .{}), + t.column("album_id", .integer, .{ .optional = true }), + t.column("artist_id", .integer, .{}), + t.timestamps(.{}), + }, + .{}, + ); +} + +pub fn down(repo: anytype) !void { + try repo.dropTable("scrobbles", .{}); +} diff --git a/src/app/jobs/process_scrobbles.zig b/src/app/jobs/process_scrobbles.zig new file mode 100644 index 0000000..e0d276a --- /dev/null +++ b/src/app/jobs/process_scrobbles.zig @@ -0,0 +1,86 @@ +const std = @import("std"); +const jetzig = @import("jetzig"); +const jetquery = @import("jetzig").jetquery; +//const time = @cImport({ +// @cInclude("time.h"); +//}); + +// The `run` function for a job is invoked every time the job is processed by a queue worker +// (or by the Jetzig server if the job is processed in-line). +// +// Arguments: +// * allocator: Arena allocator for use during the job execution process. +// * params: Params assigned to a job (from a request, values added to response data). +// * env: Provides the following fields: +// - logger: Logger attached to the same stream as the Jetzig server. +// - environment: Enum of `{ production, development }`. +pub fn run(allocator: std.mem.Allocator, params: *jetzig.data.Value, env: jetzig.jobs.JobEnv) !void { + _ = allocator; + _ = params; + + //const memory = try allocator.alloc(u8, 19); + + // Get all scrobbles from the RawScrobbles table + + const query = jetzig.database.Query(.RawScrobble).select(.{}); + const scrobbles = try env.repo.all(query); + defer env.repo.free(scrobbles); + + for (scrobbles) |scrobble| { + //const date = [19]u8{}; + //time.strftime{ date, 19, "%Y-%m-%d %H:%M:%D", scrobbles.date }; + //time.strftime(memory, 19, "%Y-%m-%d %H:%M:%S", scrobbles.date); + //std.debug.print("{s}", .{memory}); + + // Make hashes + const album_hash = std.hash.Fnv1a_64.hash(scrobble.album); + const artist_hash = std.hash.Fnv1a_64.hash(scrobble.artist); + const song_hash = std.hash.Fnv1a_64.hash(scrobble.track); + + var album_id: u64 = 0; + const song_id = (song_hash ^ artist_hash ^ album_hash) % 99999989; + if (artist_hash == album_hash) { + album_id = album_hash % 99999989; + } else { + album_id = (artist_hash ^ album_hash) % 99999989; + } + const artist_id = artist_hash % 99999989; + + // ID start - I think we can use SERIAL for this + // We don't compare intermediate IDs to anything, + // so keeping it a SERIAL is probably fine + const artistalbum_offset = try jetzig.database.Query(.ArtistAlbum).select(.{}).count().execute(env.repo) orelse unreachable; + const albumsong_offset = try jetzig.database.Query(.AlbumSong).select(.{}).count().execute(env.repo) orelse unreachable; + const artistsong_offset = try jetzig.database.Query(.ArtistSong).select(.{}).count().execute(env.repo) orelse unreachable; + + // Inserts + const artistalbum_insert = jetzig.database.Query(.ArtistAlbum).insert(.{ .id = 1 + artistalbum_offset, .artist_id = artist_id, .album_id = album_id }); + const albumsong_insert = jetzig.database.Query(.AlbumSong).insert(.{ .id = 1 + albumsong_offset, .song_id = song_id, .album_id = album_id }); + const artistsong_insert = jetzig.database.Query(.ArtistSong).insert(.{ .id = 1 + artistsong_offset, .artist_id = artist_id, .song_id = song_id }); + const album_insert = jetzig.database.Query(.Album).insert(.{ .id = album_id, .title = scrobble.album, .song_num = 0, .length = 0.0, .play_count = 0, .holiday = false, .compilation = false, .deluxe = false, .live = false }); + const artist_insert = jetzig.database.Query(.Artist).insert(.{ .id = artist_id, .name = scrobble.artist, .album_num = 0, .song_num = 0, .play_count = 0 }); + const song_insert = jetzig.database.Query(.Song).insert(.{ .id = song_id, .title = scrobble.track, .length = 0.0, .hidden = false, .holiday = false, .play_count = 0 }); + + // Checks + const artistalbum_check = try jetzig.database.Query(.ArtistAlbum).where(.{ .{ .artist_id = artist_id }, .AND, .{ .album_id = album_id } }).count().execute(env.repo); + const albumsong_check = try jetzig.database.Query(.AlbumSong).where(.{ .{ .album_id = album_id }, .AND, .{ .song_id = song_id } }).count().execute(env.repo); + const artistsong_check = try jetzig.database.Query(.ArtistSong).where(.{ .{ .artist_id = artist_id }, .AND, .{ .song_id = song_id } }).count().execute(env.repo); + const album_check = try jetzig.database.Query(.Album).where(.{.{ .id = album_id }}).count().execute(env.repo); + const artist_check = try jetzig.database.Query(.Artist).where(.{.{ .id = artist_id }}).count().execute(env.repo); + const song_check = try jetzig.database.Query(.Song).where(.{.{ .id = song_id }}).count().execute(env.repo); + + // Insert into Intermediate Tables + if (artistalbum_check == 0) try env.repo.execute(artistalbum_insert); + if (albumsong_check == 0) try env.repo.execute(albumsong_insert); + if (artistsong_check == 0) try env.repo.execute(artistsong_insert); + + if (album_check == 0) try env.repo.execute(album_insert); + if (artist_check == 0) try env.repo.execute(artist_insert); + if (song_check == 0) try env.repo.execute(song_insert); + + const scrobble_offset = try jetzig.database.Query(.Scrobble).select(.{}).count().execute(env.repo) orelse unreachable; + try jetzig.database.Query(.Scrobble).insert(.{ .id = scrobble_offset + 1, .song_id = song_id, .album_id = album_id, .artist_id = artist_id, .date = scrobble.date }).execute(env.repo); + } + // Clear RawScrobbles when done processing + try jetzig.database.Query(.RawScrobble).deleteAll().execute(env.repo); +} diff --git a/src/app/views/collection.zig b/src/app/views/collection.zig new file mode 100644 index 0000000..8125efd --- /dev/null +++ b/src/app/views/collection.zig @@ -0,0 +1,36 @@ +const std = @import("std"); +const jetzig = @import("jetzig"); + +pub fn index(request: *jetzig.Request, data: *jetzig.Data) !jetzig.View { + _ = data; + return request.render(.ok); +} + +pub fn get(id: []const u8, request: *jetzig.Request, data: *jetzig.Data) !jetzig.View { + _ = data; + _ = id; + return request.render(.ok); +} + +pub fn post(request: *jetzig.Request, data: *jetzig.Data) !jetzig.View { + _ = data; + return request.render(.created); +} + +pub fn put(id: []const u8, request: *jetzig.Request, data: *jetzig.Data) !jetzig.View { + _ = data; + _ = id; + return request.render(.ok); +} + +pub fn patch(id: []const u8, request: *jetzig.Request, data: *jetzig.Data) !jetzig.View { + _ = data; + _ = id; + return request.render(.ok); +} + +pub fn delete(id: []const u8, request: *jetzig.Request, data: *jetzig.Data) !jetzig.View { + _ = data; + _ = id; + return request.render(.ok); +} diff --git a/src/app/views/collection/delete.zmpl b/src/app/views/collection/delete.zmpl new file mode 100644 index 0000000..76457d0 --- /dev/null +++ b/src/app/views/collection/delete.zmpl @@ -0,0 +1,3 @@ +
| {{text}} | +} +|||
|---|---|---|---|
| {{value.track}} | +{{value.artist}} | +{{value.album}} | +{{value.date}} | +

-
-
-