jedmund-svelte/src/routes/api/albums/+server.ts
Justin Edmund 003e08836e feat(api): add album content support and media management endpoints
- Update album CRUD endpoints to handle content field
- Add /api/albums/[id]/media endpoint for managing album media
- Add /api/media/[id]/albums endpoint for media-album associations
- Create album routes for public album viewing
- Update album queries to use new MediaAlbum join table
- Support filtering and sorting in album listings

Enables rich content albums with flexible media associations.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-06-24 01:11:30 +01:00

164 lines
4.3 KiB
TypeScript

import type { RequestHandler } from './$types'
import { prisma } from '$lib/server/database'
import { jsonResponse, errorResponse, checkAdminAuth } from '$lib/server/api-utils'
import { logger } from '$lib/server/logger'
// GET /api/albums - Get published photography albums (or all albums if admin)
export const GET: RequestHandler = async (event) => {
try {
const url = new URL(event.request.url)
const limit = parseInt(url.searchParams.get('limit') || '50')
const offset = parseInt(url.searchParams.get('offset') || '0')
// Check if this is an admin request
const isAdmin = checkAdminAuth(event)
// Fetch albums - all for admin, only published for public
const albums = await prisma.album.findMany({
where: isAdmin
? {}
: {
status: 'published'
},
include: {
media: {
orderBy: { displayOrder: 'asc' },
take: 1, // Only need the first photo for cover
include: {
media: {
select: {
id: true,
url: true,
thumbnailUrl: true,
width: true,
height: true,
dominantColor: true,
colors: true,
aspectRatio: true,
photoCaption: true
}
}
}
},
_count: {
select: {
media: true
}
}
},
orderBy: [{ date: 'desc' }, { createdAt: 'desc' }],
skip: offset,
take: limit
})
// Get total count for pagination
const totalCount = await prisma.album.count({
where: isAdmin
? {}
: {
status: 'published'
}
})
// Transform albums for response
const transformedAlbums = albums.map((album) => ({
id: album.id,
slug: album.slug,
title: album.title,
description: album.description,
date: album.date,
location: album.location,
photoCount: album._count.media,
coverPhoto: album.media[0]?.media
? {
id: album.media[0].media.id,
url: album.media[0].media.url,
thumbnailUrl: album.media[0].media.thumbnailUrl,
width: album.media[0].media.width,
height: album.media[0].media.height,
dominantColor: album.media[0].media.dominantColor,
colors: album.media[0].media.colors,
aspectRatio: album.media[0].media.aspectRatio,
caption: album.media[0].media.photoCaption
}
: null,
hasContent: !!album.content, // Indicates if album has composed content
// Include additional fields for admin
...(isAdmin
? {
status: album.status,
showInUniverse: album.showInUniverse,
publishedAt: album.publishedAt,
createdAt: album.createdAt,
updatedAt: album.updatedAt,
coverPhotoId: album.coverPhotoId,
photos: album.media.map((m) => ({
id: m.media.id,
url: m.media.url,
thumbnailUrl: m.media.thumbnailUrl,
caption: m.media.photoCaption
})),
_count: album._count
}
: {})
}))
const response = {
albums: transformedAlbums,
pagination: {
total: totalCount,
limit,
offset,
hasMore: offset + limit < totalCount
}
}
return jsonResponse(response)
} catch (error) {
logger.error('Failed to fetch albums', error as Error)
return errorResponse('Failed to fetch albums', 500)
}
}
// POST /api/albums - Create a new album (admin only)
export const POST: RequestHandler = async (event) => {
// Check admin auth
if (!checkAdminAuth(event)) {
return errorResponse('Unauthorized', 401)
}
try {
const body = await event.request.json()
// Validate required fields
if (!body.title || !body.slug) {
return errorResponse('Title and slug are required', 400)
}
// Create the album
const album = await prisma.album.create({
data: {
title: body.title,
slug: body.slug,
description: body.description || null,
date: body.date ? new Date(body.date) : null,
location: body.location || null,
showInUniverse: body.showInUniverse ?? false,
status: body.status || 'draft',
content: body.content || null,
publishedAt: body.status === 'published' ? new Date() : null
}
})
return jsonResponse(album, 201)
} catch (error) {
logger.error('Failed to create album', error as Error)
// Check for unique constraint violation
if (error instanceof Error && error.message.includes('Unique constraint')) {
return errorResponse('An album with this slug already exists', 409)
}
return errorResponse('Failed to create album', 500)
}
}