Create rating interface on songs view

If no ratings are present, provide a textbox to make a rating. If a rating is present, show the rating. Eventually, there will be a button that allows an additional rating to be made, and the ability to delete ratings
This commit is contained in:
mitteneer 2025-06-23 16:50:13 -04:00
parent 996022fe5f
commit 6f6aaecb8f
6 changed files with 36 additions and 7 deletions

View file

@ -3,9 +3,11 @@ const jetzig = @import("jetzig");
pub fn post(request: *jetzig.Request) !jetzig.View { pub fn post(request: *jetzig.Request) !jetzig.View {
var root = try request.data(.object); var root = try request.data(.object);
const params = try request.params(); const params = try request.params();
const id = params.getT(.integer, "song_id"); const id = params.getT(.integer, "song_id").?;
const score = if (params.getT(.integer, "score")) |score| @as(i16, @truncate(score)) else null;
const review = params.getT(.string, "review"); const review = params.getT(.string, "review");
try root.put("song_id", id); try jetzig.database.Query(.Songrating).insert(.{ .song = id, .rating = score, .rating_text = review, .date = @divFloor(request.start_time, 1_000) }).execute(request.repo);
try root.put("score", score);
try root.put("review", review); try root.put("review", review);
return request.render(.created); return request.render(.created);

View file

@ -1,2 +1 @@
{{.song_id}} <b>{{.score}}</b>: {{.review}} (Today)
{{.review}}

View file

@ -59,5 +59,9 @@ pub fn get(id: []const u8, request: *jetzig.Request) !jetzig.View {
const timescale = try queries.entityQueryResult(request, queries.loadQuery(.song, .timescale), .{id_int}); const timescale = try queries.entityQueryResult(request, queries.loadQuery(.song, .timescale), .{id_int});
try root.put("yearly", timescale); try root.put("yearly", timescale);
const ratings = try queries.entityQueryResult(request, queries.loadQuery(.song, .get_ratings), .{id_int});
try root.put("reviews", ratings);
return request.render(.ok); return request.render(.ok);
} }

View file

@ -1,6 +1,7 @@
@zig { @zig {
const ColumnChoices = []const enum{song, album, artist, artistlist, scrobbles, date}; const ColumnChoices = []const enum{song, album, artist, artistlist, scrobbles, date};
const columns: ColumnChoices = &.{.song, .artistlist, .album, .date}; const columns: ColumnChoices = &.{.song, .artistlist, .album, .date};
const reviews = try zmpl.coerceArray(".reviews");
} }
<html> <html>
@ -25,13 +26,21 @@
</div> </div>
<div style="display:flex;flex-direction:column;align-self:right"> <div style="display:flex;flex-direction:column;align-self:right">
<h2>Rating</h2> <h2>Rating</h2>
<div id="review-container">
@zig {
if (reviews.len == 0) {
<form> <form>
<input type="number" name="score" id="score" style="width:50px;height:30px"> <input type="number" name="score" id="score" style="width:50px;height:30px">
<textarea name="review" id="review" style="width:350px;height:100px"></textarea> <textarea name="review" id="review" style="width:350px;height:100px"></textarea>
<button hx-post="/ratings/songs" hx-vals='{"song_id":"{{.song.song_id}}"}' hx-target="#review-container" style="width:50px;height:30px">Post</button> <button hx-post="/ratings/songs" hx-vals='{"song_id":"{{.song.song_id}}"}' hx-target="#review-container" style="width:50px;height:30px">Post</button>
</form> </form>
</div> } else {
<div id="review-container">No reviews</div> for (reviews) |review| {
<b>{{review.score}}</b>: {{review.review}} ({{review.date}})
}
}
}
</div>
</div> </div>
</body> </body>
</html> </html>

View file

@ -42,6 +42,8 @@ pub fn entityQueryResult(request: *jetzig.Request, query: GeneratedQuery, args:
.artistlist = if (artist_list.getLastOrNull()) |_| try artist_list.toOwnedSlice() else null, .artistlist = if (artist_list.getLastOrNull()) |_| try artist_list.toOwnedSlice() else null,
.scrobbles = if (entity.scrobbles) |scrobbles| scrobbles else null, .scrobbles = if (entity.scrobbles) |scrobbles| scrobbles else null,
.date = if (entity.date) |date| date else null, .date = if (entity.date) |date| date else null,
.score = if (entity.score) |score| score else null,
.review = if (entity.review) |review| review else null,
}); });
} }
@ -49,7 +51,7 @@ pub fn entityQueryResult(request: *jetzig.Request, query: GeneratedQuery, args:
} }
const EntityType = enum { scrobble, song, album, artist }; const EntityType = enum { scrobble, song, album, artist };
const QueryTypeEnum = enum { firstlast, timescale, entities, get_songs, get_albums, get_scrobbles, appears, entity_info, datestreak, entities_by_name }; const QueryTypeEnum = enum { firstlast, timescale, entities, get_songs, get_albums, get_scrobbles, appears, entity_info, datestreak, entities_by_name, get_ratings };
const GeneratedQuery = struct { const GeneratedQuery = struct {
entity: EntityType, entity: EntityType,
@ -66,6 +68,8 @@ const UnifiedResult = struct {
artist_id: ?i64 = null, artist_id: ?i64 = null,
scrobbles: ?i64 = null, scrobbles: ?i64 = null,
date: ?[]const u8 = null, date: ?[]const u8 = null,
score: ?i16 = null,
review: ?[]const u8 = null,
}; };
const EntityInfoResult = struct { const EntityInfoResult = struct {
@ -435,6 +439,15 @@ pub fn loadQuery(entity: EntityType, query_type: QueryTypeEnum) GeneratedQuery {
, ,
else => unreachable, else => unreachable,
}, },
.get_ratings => switch (entity) {
.song =>
\\SELECT rating AS score, rating_text AS review, TO_CHAR(date, 'YYYY-MM-DD') AS date
\\FROM songratings
\\WHERE song = $1
\\ORDER BY date DESC;
,
else => unreachable,
},
}, },
}; };
} }

View file

@ -129,6 +129,8 @@ pub const TableRow = struct {
artistlist: ?[]HyperlinkData = null, artistlist: ?[]HyperlinkData = null,
scrobbles: ?i64 = null, scrobbles: ?i64 = null,
date: ?[]const u8 = null, date: ?[]const u8 = null,
score: ?i16 = null,
review: ?[]const u8 = null,
}; };
pub const HyperlinkData = struct { pub const HyperlinkData = struct {