From 7cac6ff446c5f4a473d8ce27da2b36f47f649de0 Mon Sep 17 00:00:00 2001 From: XShine555 Date: Fri, 25 Sep 2026 01:15:04 +0200 Subject: [PATCH] Move library album and track operations into shared server modules --- .../src/lib/server/actions/libraryAlbum.ts | 56 +++++++++ web-player/src/lib/server/actions/tagged.ts | 14 +++ web-player/src/lib/server/albums.test.ts | 41 +++++++ web-player/src/lib/server/albums.ts | 107 +++++++----------- web-player/src/lib/server/forms/albumForm.ts | 2 +- web-player/src/lib/server/forms/fields.ts | 7 ++ web-player/src/lib/server/tracks.test.ts | 19 ++++ web-player/src/lib/server/tracks.ts | 8 ++ .../src/routes/(app)/albums/+page.server.ts | 24 ++-- .../routes/(app)/albums/[id]/+page.server.ts | 50 ++------ .../src/routes/(app)/library/+page.server.ts | 28 +++-- .../(app)/library/albums/[id]/+page.server.ts | 5 +- .../(app)/library/albums/new/+page.server.ts | 3 +- .../(app)/library/albums/new/+page.svelte | 4 + 14 files changed, 229 insertions(+), 139 deletions(-) create mode 100644 web-player/src/lib/server/actions/libraryAlbum.ts create mode 100644 web-player/src/lib/server/actions/tagged.ts create mode 100644 web-player/src/lib/server/albums.test.ts create mode 100644 web-player/src/lib/server/tracks.test.ts create mode 100644 web-player/src/lib/server/tracks.ts diff --git a/web-player/src/lib/server/actions/libraryAlbum.ts b/web-player/src/lib/server/actions/libraryAlbum.ts new file mode 100644 index 0000000..9ceb0a4 --- /dev/null +++ b/web-player/src/lib/server/actions/libraryAlbum.ts @@ -0,0 +1,56 @@ +import { fail, redirect } from '@sveltejs/kit'; +import { formFile, formIds, formString } from '$lib/server/forms/fields'; +import { parseAlbumForm } from '$lib/server/forms/albumForm'; +import { tagged } from '$lib/server/actions/tagged'; +import { + ALBUMS_SECTION_URL, + createAlbum, + deleteAlbum, + syncAlbumTracks, + updateAlbum, + uploadAlbumCover +} from '$lib/server/albums'; + +export const saveAlbum = tagged('saveAlbum', async ({ api, form }) => { + const parsed = parseAlbumForm(form); + if ('failMessage' in parsed) return fail(400, { message: parsed.failMessage }); + + const existingId = formString(form, 'albumId'); + const cover = await uploadAlbumCover(api, formFile(form, 'cover')); + if ('failMessage' in cover) + return fail(502, { message: cover.failMessage, detail: cover.detail }); + + let albumId = existingId; + if (existingId) { + const failure = await updateAlbum(api, existingId, parsed.body, cover.intentId); + if (failure) return failure; + } else { + if (!cover.intentId) return fail(400, { message: 'La portada es obligatoria.' }); + const created = await createAlbum(api, parsed.body, cover.intentId); + if ('failMessage' in created) return fail(502, { message: created.failMessage }); + albumId = created.id; + } + + const failure = await syncAlbumTracks( + api, + albumId, + formIds(form, 'addTrackIds'), + formIds(form, 'removeTrackIds') + ); + if (failure && !existingId) { + return fail(502, { + message: 'El álbum se creó, pero no se pudieron añadir las canciones. Inténtalo de nuevo.', + albumId + }); + } + return failure ?? { saved: true }; +}); + +export const removeAlbum = tagged('deleteAlbum', async ({ api, form }) => { + const albumId = formString(form, 'albumId'); + if (!albumId) return fail(400, { message: 'Falta el álbum.' }); + + const failure = await deleteAlbum(api, albumId); + if (failure) return failure; + redirect(303, ALBUMS_SECTION_URL); +}); diff --git a/web-player/src/lib/server/actions/tagged.ts b/web-player/src/lib/server/actions/tagged.ts new file mode 100644 index 0000000..663b442 --- /dev/null +++ b/web-player/src/lib/server/actions/tagged.ts @@ -0,0 +1,14 @@ +import { fail, isActionFailure } from '@sveltejs/kit'; +import { authedAction, type ActionContext } from '$lib/server/actions/authedAction'; + +export function tagged( + action: string, + handler: (context: ActionContext) => Promise +) { + return authedAction(async (context) => { + const result = await handler(context); + return isActionFailure(result) + ? fail(result.status, { ...(result.data as Record | undefined), action }) + : { ...result, action }; + }); +} diff --git a/web-player/src/lib/server/albums.test.ts b/web-player/src/lib/server/albums.test.ts new file mode 100644 index 0000000..0a201a4 --- /dev/null +++ b/web-player/src/lib/server/albums.test.ts @@ -0,0 +1,41 @@ +import { describe, expect, it, vi } from 'vitest'; +import type { ApiClient } from '$lib/server/api'; +import { syncAlbumTracks } from '$lib/server/albums'; + +function fakeApi(overrides: { post?: unknown; remove?: (trackId: string) => unknown } = {}) { + return { + POST: vi.fn(async () => overrides.post ?? {}), + DELETE: vi.fn(async (_path: string, init: { params: { path: { trackId: string } } }) => + overrides.remove ? overrides.remove(init.params.path.trackId) : {} + ) + }; +} + +describe('syncAlbumTracks', () => { + it('adds and removes tracks', async () => { + const api = fakeApi(); + const failure = await syncAlbumTracks(api as unknown as ApiClient, 'album', ['a'], ['b', 'c']); + expect(failure).toBeUndefined(); + expect(api.POST).toHaveBeenCalledTimes(1); + expect(api.DELETE).toHaveBeenCalledTimes(2); + }); + + it('skips the add request when there is nothing to add', async () => { + const api = fakeApi(); + await syncAlbumTracks(api as unknown as ApiClient, 'album', [], ['b']); + expect(api.POST).not.toHaveBeenCalled(); + }); + + it('fails without removing when adding fails', async () => { + const api = fakeApi({ post: { error: { status: 500 } } }); + const failure = await syncAlbumTracks(api as unknown as ApiClient, 'album', ['a'], ['b']); + expect(failure?.data?.message).toBe('No se pudieron añadir las canciones.'); + expect(api.DELETE).not.toHaveBeenCalled(); + }); + + it('fails when a removal fails', async () => { + const api = fakeApi({ remove: (id) => (id === 'c' ? { error: { status: 500 } } : {}) }); + const failure = await syncAlbumTracks(api as unknown as ApiClient, 'album', [], ['b', 'c']); + expect(failure?.data?.message).toBe('No se pudieron quitar las canciones.'); + }); +}); diff --git a/web-player/src/lib/server/albums.ts b/web-player/src/lib/server/albums.ts index ef896fb..90446a1 100644 --- a/web-player/src/lib/server/albums.ts +++ b/web-player/src/lib/server/albums.ts @@ -1,11 +1,8 @@ -import { fail, isActionFailure, redirect } from '@sveltejs/kit'; import type { Album } from '$lib/types'; import { toAlbum, toLibraryTrack, toPage } from '$lib/server/mappers'; import { failOnError, unwrapOrError, type ApiClient } from '$lib/server/api'; -import { formFile, formString } from '$lib/server/forms/fields'; -import { parseAlbumForm } from '$lib/server/forms/albumForm'; import { uploadOptionalCover } from '$lib/server/upload'; -import { authedAction, type ActionContext } from '$lib/server/actions/authedAction'; +import type { AlbumFormBody } from '$lib/server/forms/albumForm'; import { ALBUMS_PAGE_SIZE, ALBUM_TRACKS_PAGE_SIZE, LIBRARY_PAGE_SIZE } from '$lib/config'; export const ALBUMS_SECTION_URL = '/library/albums'; @@ -54,80 +51,64 @@ export async function loadLibraryTracks(api: ApiClient, userId: string) { ); } -export function formIds(form: FormData, key: string): string[] { - return formString(form, key).split(',').filter(Boolean); +export type CoverUpload = { intentId: string | null } | { failMessage: string; detail?: string }; + +export function uploadAlbumCover(api: ApiClient, file: File | null): Promise { + return uploadOptionalCover(api, '/albums/upload-picture', file); } -export function tagged(action: string, handler: (context: ActionContext) => Promise) { - return authedAction(async (context) => { - const result = await handler(context); - return isActionFailure(result) - ? fail(result.status, { ...(result.data as Record | undefined), action }) - : { ...result, action }; +export async function createAlbum( + api: ApiClient, + body: AlbumFormBody, + pictureIntentId: string +): Promise<{ id: string } | { failMessage: string }> { + const created = await api.POST('/albums', { body: { ...body, pictureIntentId } }); + if (created.error || !created.data) return { failMessage: 'No se pudo crear el álbum.' }; + return { id: created.data.id }; +} + +export async function updateAlbum( + api: ApiClient, + id: string, + body: AlbumFormBody, + pictureIntentId?: string | null +) { + const updated = await api.PUT('/albums/{id}', { params: { path: { id } }, body }); + const failure = failOnError(updated, 'No se pudo actualizar el álbum.'); + if (failure || !pictureIntentId) return failure; + + const picture = await api.PUT('/albums/{id}/picture', { + params: { path: { id } }, + body: { pictureIntentId } }); + return failOnError(picture, 'No se pudo actualizar la portada.'); } -export const saveAlbum = tagged('saveAlbum', async ({ api, form }) => { - const parsed = parseAlbumForm(form); - if ('failMessage' in parsed) return fail(400, { message: parsed.failMessage }); - - const existingId = formString(form, 'albumId'); - const cover = await uploadOptionalCover(api, '/albums/upload-picture', formFile(form, 'cover')); - if ('failMessage' in cover) - return fail(502, { message: cover.failMessage, detail: cover.detail }); - - let albumId = existingId; - if (existingId) { - const updated = await api.PUT('/albums/{id}', { - params: { path: { id: existingId } }, - body: parsed.body - }); - const failure = failOnError(updated, 'No se pudo actualizar el álbum.'); - if (failure) return failure; - - if (cover.intentId) { - const picture = await api.PUT('/albums/{id}/picture', { - params: { path: { id: existingId } }, - body: { pictureIntentId: cover.intentId } - }); - const pictureFailure = failOnError(picture, 'No se pudo actualizar la portada.'); - if (pictureFailure) return pictureFailure; - } - } else { - if (!cover.intentId) return fail(400, { message: 'La portada es obligatoria.' }); - const created = await api.POST('/albums', { - body: { ...parsed.body, pictureIntentId: cover.intentId } - }); - if (created.error || !created.data) return fail(502, { message: 'No se pudo crear el álbum.' }); - albumId = created.data.id; - } - - const addIds = formIds(form, 'addTrackIds'); - if (addIds.length > 0) { +export async function syncAlbumTracks( + api: ApiClient, + albumId: string, + add: string[], + remove: string[] +) { + if (add.length > 0) { const added = await api.POST('/albums/{id}/tracks', { params: { path: { id: albumId } }, - body: { trackIds: addIds } + body: { trackIds: add } }); const failure = failOnError(added, 'No se pudieron añadir las canciones.'); if (failure) return failure; } const removed = await Promise.all( - formIds(form, 'removeTrackIds').map((trackId) => + remove.map((trackId) => api.DELETE('/albums/{id}/tracks/{trackId}', { params: { path: { id: albumId, trackId } } }) ) ); const removeFailed = removed.find((result) => result.error); - const failure = removeFailed && failOnError(removeFailed, 'No se pudieron quitar las canciones.'); - return failure ?? { saved: true }; -}); + return removeFailed && failOnError(removeFailed, 'No se pudieron quitar las canciones.'); +} -export const deleteAlbum = tagged('deleteAlbum', async ({ api, form }) => { - const albumId = formString(form, 'albumId'); - if (!albumId) return fail(400, { message: 'Falta el álbum.' }); - - const result = await api.DELETE('/albums/{id}', { params: { path: { id: albumId } } }); - const failure = failOnError(result, 'No se pudo eliminar el álbum.'); - if (failure) return failure; - redirect(303, ALBUMS_SECTION_URL); -}); +export async function deleteAlbum(api: ApiClient, id: string) { + const result = await api.DELETE('/albums/{id}', { params: { path: { id } } }); + return failOnError(result, 'No se pudo eliminar el álbum.'); +} diff --git a/web-player/src/lib/server/forms/albumForm.ts b/web-player/src/lib/server/forms/albumForm.ts index 4d33be5..f3bbf9d 100644 --- a/web-player/src/lib/server/forms/albumForm.ts +++ b/web-player/src/lib/server/forms/albumForm.ts @@ -1,7 +1,7 @@ import { formString } from '$lib/server/forms/fields'; import { ALBUM_EARLIEST_YEAR, LIMITS } from '$lib/validation'; -interface AlbumFormBody { +export interface AlbumFormBody { title: string; description: string | null; releaseYear: number | null; diff --git a/web-player/src/lib/server/forms/fields.ts b/web-player/src/lib/server/forms/fields.ts index f594019..bd0bb27 100644 --- a/web-player/src/lib/server/forms/fields.ts +++ b/web-player/src/lib/server/forms/fields.ts @@ -6,3 +6,10 @@ export function formFile(form: FormData, key: string): File | null { const value = form.get(key); return value instanceof File && value.size > 0 ? value : null; } + +export function formIds(form: FormData, key: string): string[] { + return form + .getAll(key) + .flatMap((value) => String(value).split(',')) + .filter(Boolean); +} diff --git a/web-player/src/lib/server/tracks.test.ts b/web-player/src/lib/server/tracks.test.ts new file mode 100644 index 0000000..d8c62f9 --- /dev/null +++ b/web-player/src/lib/server/tracks.test.ts @@ -0,0 +1,19 @@ +import { describe, expect, it, vi } from 'vitest'; +import type { ApiClient } from '$lib/server/api'; +import { deleteTracks } from '$lib/server/tracks'; + +describe('deleteTracks', () => { + it('counts the deletions that failed', async () => { + const DELETE = vi.fn(async (_path: string, init: { params: { path: { id: string } } }) => + init.params.path.id === 'b' ? { error: { status: 500 } } : {} + ); + const result = await deleteTracks({ DELETE } as unknown as ApiClient, ['a', 'b', 'c']); + expect(result).toEqual({ failed: 1 }); + expect(DELETE).toHaveBeenCalledTimes(3); + }); + + it('reports no failures when every deletion succeeds', async () => { + const DELETE = vi.fn(async () => ({})); + expect(await deleteTracks({ DELETE } as unknown as ApiClient, ['a'])).toEqual({ failed: 0 }); + }); +}); diff --git a/web-player/src/lib/server/tracks.ts b/web-player/src/lib/server/tracks.ts new file mode 100644 index 0000000..e682818 --- /dev/null +++ b/web-player/src/lib/server/tracks.ts @@ -0,0 +1,8 @@ +import type { ApiClient } from '$lib/server/api'; + +export async function deleteTracks(api: ApiClient, ids: string[]): Promise<{ failed: number }> { + const results = await Promise.all( + ids.map((id) => api.DELETE('/tracks/{id}', { params: { path: { id } } })) + ); + return { failed: results.filter((result) => result.error).length }; +} diff --git a/web-player/src/routes/(app)/albums/+page.server.ts b/web-player/src/routes/(app)/albums/+page.server.ts index a81d2e7..26b5ec5 100644 --- a/web-player/src/routes/(app)/albums/+page.server.ts +++ b/web-player/src/routes/(app)/albums/+page.server.ts @@ -7,7 +7,7 @@ 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'; -import { uploadPresignedImage } from '$lib/server/upload'; +import { createAlbum, uploadAlbumCover } from '$lib/server/albums'; export const load: PageServerLoad = async ({ locals, url, fetch }) => { const user = requireUser(locals, url); @@ -27,23 +27,15 @@ export const actions: Actions = { const parsed = parseAlbumForm(form); if ('failMessage' in parsed) return fail(400, { message: parsed.failMessage }); - const cover = formFile(form, 'cover'); - if (!cover) return fail(400, { message: 'La portada es obligatoria.' }); - - const uploaded = await uploadPresignedImage( - (args) => api.POST('/albums/upload-picture', { body: args }), - cover - ); - if ('failMessage' in uploaded) { - return fail(502, { message: uploaded.failMessage, detail: uploaded.detail }); + const cover = await uploadAlbumCover(api, formFile(form, 'cover')); + if ('failMessage' in cover) { + return fail(502, { message: cover.failMessage, detail: cover.detail }); } + if (!cover.intentId) return fail(400, { message: 'La portada es obligatoria.' }); - const { data, error } = await api.POST('/albums', { - body: { ...parsed.body, pictureIntentId: uploaded.intentId } - }); + const created = await createAlbum(api, parsed.body, cover.intentId); + if ('failMessage' in created) return fail(502, { message: created.failMessage }); - if (error || !data) return fail(502, { message: 'No se pudo crear el álbum.' }); - - redirect(303, `/albums/${data.id}`); + redirect(303, `/albums/${created.id}`); }) }; 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 c8d9199..3903a2d 100644 --- a/web-player/src/routes/(app)/albums/[id]/+page.server.ts +++ b/web-player/src/routes/(app)/albums/[id]/+page.server.ts @@ -1,13 +1,13 @@ import type { PageServerLoad, Actions } from './$types'; import { fail, redirect } from '@sveltejs/kit'; -import { apiFor, failOnError, unwrapOrError } from '$lib/server/api'; +import { apiFor, 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'; -import { uploadOptionalCover } from '$lib/server/upload'; +import { deleteAlbum, syncAlbumTracks, updateAlbum, uploadAlbumCover } from '$lib/server/albums'; export const load: PageServerLoad = async ({ params, locals, url, fetch, parent }) => { const { allowAnonymousListening } = await parent(); @@ -50,62 +50,30 @@ export const actions: Actions = { const trackId = formString(form, 'trackId'); if (!trackId) return fail(400, { message: 'Falta la canción.' }); - const result = await api.POST('/albums/{id}/tracks', { - params: { path: { id: params.id } }, - body: { trackIds: [trackId] } - }); - return failOnError(result, 'No se pudo añadir la canción.') ?? { added: true }; + return (await syncAlbumTracks(api, params.id, [trackId], [])) ?? { added: true }; }), removeTrack: authedAction(async ({ api, form, params }) => { const trackId = formString(form, 'trackId'); if (!trackId) return fail(400, { message: 'Falta la canción.' }); - const result = await api.DELETE('/albums/{id}/tracks/{trackId}', { - params: { path: { id: params.id, trackId } } - }); - return failOnError(result, 'No se pudo quitar la canción.') ?? { removed: true }; + return (await syncAlbumTracks(api, params.id, [], [trackId])) ?? { removed: true }; }), edit: authedAction(async ({ api, form, params }) => { const parsed = parseAlbumForm(form); if ('failMessage' in parsed) return fail(400, { message: parsed.failMessage }); - const result = await api.PUT('/albums/{id}', { - params: { path: { id: params.id } }, - body: { - title: parsed.body.title, - description: parsed.body.description, - releaseYear: parsed.body.releaseYear - } - }); - const failure = failOnError(result, 'No se pudo actualizar el álbum.'); - if (failure) return failure; - - const coverFile = formFile(form, 'cover'); - if (coverFile) { - const cover = await uploadOptionalCover(api, '/albums/upload-picture', coverFile); - if ('failMessage' in cover) { - return fail(502, { message: cover.failMessage, detail: cover.detail }); - } - if (cover.intentId) { - const pictureResult = await api.PUT('/albums/{id}/picture', { - params: { path: { id: params.id } }, - body: { pictureIntentId: cover.intentId } - }); - const pictureFailure = failOnError(pictureResult, 'No se pudo actualizar la portada.'); - if (pictureFailure) return pictureFailure; - } + const cover = await uploadAlbumCover(api, formFile(form, 'cover')); + if ('failMessage' in cover) { + return fail(502, { message: cover.failMessage, detail: cover.detail }); } - return { edited: true }; + return (await updateAlbum(api, params.id, parsed.body, cover.intentId)) ?? { edited: true }; }), delete: authedAction(async ({ api, params }) => { - const result = await api.DELETE('/albums/{id}', { - params: { path: { id: params.id } } - }); - const failure = failOnError(result, 'No se pudo eliminar el álbum.'); + const failure = await deleteAlbum(api, params.id); if (failure) return failure; redirect(303, '/albums'); }) diff --git a/web-player/src/routes/(app)/library/+page.server.ts b/web-player/src/routes/(app)/library/+page.server.ts index 3cfab88..2f63f0f 100644 --- a/web-player/src/routes/(app)/library/+page.server.ts +++ b/web-player/src/routes/(app)/library/+page.server.ts @@ -1,8 +1,11 @@ import type { PageServerLoad, Actions } from './$types'; import { fail } from '@sveltejs/kit'; -import { apiFor, failOnError } from '$lib/server/api'; +import { apiFor } from '$lib/server/api'; import { requireUser } from '$lib/server/guards'; -import { formIds, loadLibraryTracks, tagged } from '$lib/server/albums'; +import { formIds } from '$lib/server/forms/fields'; +import { tagged } from '$lib/server/actions/tagged'; +import { loadLibraryTracks } from '$lib/server/albums'; +import { deleteTracks } from '$lib/server/tracks'; export const load: PageServerLoad = async ({ locals, url, fetch }) => { const user = requireUser(locals, url); @@ -15,18 +18,13 @@ export const actions: Actions = { const trackIds = formIds(form, 'trackIds'); if (trackIds.length === 0) return fail(400, { message: 'Falta la canción.' }); - const results = await Promise.all( - trackIds.map((id) => api.DELETE('/tracks/{id}', { params: { path: { id } } })) - ); - const failed = results.find((result) => result.error); - return ( - (failed && - failOnError( - failed, - trackIds.length === 1 - ? 'No se pudo borrar la canción.' - : 'No se pudieron borrar todas las canciones.' - )) ?? { deleted: true } - ); + const { failed } = await deleteTracks(api, trackIds); + if (failed === 0) return { deleted: true }; + return fail(502, { + message: + trackIds.length === 1 + ? 'No se pudo borrar la canción.' + : `No se pudieron borrar ${failed} de ${trackIds.length} canciones.` + }); }) }; diff --git a/web-player/src/routes/(app)/library/albums/[id]/+page.server.ts b/web-player/src/routes/(app)/library/albums/[id]/+page.server.ts index ca05cfe..7e897fd 100644 --- a/web-player/src/routes/(app)/library/albums/[id]/+page.server.ts +++ b/web-player/src/routes/(app)/library/albums/[id]/+page.server.ts @@ -2,7 +2,8 @@ import type { Actions, PageServerLoad } from './$types'; import { apiFor, unwrapOrError } from '$lib/server/api'; import { requireUser } from '$lib/server/guards'; import { toAlbum } from '$lib/server/mappers'; -import { deleteAlbum, loadLibraryTracks, saveAlbum, trackIdsByAlbum } from '$lib/server/albums'; +import { loadLibraryTracks, trackIdsByAlbum } from '$lib/server/albums'; +import { removeAlbum, saveAlbum } from '$lib/server/actions/libraryAlbum'; export const load: PageServerLoad = async ({ params, locals, url, fetch }) => { const user = requireUser(locals, url); @@ -16,4 +17,4 @@ export const load: PageServerLoad = async ({ params, locals, url, fetch }) => { return { album, tracks, memberIds: memberIds[album.id] ?? [] }; }; -export const actions: Actions = { saveAlbum, deleteAlbum }; +export const actions: Actions = { saveAlbum, deleteAlbum: removeAlbum }; diff --git a/web-player/src/routes/(app)/library/albums/new/+page.server.ts b/web-player/src/routes/(app)/library/albums/new/+page.server.ts index 80c76b6..d198cf1 100644 --- a/web-player/src/routes/(app)/library/albums/new/+page.server.ts +++ b/web-player/src/routes/(app)/library/albums/new/+page.server.ts @@ -1,7 +1,8 @@ import type { Actions, PageServerLoad } from './$types'; import { apiFor } from '$lib/server/api'; import { requireUser } from '$lib/server/guards'; -import { loadLibraryTracks, saveAlbum } from '$lib/server/albums'; +import { loadLibraryTracks } from '$lib/server/albums'; +import { saveAlbum } from '$lib/server/actions/libraryAlbum'; export const load: PageServerLoad = async ({ locals, url, fetch }) => { const user = requireUser(locals, url); diff --git a/web-player/src/routes/(app)/library/albums/new/+page.svelte b/web-player/src/routes/(app)/library/albums/new/+page.svelte index a145145..84873ae 100644 --- a/web-player/src/routes/(app)/library/albums/new/+page.svelte +++ b/web-player/src/routes/(app)/library/albums/new/+page.svelte @@ -3,6 +3,10 @@ import LibraryAlbumEditor from '$lib/components/music/library/LibraryAlbumEditor.svelte'; let { data, form } = $props(); + + $effect(() => { + if (form && 'albumId' in form) goto(`/library/albums/${form.albumId}`); + });