Initial commit

This commit is contained in:
Cadence Ember 2025-03-31 17:51:14 +13:00
parent 5a8186a46c
commit 15ff7e5b47
25 changed files with 2703 additions and 0 deletions

94
routes/app.js Normal file
View file

@ -0,0 +1,94 @@
// @ts-check
const {z} = require("zod")
const {defineEventHandler, getQuery, getValidatedQuery, sendRedirect, createError} = require("h3")
const {router, db, sync, select, from} = require("../passthrough")
const pugSync = sync.require("../pug-sync")
const displayCurrency = "NZD"
const displayCurrencySymbol = "$"
const currencyExchange = new Map([
["USD", 1],
["JPY", 0.0067],
["NZD", 0.57],
["EUR", 1.08],
["GBP", 1.3],
["CAD", 0.7],
["NOK", 0.1]
])
const sqls = {
album_grid: "SELECT item.*, count(*) AS track_count, iif(sum(duration) > 3600, cast(total(duration)/3600 AS INTEGER) || 'h ' || cast(total(duration)/60%60 AS INTEGER) || 'm', cast(total(duration)/60 AS INTEGER) || 'm') AS total_duration FROM item INNER JOIN track USING (item_id) {WHERE} GROUP BY item_id {ORDER}",
album_list: "SELECT item.*, count(*) AS track_count, iif(sum(duration) > 3600, cast(total(duration)/3600 AS INTEGER) || 'h ' || cast(total(duration)/60%60 AS INTEGER) || 'm', cast(total(duration)/60 AS INTEGER) || 'm') AS total_duration FROM item INNER JOIN track USING (item_id) {WHERE} GROUP BY item_id ORDER BY band_url, band_name COLLATE NOCASE, item_title COLLATE NOCASE",
artist_grid: "SELECT band_name, count(DISTINCT item_id) AS album_count, group_concat(DISTINCT band_url) AS labels, count(*) AS track_count, iif(sum(duration) > 3600, cast(total(duration)/3600 AS INTEGER) || 'h ' || cast(total(duration)/60%60 AS INTEGER) || 'm', cast(total(duration)/60 AS INTEGER) || 'm') AS total_duration FROM item INNER JOIN track USING (item_id) {WHERE} GROUP BY band_name ORDER BY band_name COLLATE NOCASE",
artist_list: "SELECT band_name, count(DISTINCT item_id) AS album_count, band_url, count(*) AS track_count, iif(sum(duration) > 3600, cast(total(duration)/3600 AS INTEGER) || 'h ' || cast(total(duration)/60%60 AS INTEGER) || 'm', cast(total(duration)/60 AS INTEGER) || 'm') AS total_duration FROM item INNER JOIN track USING (item_id) {WHERE} GROUP BY band_name ORDER BY band_name COLLATE NOCASE",
label_grid: "SELECT iif(count(DISTINCT band_name) = 1, band_name, band_url) AS display_name, band_url, count(DISTINCT item_id) AS album_count, count(DISTINCT band_name) AS artist_count, count(*) AS track_count, iif(sum(duration) > 3600, cast(total(duration)/3600 AS INTEGER) || 'h ' || cast(total(duration)/60%60 AS INTEGER) || 'm', cast(total(duration)/60 AS INTEGER) || 'm') AS total_duration FROM item INNER JOIN track USING (item_id) {WHERE} GROUP BY band_url ORDER BY display_name COLLATE NOCASE",
label_list: "SELECT iif(count(DISTINCT band_name) = 1, band_name, band_url) AS display_name, band_url, count(DISTINCT item_id) AS album_count, count(DISTINCT band_name) AS artist_count, count(*) AS track_count, iif(sum(duration) > 3600, cast(total(duration)/3600 AS INTEGER) || 'h ' || cast(total(duration)/60%60 AS INTEGER) || 'm', cast(total(duration)/60 AS INTEGER) || 'm') AS total_duration FROM item INNER JOIN track USING (item_id) {WHERE} GROUP BY band_url ORDER BY display_name COLLATE NOCASE",
track_list: "SELECT * FROM track INNER JOIN item USING (item_id) {WHERE} ORDER BY band_url, item_title COLLATE NOCASE, track_number"
}
function loadPreviews(locals, field, number) {
const previews = db.prepare(`SELECT ${field}, item_url, item_art_url FROM (SELECT ${field}, item_url, item_art_url, row_number() OVER (PARTITION BY ${field} ORDER BY purchased DESC) AS row_number FROM item) WHERE row_number <= ?`).all(number)
// TODO: performance?
for (const item of locals.items) {
item.previews = []
for (const preview of previews) {
if (preview[field] === item[field]) {
item.previews.push(preview)
}
}
}
}
const schema = z.object({
arrange: z.enum(["album", "artist", "label", "tag", "track"]),
shape: z.enum(["grid", "list"]),
filter_field: z.enum(["band_name", "band_url", "item_id"]).optional(),
filter: z.string().optional()
})
router.get("/", defineEventHandler({
onBeforeResponse: pugSync.compressResponse,
handler: async event => {
try {
var {arrange, shape, filter, filter_field} = await getValidatedQuery(event, schema.parse)
} catch (e) {
return sendRedirect(event, "/?arrange=album&shape=grid", 302)
}
const query = getQuery(event)
if (arrange === "track") {
shape = "list"
query.shape = "list"
}
const mode = `${arrange}_${shape}`
const params = []
let sql = sqls[mode]
if (filter_field && filter) {
sql = sql.replace("{WHERE}", `WHERE ${filter_field} LIKE ?`)
sql = sql.replace("{ORDER}", "ORDER BY item_title COLLATE NOCASE")
params.push(`%${filter}%`)
} else {
sql = sql.replace("{WHERE}", "")
sql = sql.replace("{ORDER}", "ORDER BY purchased DESC")
}
const prepared = db.prepare(sql)
const locals = {
items: prepared.all(params),
albumCount: db.prepare("SELECT count(*) FROM item WHERE item_type = 'album'").pluck().get(),
singleCount: db.prepare("SELECT count(*) FROM item WHERE item_type = 'track'").pluck().get(),
trackCount: db.prepare("SELECT count(*) FROM track").pluck().get(),
purchaseValue: Math.round(select("item", ["currency", "price"]).all().map(c => {
return (currencyExchange.get(c.currency) || 0.5) * c.price / (currencyExchange.get(displayCurrency) || 1) / 10
}).reduce((a, c) => a + c, 0)) * 10,
displayCurrencySymbol,
displayCurrency,
query
}
if (mode === "artist_grid") {
loadPreviews(locals, "band_name", 4)
} else if (mode === "label_grid") {
loadPreviews(locals, "band_url", 6)
}
return pugSync.render(event, `${arrange}_${shape}.pug`, locals)
}
}))