Add views and begin database

This commit is contained in:
mitteneer 2024-11-20 18:17:14 -05:00
parent 348545949a
commit a15db5fb31
68 changed files with 709 additions and 90 deletions

View file

@ -16,8 +16,8 @@
// internet connectivity.
.dependencies = .{
.jetzig = .{
.url = "https://github.com/jetzig-framework/jetzig/archive/dda433bb73000614482af10a277d47dc9d89600c.tar.gz",
.hash = "12202ce84b803a8b300c91d98afbc7c326298b55a23bf05cf603182e934b621008ec",
.url = "https://github.com/jetzig-framework/jetzig/archive/d887cd5bd88eef611fc145e8acb12eee05b9aeef.tar.gz",
.hash = "12204458ae2b5cb93339296d7ac0f90fc0a0292711fcd82156b6595131fc0a96c648",
},
},
.paths = .{

28
config/database.zig Normal file
View file

@ -0,0 +1,28 @@
pub const database = .{
.testing = .{
.adapter = .postgresql,
.hostname = "localhost",
.port = 5432,
.username = "postgres",
.password = "postgres",
.database = "zuletzt_testing",
},
.development = .{
.adapter = .postgresql,
.hostname = "localhost",
.port = 5432,
.username = "postgres",
.password = "postgres",
.database = "zuletzt_dev",
},
.production = .{
.adapter = .postgresql,
.hostname = "localhost",
.port = 5432,
.username = "postgres",
.password = "postgres",
.database = "zuletzt",
},
};

130
src/app/database/Schema.zig Normal file
View file

@ -0,0 +1,130 @@
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: f64,
play_count: i32,
score: f64,
avg_song_score: f64,
url: []const u8,
holiday: bool,
compilation: bool,
collaboration: bool,
created_at: jetquery.DateTime,
updated_at: jetquery.DateTime,
},
.{
.relations = .{
.scrobbles = jetquery.hasMany(.Scrobble, .{}),
},
},
);
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,
avg_album_score: f64,
avg_song_score: f64,
url: []const u8,
aliased: bool,
created_at: jetquery.DateTime,
updated_at: jetquery.DateTime,
},
.{
.relations = .{
.scrobbles = jetquery.hasMany(.Scrobble, .{}),
},
},
);
pub const Scrobble = jetquery.Model(
@This(),
"scrobbles",
struct {
id: i32,
date: jetquery.DateTime,
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,
name: []const u8,
play_count: i32,
length: f64,
score: f64,
url: []const u8,
aliased: bool,
track_num: i32,
hidden: bool,
holiday: bool,
created_at: jetquery.DateTime,
updated_at: jetquery.DateTime,
},
.{
.relations = .{
.scrobbles = jetquery.hasMany(.Scrobble, .{}),
},
},
);

Binary file not shown.

View file

@ -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(
"artists",
&.{
t.primaryKey("id", .{}),
t.column("name", .string, .{}),
t.column("album_num", .integer, .{}),
t.column("song_num", .integer, .{}),
t.column("play_count", .integer, .{}),
t.column("avg_album_score", .float, .{}),
t.column("avg_song_score", .float, .{}),
t.column("url", .string, .{}),
t.column("aliased", .boolean, .{}),
t.timestamps(.{}),
},
.{},
);
}
pub fn down(repo: anytype) !void {
try repo.dropTable("artists", .{});
}

View file

@ -0,0 +1,27 @@
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("name", .string, .{}),
t.column("play_count", .integer, .{}),
t.column("length", .float, .{}),
t.column("score", .float, .{}),
t.column("url", .string, .{}),
t.column("aliased", .boolean, .{}),
t.column("track_num", .integer, .{}),
t.column("hidden", .boolean, .{}),
t.column("holiday", .boolean, .{}),
t.timestamps(.{}),
},
.{},
);
}
pub fn down(repo: anytype) !void {
try repo.dropTable("songs", .{});
}

View file

@ -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", .{});
}

View file

@ -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", .{});
}

View file

@ -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", .{});
}

View file

@ -0,0 +1,19 @@
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.timestamps(.{}),
},
.{},
);
}
pub fn down(repo: anytype) !void {
try repo.dropTable("scrobbles", .{});
}

View file

@ -0,0 +1,28 @@
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("score", .float, .{}),
t.column("avg_song_score", .float, .{}),
t.column("url", .string, .{}),
t.column("holiday", .boolean, .{}),
t.column("compilation", .boolean, .{}),
t.column("collaboration", .boolean, .{}),
t.timestamps(.{}),
},
.{},
);
}
pub fn down(repo: anytype) !void {
try repo.dropTable("albums", .{});
}

View file

