From 351b333529abf92fbdbeb194d2225752939e84c6 Mon Sep 17 00:00:00 2001 From: XShine555 Date: Thu, 24 Sep 2026 13:59:21 +0200 Subject: [PATCH] Split the server API module into client, guards, form fields and action helpers --- .../src/lib/server/actions/authedAction.ts | 27 +++++++++ .../{followActions.ts => actions/follow.ts} | 2 +- .../playlist.ts} | 3 +- web-player/src/lib/server/api.ts | 56 +------------------ web-player/src/lib/server/forms/albumForm.ts | 2 +- web-player/src/lib/server/forms/fields.ts | 8 +++ .../src/lib/server/forms/playlistForm.ts | 2 +- .../src/lib/server/forms/trackUploadForm.ts | 2 +- web-player/src/lib/server/guards.ts | 21 +++++++ web-player/src/routes/(app)/+layout.server.ts | 3 +- web-player/src/routes/(app)/+page.server.ts | 5 +- .../src/routes/(app)/albums/+page.server.ts | 5 +- .../routes/(app)/albums/[id]/+page.server.ts | 13 ++--- .../src/routes/(app)/explore/+page.server.ts | 2 +- .../src/routes/(app)/library/+page.server.ts | 12 ++-- .../routes/(app)/mixes/[id]/+page.server.ts | 5 +- .../routes/(app)/playlists/+page.server.ts | 4 +- .../(app)/playlists/[id]/+page.server.ts | 12 ++-- .../src/routes/(app)/upload/+page.server.ts | 4 +- .../routes/(app)/user/[id]/+page.server.ts | 5 +- .../[id]/[list=followList]/+page.server.ts | 3 +- 21 files changed, 99 insertions(+), 97 deletions(-) create mode 100644 web-player/src/lib/server/actions/authedAction.ts rename web-player/src/lib/server/{followActions.ts => actions/follow.ts} (91%) rename web-player/src/lib/server/{playlistActions.ts => actions/playlist.ts} (92%) create mode 100644 web-player/src/lib/server/forms/fields.ts create mode 100644 web-player/src/lib/server/guards.ts diff --git a/web-player/src/lib/server/actions/authedAction.ts b/web-player/src/lib/server/actions/authedAction.ts new file mode 100644 index 0000000..2764813 --- /dev/null +++ b/web-player/src/lib/server/actions/authedAction.ts @@ -0,0 +1,27 @@ +import { fail, type RequestEvent } from '@sveltejs/kit'; +import { apiFor, type ApiClient } from '$lib/server/api'; + +export interface ActionContext { + api: ApiClient; + form: FormData; + params: Record; + locals: App.Locals; + url: URL; +} + +export function authedAction( + handler: (context: ActionContext) => R | Promise, + unauthorizedMessage = 'Inicia sesión.' +) { + return async (event: RequestEvent) => { + if (!event.locals.accessToken) return fail(401, { message: unauthorizedMessage }); + const form = await event.request.formData(); + return handler({ + api: apiFor(event), + form, + params: event.params as Record, + locals: event.locals, + url: event.url + }); + }; +} diff --git a/web-player/src/lib/server/followActions.ts b/web-player/src/lib/server/actions/follow.ts similarity index 91% rename from web-player/src/lib/server/followActions.ts rename to web-player/src/lib/server/actions/follow.ts index 93b81b3..bd7bb30 100644 --- a/web-player/src/lib/server/followActions.ts +++ b/web-player/src/lib/server/actions/follow.ts @@ -1,5 +1,5 @@ import { fail } from '@sveltejs/kit'; -import { authedAction } from '$lib/server/api'; +import { authedAction } from '$lib/server/actions/authedAction'; const setFollowing = (follow: boolean) => authedAction(async ({ api, params }) => { diff --git a/web-player/src/lib/server/playlistActions.ts b/web-player/src/lib/server/actions/playlist.ts similarity index 92% rename from web-player/src/lib/server/playlistActions.ts rename to web-player/src/lib/server/actions/playlist.ts index b98a10a..9337206 100644 --- a/web-player/src/lib/server/playlistActions.ts +++ b/web-player/src/lib/server/actions/playlist.ts @@ -1,5 +1,6 @@ import { fail } from '@sveltejs/kit'; -import { authedAction, formString } from '$lib/server/api'; +import { formString } from '$lib/server/forms/fields'; +import { authedAction } from '$lib/server/actions/authedAction'; export const addTrackAction = authedAction(async ({ api, form, params }) => { const playlistId = formString(form, 'playlistId') || (params.id ?? ''); diff --git a/web-player/src/lib/server/api.ts b/web-player/src/lib/server/api.ts index fa2f878..316eabf 100644 --- a/web-player/src/lib/server/api.ts +++ b/web-player/src/lib/server/api.ts @@ -1,7 +1,6 @@ import createClient from 'openapi-fetch'; -import { error, fail, redirect, type ActionFailure, type RequestEvent } from '@sveltejs/kit'; +import { error, fail, type ActionFailure, type RequestEvent } from '@sveltejs/kit'; import type { paths } from '$lib/api/schema'; -import type { SessionUser } from '$lib/types'; import { apiConfig } from '$lib/server/config'; interface ApiClientOptions { @@ -23,59 +22,6 @@ export function apiFor({ fetch, locals }: Pick return createApiClient({ fetch, accessToken: locals.accessToken ?? undefined }); } -export function loginRedirect(url: URL): never { - redirect(302, `/auth?returnTo=${encodeURIComponent(url.pathname + url.search)}`); -} - -export function requireUser(locals: App.Locals, url: URL): SessionUser { - if (!locals.user) loginRedirect(url); - return locals.user; -} - -export function optionalUser( - locals: App.Locals, - url: URL, - allowAnonymousListening: boolean -): SessionUser | null { - if (locals.user) return locals.user; - if (!allowAnonymousListening) loginRedirect(url); - return null; -} - -export function formString(form: FormData, key: string): string { - return String(form.get(key) ?? ''); -} - -export function formFile(form: FormData, key: string): File | null { - const value = form.get(key); - return value instanceof File && value.size > 0 ? value : null; -} - -interface ActionContext { - api: ApiClient; - form: FormData; - params: Record; - locals: App.Locals; - url: URL; -} - -export function authedAction( - handler: (context: ActionContext) => R | Promise, - unauthorizedMessage = 'Inicia sesión.' -) { - return async (event: RequestEvent) => { - if (!event.locals.accessToken) return fail(401, { message: unauthorizedMessage }); - const form = await event.request.formData(); - return handler({ - api: apiFor(event), - form, - params: event.params as Record, - locals: event.locals, - url: event.url - }); - }; -} - export function unwrapOrError( result: { data?: T; error?: unknown; response?: Response }, message: string, diff --git a/web-player/src/lib/server/forms/albumForm.ts b/web-player/src/lib/server/forms/albumForm.ts index b4776a4..4d33be5 100644 --- a/web-player/src/lib/server/forms/albumForm.ts +++ b/web-player/src/lib/server/forms/albumForm.ts @@ -1,4 +1,4 @@ -import { formString } from '$lib/server/api'; +import { formString } from '$lib/server/forms/fields'; import { ALBUM_EARLIEST_YEAR, LIMITS } from '$lib/validation'; interface AlbumFormBody { diff --git a/web-player/src/lib/server/forms/fields.ts b/web-player/src/lib/server/forms/fields.ts new file mode 100644 index 0000000..f594019 --- /dev/null +++ b/web-player/src/lib/server/forms/fields.ts @@ -0,0 +1,8 @@ +export function formString(form: FormData, key: string): string { + return String(form.get(key) ?? ''); +} + +export function formFile(form: FormData, key: string): File | null { + const value = form.get(key); + return value instanceof File && value.size > 0 ? value : null; +} diff --git a/web-player/src/lib/server/forms/playlistForm.ts b/web-player/src/lib/server/forms/playlistForm.ts index f89ee5e..a27fd7d 100644 --- a/web-player/src/lib/server/forms/playlistForm.ts +++ b/web-player/src/lib/server/forms/playlistForm.ts @@ -1,4 +1,4 @@ -import { formFile, formString } from '$lib/server/api'; +import { formFile, formString } from '$lib/server/forms/fields'; import { LIMITS } from '$lib/validation'; interface PlaylistFormBody { diff --git a/web-player/src/lib/server/forms/trackUploadForm.ts b/web-player/src/lib/server/forms/trackUploadForm.ts index f1ef7e9..d64a3bb 100644 --- a/web-player/src/lib/server/forms/trackUploadForm.ts +++ b/web-player/src/lib/server/forms/trackUploadForm.ts @@ -1,4 +1,4 @@ -import { formFile, formString } from '$lib/server/api'; +import { formFile, formString } from '$lib/server/forms/fields'; import { LIMITS } from '$lib/validation'; interface TrackUploadBody { diff --git a/web-player/src/lib/server/guards.ts b/web-player/src/lib/server/guards.ts new file mode 100644 index 0000000..698e9cd --- /dev/null +++ b/web-player/src/lib/server/guards.ts @@ -0,0 +1,21 @@ +import { redirect } from '@sveltejs/kit'; +import type { SessionUser } from '$lib/types'; + +export function loginRedirect(url: URL): never { + redirect(302, `/auth?returnTo=${encodeURIComponent(url.pathname + url.search)}`); +} + +export function requireUser(locals: App.Locals, url: URL): SessionUser { + if (!locals.user) loginRedirect(url); + return locals.user; +} + +export function optionalUser( + locals: App.Locals, + url: URL, + allowAnonymousListening: boolean +): SessionUser | null { + if (locals.user) return locals.user; + if (!allowAnonymousListening) loginRedirect(url); + return null; +} diff --git a/web-player/src/routes/(app)/+layout.server.ts b/web-player/src/routes/(app)/+layout.server.ts index 4b61d6a..529d9ee 100644 --- a/web-player/src/routes/(app)/+layout.server.ts +++ b/web-player/src/routes/(app)/+layout.server.ts @@ -1,6 +1,7 @@ import type { LayoutServerLoad } from './$types'; import { getAllowAnonymousListening } from '$lib/server/playbackConfig'; -import { apiFor, loginRedirect } from '$lib/server/api'; +import { apiFor } from '$lib/server/api'; +import { loginRedirect } from '$lib/server/guards'; import { toLikedTrack, toPlaylist, toTrack } from '$lib/server/mappers'; import { authConfig } from '$lib/server/config'; import { LIKED_TRACKS_PAGE_SIZE, PLAYLIST_PICKER_PAGE_SIZE } from '$lib/config'; diff --git a/web-player/src/routes/(app)/+page.server.ts b/web-player/src/routes/(app)/+page.server.ts index ed82526..80f24c0 100644 --- a/web-player/src/routes/(app)/+page.server.ts +++ b/web-player/src/routes/(app)/+page.server.ts @@ -1,8 +1,9 @@ import { redirect } from '@sveltejs/kit'; import type { PageServerLoad, Actions } from './$types'; -import { apiFor, requireUser } from '$lib/server/api'; +import { apiFor } from '$lib/server/api'; +import { requireUser } from '$lib/server/guards'; import { toMix, toPlaylist, toTrack } from '$lib/server/mappers'; -import { addTrackAction } from '$lib/server/playlistActions'; +import { addTrackAction } from '$lib/server/actions/playlist'; import { HOME_MIXES_LIMIT, HOME_SHELF_LIMIT, HOME_SPOTLIGHT_TRACKS_LIMIT } from '$lib/config'; import { pickGreeting } from '$lib/server/greeting'; diff --git a/web-player/src/routes/(app)/albums/+page.server.ts b/web-player/src/routes/(app)/albums/+page.server.ts index 9dd0d7d..a81d2e7 100644 --- a/web-player/src/routes/(app)/albums/+page.server.ts +++ b/web-player/src/routes/(app)/albums/+page.server.ts @@ -1,6 +1,9 @@ import type { PageServerLoad, Actions } from './$types'; import { fail, redirect } from '@sveltejs/kit'; -import { apiFor, authedAction, formFile, requireUser, unwrapOrError } from '$lib/server/api'; +import { apiFor, unwrapOrError } from '$lib/server/api'; +import { requireUser } from '$lib/server/guards'; +import { formFile } from '$lib/server/forms/fields'; +import { authedAction } from '$lib/server/actions/authedAction'; import { toAlbum, toPage } from '$lib/server/mappers'; import { ALBUMS_PAGE_SIZE } from '$lib/config'; import { parseAlbumForm } from '$lib/server/forms/albumForm'; diff --git a/web-player/src/routes/(app)/albums/[id]/+page.server.ts b/web-player/src/routes/(app)/albums/[id]/+page.server.ts index 4e6864e..6dc0a2b 100644 --- a/web-player/src/routes/(app)/albums/[id]/+page.server.ts +++ b/web-player/src/routes/(app)/albums/[id]/+page.server.ts @@ -1,14 +1,9 @@ import type { PageServerLoad, Actions } from './$types'; import { fail, redirect } from '@sveltejs/kit'; -import { - apiFor, - authedAction, - failOnError, - formFile, - formString, - optionalUser, - unwrapOrError -} from '$lib/server/api'; +import { apiFor, failOnError, unwrapOrError } from '$lib/server/api'; +import { optionalUser } from '$lib/server/guards'; +import { formFile, formString } from '$lib/server/forms/fields'; +import { authedAction } from '$lib/server/actions/authedAction'; import { toAlbum, toTrack } from '$lib/server/mappers'; import { ALBUM_TRACKS_PAGE_SIZE, LIBRARY_PICKER_PAGE_SIZE } from '$lib/config'; import { parseAlbumForm } from '$lib/server/forms/albumForm'; diff --git a/web-player/src/routes/(app)/explore/+page.server.ts b/web-player/src/routes/(app)/explore/+page.server.ts index 4ea07e4..8daa47d 100644 --- a/web-player/src/routes/(app)/explore/+page.server.ts +++ b/web-player/src/routes/(app)/explore/+page.server.ts @@ -2,7 +2,7 @@ import type { PageServerLoad, Actions } from './$types'; import { apiFor, unwrapOrError } from '$lib/server/api'; import { getGenres } from '$lib/server/genres'; import { toAlbum, toPage, toTrack } from '$lib/server/mappers'; -import { addAlbumToPlaylistAction, addTrackAction } from '$lib/server/playlistActions'; +import { addAlbumToPlaylistAction, addTrackAction } from '$lib/server/actions/playlist'; import { EXPLORE_ALBUMS_PAGE_SIZE, EXPLORE_PAGE_SIZE, EXPLORE_USERS_LIMIT } from '$lib/config'; export const load: PageServerLoad = async ({ url, locals, fetch }) => { diff --git a/web-player/src/routes/(app)/library/+page.server.ts b/web-player/src/routes/(app)/library/+page.server.ts index d3eeaa7..684dc53 100644 --- a/web-player/src/routes/(app)/library/+page.server.ts +++ b/web-player/src/routes/(app)/library/+page.server.ts @@ -1,14 +1,10 @@ import type { PageServerLoad, Actions } from './$types'; import { fail } from '@sveltejs/kit'; import { toDatedTrack, toPage } from '$lib/server/mappers'; -import { - apiFor, - authedAction, - failOnError, - formString, - requireUser, - unwrapOrError -} from '$lib/server/api'; +import { apiFor, failOnError, unwrapOrError } from '$lib/server/api'; +import { requireUser } from '$lib/server/guards'; +import { formString } from '$lib/server/forms/fields'; +import { authedAction } from '$lib/server/actions/authedAction'; import { LIBRARY_PAGE_SIZE } from '$lib/config'; export const load: PageServerLoad = async ({ locals, url, fetch }) => { diff --git a/web-player/src/routes/(app)/mixes/[id]/+page.server.ts b/web-player/src/routes/(app)/mixes/[id]/+page.server.ts index c9e0518..7930923 100644 --- a/web-player/src/routes/(app)/mixes/[id]/+page.server.ts +++ b/web-player/src/routes/(app)/mixes/[id]/+page.server.ts @@ -1,7 +1,8 @@ import type { PageServerLoad, Actions } from './$types'; -import { apiFor, requireUser, unwrapOrError } from '$lib/server/api'; +import { apiFor, unwrapOrError } from '$lib/server/api'; +import { requireUser } from '$lib/server/guards'; import { toMix } from '$lib/server/mappers'; -import { addTrackAction } from '$lib/server/playlistActions'; +import { addTrackAction } from '$lib/server/actions/playlist'; export const load: PageServerLoad = async ({ params, locals, url, fetch }) => { requireUser(locals, url); diff --git a/web-player/src/routes/(app)/playlists/+page.server.ts b/web-player/src/routes/(app)/playlists/+page.server.ts index f3ad2e9..dd3607b 100644 --- a/web-player/src/routes/(app)/playlists/+page.server.ts +++ b/web-player/src/routes/(app)/playlists/+page.server.ts @@ -1,6 +1,8 @@ import type { PageServerLoad, Actions } from './$types'; import { fail, redirect } from '@sveltejs/kit'; -import { apiFor, authedAction, requireUser, unwrapOrError } from '$lib/server/api'; +import { apiFor, unwrapOrError } from '$lib/server/api'; +import { requireUser } from '$lib/server/guards'; +import { authedAction } from '$lib/server/actions/authedAction'; import { PLAYLISTS_PAGE_SIZE } from '$lib/config'; import { toPlaylist } from '$lib/server/mappers'; import { parsePlaylistForm } from '$lib/server/forms/playlistForm'; diff --git a/web-player/src/routes/(app)/playlists/[id]/+page.server.ts b/web-player/src/routes/(app)/playlists/[id]/+page.server.ts index 44f11ba..8449071 100644 --- a/web-player/src/routes/(app)/playlists/[id]/+page.server.ts +++ b/web-player/src/routes/(app)/playlists/[id]/+page.server.ts @@ -1,13 +1,9 @@ import type { PageServerLoad, Actions } from './$types'; import { fail, redirect } from '@sveltejs/kit'; -import { - apiFor, - authedAction, - failOnError, - formString, - optionalUser, - unwrapOrError -} from '$lib/server/api'; +import { apiFor, failOnError, unwrapOrError } from '$lib/server/api'; +import { optionalUser } from '$lib/server/guards'; +import { formString } from '$lib/server/forms/fields'; +import { authedAction } from '$lib/server/actions/authedAction'; import { PLAYLIST_TRACKS_PAGE_SIZE } from '$lib/config'; import { toDatedTrack, toPlaylist } from '$lib/server/mappers'; import { parsePlaylistForm } from '$lib/server/forms/playlistForm'; diff --git a/web-player/src/routes/(app)/upload/+page.server.ts b/web-player/src/routes/(app)/upload/+page.server.ts index 2c53046..d02b72b 100644 --- a/web-player/src/routes/(app)/upload/+page.server.ts +++ b/web-player/src/routes/(app)/upload/+page.server.ts @@ -1,6 +1,8 @@ import type { Actions, PageServerLoad } from './$types'; import { fail } from '@sveltejs/kit'; -import { apiFor, authedAction, requireData, requireUser } from '$lib/server/api'; +import { apiFor, requireData } from '$lib/server/api'; +import { requireUser } from '$lib/server/guards'; +import { authedAction } from '$lib/server/actions/authedAction'; import { parseTrackUploadForm } from '$lib/server/forms/trackUploadForm'; import { putPresigned, extOf, contentTypeOf, AUDIO_TYPES, IMAGE_TYPES } from '$lib/server/upload'; import { findConflict, genreInfo } from '$lib/data/genres'; diff --git a/web-player/src/routes/(app)/user/[id]/+page.server.ts b/web-player/src/routes/(app)/user/[id]/+page.server.ts index 90947bf..c0dc5de 100644 --- a/web-player/src/routes/(app)/user/[id]/+page.server.ts +++ b/web-player/src/routes/(app)/user/[id]/+page.server.ts @@ -1,8 +1,9 @@ import type { PageServerLoad, Actions } from './$types'; -import { apiFor, optionalUser, unwrapOrError } from '$lib/server/api'; +import { apiFor, unwrapOrError } from '$lib/server/api'; +import { optionalUser } from '$lib/server/guards'; import { toPlaylist } from '$lib/server/mappers'; import { PROFILE_PLAYLISTS_PAGE_SIZE } from '$lib/config'; -import { followUserAction, unfollowUserAction } from '$lib/server/followActions'; +import { followUserAction, unfollowUserAction } from '$lib/server/actions/follow'; export const load: PageServerLoad = async ({ params, locals, url, fetch, parent }) => { const { allowAnonymousListening } = await parent(); diff --git a/web-player/src/routes/(app)/user/[id]/[list=followList]/+page.server.ts b/web-player/src/routes/(app)/user/[id]/[list=followList]/+page.server.ts index d7befb2..46a3c41 100644 --- a/web-player/src/routes/(app)/user/[id]/[list=followList]/+page.server.ts +++ b/web-player/src/routes/(app)/user/[id]/[list=followList]/+page.server.ts @@ -1,5 +1,6 @@ import type { PageServerLoad } from './$types'; -import { apiFor, optionalUser, unwrapOrError } from '$lib/server/api'; +import { apiFor, unwrapOrError } from '$lib/server/api'; +import { optionalUser } from '$lib/server/guards'; import { toPage } from '$lib/server/mappers'; import { fetchFollowList } from '$lib/server/follows'; import { FOLLOW_LIST_PAGE_SIZE } from '$lib/config';