Move library album and track operations into shared server modules

This commit is contained in:
2026-09-25 01:15:04 +02:00
parent 797bed8e7d
commit 7cac6ff446
14 changed files with 229 additions and 139 deletions
@@ -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}`);
})
};
@@ -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');
})
@@ -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.`
});
})
};
@@ -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 };
@@ -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);
@@ -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}`);
});
</script>
<div class="mt-4">