This commit is contained in:
2026-07-18 23:29:57 +02:00
parent 4fed9bd059
commit 2dc544b2c0
109 changed files with 5134 additions and 2163 deletions
@@ -1,52 +1,34 @@
import type { PageServerLoad, Actions } from './$types';
import { error, fail, redirect } from '@sveltejs/kit';
import { createApiClient } from '$lib/server/api';
import { putPresigned } from '$lib/server/upload';
import { fail, redirect } from '@sveltejs/kit';
import {
createApiClient,
playlistCoverTrackIds,
requireAccessTokenAction,
unwrapOrError
} from '$lib/server/api';
import { uploadPresignedImage } from '$lib/server/upload';
const MAX_NAME = 100;
const IMAGE_TYPES: Record<string, string> = {
jpg: 'image/jpeg',
jpeg: 'image/jpeg',
png: 'image/png',
webp: 'image/webp'
};
function extOf(name: string): string {
const dot = name.lastIndexOf('.');
return dot >= 0 ? name.slice(dot + 1).toLowerCase() : '';
}
function contentTypeOf(file: File): string {
if (file.type) return file.type;
return IMAGE_TYPES[extOf(file.name)] ?? 'application/octet-stream';
}
export const load: PageServerLoad = async ({ locals, fetch }) => {
if (!locals.user) error(401, 'Inicia sesión.');
const api = createApiClient({ fetch, accessToken: locals.accessToken ?? undefined });
const { data, error: err } = await api.GET('/playlists/users/{userId}', {
params: { path: { userId: locals.user.sub }, query: { pageNumber: 1, pageSize: 50 } }
const result = await api.GET('/playlists/users/{userId}', {
params: { path: { userId: locals.user!.sub }, query: { pageNumber: 1, pageSize: 50 } }
});
if (err || !data) error(502, 'No se pudieron cargar tus playlists.');
const covers = await Promise.all(
data.items.map(async (playlist) => {
const { data: tracks } = await api.GET('/playlists/{playlistId}/tracks', {
params: { path: { playlistId: playlist.id }, query: { pageNumber: 1, pageSize: 4 } }
});
return [playlist.id, tracks?.items.map((t) => t.id) ?? []] as const;
})
const data = unwrapOrError(result, 'No se pudieron cargar tus playlists.');
const trackIds = await playlistCoverTrackIds(
api,
data.items.map((p) => p.id)
);
return { playlists: data, trackIds: Object.fromEntries(covers) as Record<string, string[]> };
return { playlists: data, trackIds };
};
export const actions: Actions = {
create: async ({ request, locals, fetch }) => {
if (!locals.accessToken) return fail(401, { message: 'Inicia sesión.' });
const accessToken = requireAccessTokenAction(locals);
if (typeof accessToken !== 'string') return accessToken;
const form = await request.formData();
const name = String(form.get('name') ?? '').trim();
@@ -57,36 +39,18 @@ export const actions: Actions = {
return fail(400, { message: 'El nombre es obligatorio (máx. 100 caracteres).', name, description });
}
const api = createApiClient({ fetch, accessToken: locals.accessToken });
const api = createApiClient({ fetch, accessToken });
let pictureIntentId: string | null = null;
if (cover instanceof File && cover.size > 0) {
const contentType = contentTypeOf(cover);
const { data: upload, error: uploadError } = await api.POST('/playlists/upload-picture', {
body: {
fileType: extOf(cover.name) || 'jpg',
contentType,
expectedSizeBytes: cover.size
}
});
if (uploadError || !upload) {
return fail(502, { message: 'No se pudo reservar la subida de la portada.', name, description });
const result = await uploadPresignedImage(
(args) => api.POST('/playlists/upload-picture', { body: args }),
cover
);
if ('failMessage' in result) {
return fail(502, { message: result.failMessage, name, description, detail: result.detail });
}
try {
const coverBuffer = await cover.arrayBuffer();
await putPresigned(upload.uploadUrl, Buffer.from(coverBuffer), upload.contentType);
} catch (err) {
return fail(502, {
message: 'Falló la subida de la portada al almacenamiento.',
name,
description,
detail: err instanceof Error ? err.message : undefined
});
}
pictureIntentId = upload.intentId;
pictureIntentId = result.intentId;
}
const { data, error: err } = await api.POST('/playlists', {