@ -1,22 +0,0 @@
pub const addArtist = \\INSERT INTO artists ('artist', 'plays', 'url') VALUES (?,?)
;
pub const addTrack = \\INSERT INTO tracks ('artist', 'track', 'album', 'plays', 'url') VALUES (?,?,?,?)
;
pub const getArtist = \\SELECT artist, plays FROM artists WHERE artist == ?
;
pub const getTrack = \\SELECT artist, track, album, plays FROM tracks WHERE track == ?
;
pub const getTrackSearch = \\SELECT track, url, form FROM tracks WHERE track LIKE '%' || ? || '%'
;
pub const getArtistSearch = \\SELECT artist, url, form FROM artists WHERE artist LIKE '%' || ? || '%'
;
pub const getAlbumSearch = \\SELECT album, url, form FROM albums WHERE album LIKE '%' || ? || '%'
;
pub const total_search = getArtistSearch ++ " UNION " ++ getAlbumSearch ++ " UNION " ++ getTrackSearch;

View file

@ -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);
}

View file

@ -0,0 +1,3 @@
<div>
<span>Content goes here</span>
</div>

View file

@ -0,0 +1,8 @@
<html>
<body>
@partial partials/header
<div>
<span>Content goes here</span>
</div>
</body>
</html>

View file

@ -0,0 +1,3 @@
<div>
<span>Content goes here</span>
</div>

View file

@ -0,0 +1,3 @@
<div>
<span>Content goes here</span>
</div>

View file

@ -0,0 +1,3 @@
<div>
<span>Content goes here</span>
</div>

View file

@ -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);
}

View file

@ -0,0 +1,3 @@
<div>
<span>Content goes here</span>
</div>

View file

@ -0,0 +1,3 @@
<div>
<span>Content goes here</span>
</div>

View file

@ -0,0 +1,8 @@
<html>
<body>
@partial partials/header
<div>
<span>Content goes here</span>
</div>
</body>
</html>

View file

@ -0,0 +1,3 @@
<div>
<span>Content goes here</span>
</div>

View file

@ -0,0 +1,3 @@
<div>
<span>Content goes here</span>
</div>

View file

@ -0,0 +1,3 @@
<div>
<span>Content goes here</span>
</div>

36
src/app/views/lists.zig Normal file
View file

@ -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);
}

View file

@ -0,0 +1,3 @@
<div>
<span>Content goes here</span>
</div>

View file

@ -0,0 +1,3 @@
<div>
<span>Content goes here</span>
</div>

View file

@ -0,0 +1,8 @@
<html>
<body>
@partial partials/header
<div>
<span>Content goes here</span>
</div>
</body>
</html>

View file

@ -0,0 +1,3 @@
<div>
<span>Content goes here</span>
</div>

View file

@ -0,0 +1,3 @@
<div>
<span>Content goes here</span>
</div>

View file

@ -0,0 +1,3 @@
<div>
<span>Content goes here</span>
</div>

View file

@ -1,41 +0,0 @@
const std = @import("std");
const jetzig = @import("jetzig");
pub const static_params = .{
.index = .{
//.{ .params = .{ .foo = "hi", .bar = "bye" } },
//.{ .params = .{ .foo = "hello", .bar = "goodbye" } },
},
//.get = .{
// .{ .id = "1", .params = .{ .foo = "hi", .bar = "bye" } },
// .{ .id = "2", .params = .{ .foo = "hello", .bar = "goodbye" } },
//},
};
pub fn index(request: *jetzig.StaticRequest, data: *jetzig.Data) !jetzig.View {
var root = try data.object();
const params = try request.params();
if (params.get("foo")) |foo| try root.put("foo", foo);
if (params.get("foo") == null) try root.put("foo", data.string("no foo"));
if (params.get("bar")) |bar| try root.put("bar", bar);
if (params.get("bar") == null) try root.put("bar", data.string("no bar"));
return request.render(.ok);
}
//pub fn get(id: []const u8, request: *jetzig.StaticRequest, data: *jetzig.Data) !jetzig.View {
// var root = try data.object();
//
// const params = try request.params();
//
// if (std.mem.eql(u8, id, "1")) {
// try root.put("id", data.string("id is '1'"));
// }
//
// if (params.get("foo")) |foo| try root.put("foo", foo);
// if (params.get("bar")) |bar| try root.put("bar", bar);
//
// return request.render(.created);
//}

View file

@ -1,2 +0,0 @@
<div>{{.foo}}</div>
<div>{{.bar}}</div>

View file

@ -0,0 +1,7 @@
<a class="header-link" href="/">Zuletzt</a>
<a class="header-link" href="/scrobbles">Scrobbles</a>
<a class="header-link" href="/concerts">Concerts</a>
<a class="header-link" href="/collection">Collection</a>
<a class="header-link" href="/ratings">Ratings</a>
<a class="header-link" href="/lists">Lists</a>
<hr>

View file

View file

36
src/app/views/ratings.zig Normal file
View file

@ -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);
}

View file

