music: self-hosted scrobbler (Spotify poller, Last.fm + streaming-history importers, /trackers/music, homepage listening card)
All checks were successful
Deploy production / deploy (push) Successful in 2m37s

- music schema: artists/albums/tracks (+album art), plays with source + dedupe, now_playing,
  spotify_auth, import_state; plays_deduped view hides Last.fm scrobbles Spotify also has
- in-app Spotify poller (60s, leased) started from hooks init; no new role or env needed
- /api/spotify/connect + callback (owner only), /trackers/music public stats
- scripts: music-poll, music-import-lastfm (resumable), music-import-history
- deploy/music-collector: optional host timer with its own role
This commit is contained in:
2026-10-24 15:56:50 +00:00
parent 45c124a365
commit b757269151
37 changed files with 22124 additions and 6 deletions

View File

@@ -0,0 +1,29 @@
import { SPOTIFY_CLIENT_ID } from '$app/env/private';
import { error, redirect } from '@sveltejs/kit';
import { requireOwner } from '#lib/server/owner.js';
import {
authorizeUrl,
CALLBACK_PATH,
publicOrigin,
STATE_COOKIE
} from '#lib/server/music/oauth.js';
import type { RequestHandler } from './$types';
// Owner only: start the Spotify Authorization Code flow for the listening tracker.
export const GET: RequestHandler = (event) => {
requireOwner(event);
const { url, cookies } = event;
// The state cookie must be set on the host Spotify redirects back to.
if (url.hostname === 'www.stanrunge.dev')
redirect(302, 'https://stanrunge.dev/api/spotify/connect');
if (!SPOTIFY_CLIENT_ID) error(503, 'Spotify is not configured on this server.');
const state = Buffer.from(crypto.getRandomValues(new Uint8Array(32))).toString('base64url');
cookies.set(STATE_COOKIE, state, {
path: '/api/spotify',
httpOnly: true,
secure: true,
sameSite: 'lax',
maxAge: 600
});
redirect(302, authorizeUrl(SPOTIFY_CLIENT_ID, `${publicOrigin(url)}${CALLBACK_PATH}`, state));
};