Move library album and track operations into shared server modules
This commit is contained in:
@@ -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);
|
||||
});
|
||||
@@ -0,0 +1,14 @@
|
||||
import { fail, isActionFailure } from '@sveltejs/kit';
|
||||
import { authedAction, type ActionContext } from '$lib/server/actions/authedAction';
|
||||
|
||||
export function tagged<T extends object>(
|
||||
action: string,
|
||||
handler: (context: ActionContext) => Promise<T>
|
||||
) {
|
||||
return authedAction(async (context) => {
|
||||
const result = await handler(context);
|
||||
return isActionFailure(result)
|
||||
? fail(result.status, { ...(result.data as Record<string, unknown> | undefined), action })
|
||||
: { ...result, action };
|
||||
});
|
||||
}
|
||||
@@ -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.');
|
||||
});
|
||||
});
|
||||
@@ -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<CoverUpload> {
|
||||
return uploadOptionalCover(api, '/albums/upload-picture', file);
|
||||
}
|
||||
|
||||
export function tagged<T>(action: string, handler: (context: ActionContext) => Promise<T>) {
|
||||
return authedAction(async (context) => {
|
||||
const result = await handler(context);
|
||||
return isActionFailure(result)
|
||||
? fail(result.status, { ...(result.data as Record<string, unknown> | 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 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
|
||||
});
|
||||
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) return failure;
|
||||
if (failure || !pictureIntentId) return failure;
|
||||
|
||||
if (cover.intentId) {
|
||||
const picture = await api.PUT('/albums/{id}/picture', {
|
||||
params: { path: { id: existingId } },
|
||||
body: { pictureIntentId: cover.intentId }
|
||||
params: { path: { id } },
|
||||
body: { pictureIntentId }
|
||||
});
|
||||
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;
|
||||
return failOnError(picture, 'No se pudo actualizar la portada.');
|
||||
}
|
||||
|
||||
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.');
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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 });
|
||||
});
|
||||
});
|
||||
@@ -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 };
|
||||
}
|
||||
@@ -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);
|
||||
const cover = await uploadAlbumCover(api, formFile(form, 'cover'));
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
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 todas las canciones.'
|
||||
)) ?? { deleted: true }
|
||||
);
|
||||
: `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">
|
||||
|
||||
Reference in New Issue
Block a user