@ -0,0 +1,3 @@
<div>
<span>Content goes here</span>
</div>

View file

@ -0,0 +1,3 @@
<div>
<span>Content goes here</span>
</div>

View file

@ -0,0 +1,8 @@
<html>
<body>
@partial partials/header
<div>
<span>Content goes here</span>
</div>
</body>
</html>

View file

@ -0,0 +1,3 @@
<div>
<span>Content goes here</span>
</div>

View file

@ -0,0 +1,3 @@
<div>
<span>Content goes here</span>
</div>

View file

@ -0,0 +1,3 @@
<div>
<span>Content goes here</span>
</div>

View file

@ -1,6 +0,0 @@
<b class="title text-[#123456]">Zuletzt</b>
<a class="header-link" href="/artists">Artists</a>
<a class="header-link" href="/albums">Albums</a>
<a class="header-link" href="/tracks">Tracks</a>
<a class="header-link" href="/stats">Stats</a>
<hr>

View file

@ -4,11 +4,10 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="/styles.css" />
</head>
<body>
@partial root/header
@partial root/top
@partial partials/header
@partial partials/top
</body>
</html>

View file

@ -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);
}

View file

@ -0,0 +1,3 @@
<div>
<span>Content goes here</span>
</div>

View file

@ -0,0 +1,3 @@
<div>
<span>Content goes here</span>
</div>

View file

@ -0,0 +1,8 @@
<html>
<body>
@partial partials/header
<div>
<span>Content goes here</span>
</div>
</body>
</html>

View file

@ -0,0 +1,3 @@
<div>
<span>Content goes here</span>
</div>

View file

@ -0,0 +1,3 @@
<div>
<span>Content goes here</span>
</div>

View file

@ -0,0 +1,3 @@
<div>
<span>Content goes here</span>
</div>

View file

@ -1,7 +0,0 @@
const std = @import("std");
const jetzig = @import("jetzig");
pub fn index(request: *jetzig.Request, data: *jetzig.Data) !jetzig.View {
_ = data;
return request.render(.ok);
}

48
src/app/views/upload.zig Normal file
View file

@ -0,0 +1,48 @@
const std = @import("std");
const jetzig = @import("jetzig");
const jetquery = @import("jetzig").jetquery;
pub fn index(request: *jetzig.Request, data: *jetzig.Data) !jetzig.View {
_ = data;
try request.repo.insert(.Artist, .{
.id = 123,
.name = "wilco",
.album_num = 10,
.song_num = 200,
.play_count = 2700,
.avg_album_score = 10.0,
.avg_song_score = 10.0,
.url = "/wilco",
.aliased = false,
});
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);
}

View file

@ -0,0 +1,3 @@
<div>
<span>Content goes here</span>
</div>

View file

@ -0,0 +1,3 @@
<div>
<span>Content goes here</span>
</div>

View file

@ -0,0 +1,19 @@
<html>
<body>
@partial partials/header
<div>
<span>Upload Last.fm or Spotify history file here (in json format).</span>
</div>
<form action="/" enctype="multipart/form-data" method="POST">
<label>Filename</label>
<input type="text" name="description" />
<label>File</label>
<input type="file" name="upload" />
<input type="submit" value="Submit" />
<fieldset>
<input type="radio" name="t" label="Last.fm">Last.fm</input>
<input type="radio" name="t" label="Spotify">Spotify</input>
</fieldset>
</form>
</body>
</html>

View file

@ -0,0 +1,3 @@
<div>
<span>Content goes here</span>
</div>

View file

@ -0,0 +1,3 @@
<div>
<span>Content goes here</span>
</div>

View file

@ -0,0 +1,3 @@
<div>
<span>Content goes here</span>
</div>

View file

@ -5,9 +5,9 @@ const zmd = @import("zmd");
const builtin = @import("builtin");
pub const static = @import("static");
// Override default settings in `jetzig.config` here:
pub const jetzig_options = struct {
pub const Schema = @import("Schema");
/// Middleware chain. Add any custom middleware here, or use middleware provided in
/// `jetzig.middleware` (e.g. `jetzig.middleware.HtmxMiddleware`).
pub const middleware: []const type = &.{
@ -95,20 +95,20 @@ pub fn main() !void {
// },
// .threading_mode = .MultiThread,
//});
//const create =
//const create =
// \\CREATE TABLE artists ('artist', 'plays')
//;
//const query =
//const query =
// \\INSERT INTO artists ('artist', 'plays') VALUES (?,?)
//;
//var build = try db.prepare(create);
//defer build.deinit();
//try build.exec(.{},.{});
//var stmt = try db.prepare(query);
//defer stmt.deinit();
@ -117,7 +117,6 @@ pub fn main() !void {
// .plays = 2500,
//});
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer std.debug.assert(gpa.deinit() == .ok);
const allocator = gpa.allocator();