202 lines
5.1 KiB
TypeScript
202 lines
5.1 KiB
TypeScript
import type { RequestHandler } from './$types'
|
|
import { prisma } from '$lib/server/database'
|
|
import {
|
|
jsonResponse,
|
|
errorResponse,
|
|
getPaginationParams,
|
|
getPaginationMeta,
|
|
checkAdminAuth
|
|
} from '$lib/server/api-utils'
|
|
import { logger } from '$lib/server/logger'
|
|
import {
|
|
trackMediaUsage,
|
|
extractMediaIds,
|
|
type MediaUsageReference
|
|
} from '$lib/server/media-usage.js'
|
|
|
|
// GET /api/posts - List all posts
|
|
export const GET: RequestHandler = async (event) => {
|
|
if (!checkAdminAuth(event)) {
|
|
return errorResponse('Unauthorized', 401)
|
|
}
|
|
|
|
try {
|
|
const { page, limit } = getPaginationParams(event.url)
|
|
const skip = (page - 1) * limit
|
|
|
|
// Get filter parameters
|
|
const status = event.url.searchParams.get('status')
|
|
const postType = event.url.searchParams.get('postType')
|
|
|
|
// Build where clause
|
|
const where: any = {}
|
|
if (status) {
|
|
where.status = status
|
|
}
|
|
if (postType) {
|
|
where.postType = postType
|
|
}
|
|
|
|
// Get total count
|
|
const total = await prisma.post.count({ where })
|
|
|
|
// Get posts
|
|
const posts = await prisma.post.findMany({
|
|
where,
|
|
orderBy: { createdAt: 'desc' },
|
|
skip,
|
|
take: limit
|
|
})
|
|
|
|
const pagination = getPaginationMeta(total, page, limit)
|
|
|
|
logger.info('Posts list retrieved', { total, page, limit })
|
|
|
|
return jsonResponse({
|
|
posts,
|
|
pagination
|
|
})
|
|
} catch (error) {
|
|
logger.error('Failed to retrieve posts', error as Error)
|
|
return errorResponse('Failed to retrieve posts', 500)
|
|
}
|
|
}
|
|
|
|
// POST /api/posts - Create a new post
|
|
export const POST: RequestHandler = async (event) => {
|
|
if (!checkAdminAuth(event)) {
|
|
return errorResponse('Unauthorized', 401)
|
|
}
|
|
|
|
try {
|
|
const data = await event.request.json()
|
|
|
|
// Generate slug if not provided
|
|
if (!data.slug) {
|
|
if (data.title) {
|
|
// Generate slug from title
|
|
data.slug = data.title
|
|
.toLowerCase()
|
|
.replace(/[^a-z0-9]+/g, '-')
|
|
.replace(/^-+|-+$/g, '')
|
|
} else {
|
|
// Generate timestamp-based slug for posts without titles
|
|
data.slug = `post-${Date.now()}`
|
|
}
|
|
}
|
|
|
|
// Set publishedAt if status is published
|
|
if (data.status === 'published') {
|
|
data.publishedAt = new Date()
|
|
}
|
|
|
|
// Handle photo attachments for posts
|
|
let featuredImageId = data.featuredImage
|
|
if (data.attachedPhotos && data.attachedPhotos.length > 0 && !featuredImageId) {
|
|
// Use first attached photo as featured image for photo posts
|
|
featuredImageId = data.attachedPhotos[0]
|
|
}
|
|
|
|
// Handle album gallery - use first image as featured image
|
|
if (data.gallery && data.gallery.length > 0 && !featuredImageId) {
|
|
// Get the media URL for the first gallery item
|
|
const firstMedia = await prisma.media.findUnique({
|
|
where: { id: data.gallery[0] },
|
|
select: { url: true }
|
|
})
|
|
if (firstMedia) {
|
|
featuredImageId = firstMedia.url
|
|
}
|
|
}
|
|
|
|
// For albums, store gallery IDs in content field as a special structure
|
|
let postContent = data.content
|
|
if (data.type === 'album' && data.gallery) {
|
|
postContent = {
|
|
type: 'album',
|
|
gallery: data.gallery,
|
|
description: data.content
|
|
}
|
|
}
|
|
|
|
const post = await prisma.post.create({
|
|
data: {
|
|
title: data.title,
|
|
slug: data.slug,
|
|
postType: data.type,
|
|
status: data.status,
|
|
content: postContent,
|
|
linkUrl: data.link_url,
|
|
linkDescription: data.linkDescription,
|
|
featuredImage: featuredImageId,
|
|
attachments:
|
|
data.attachedPhotos && data.attachedPhotos.length > 0 ? data.attachedPhotos : null,
|
|
tags: data.tags,
|
|
publishedAt: data.publishedAt
|
|
}
|
|
})
|
|
|
|
// Track media usage
|
|
try {
|
|
const usageReferences: MediaUsageReference[] = []
|
|
|
|
// Track featured image
|
|
const featuredImageIds = extractMediaIds({ featuredImage: featuredImageId }, 'featuredImage')
|
|
featuredImageIds.forEach((mediaId) => {
|
|
usageReferences.push({
|
|
mediaId,
|
|
contentType: 'post',
|
|
contentId: post.id,
|
|
fieldName: 'featuredImage'
|
|
})
|
|
})
|
|
|
|
// Track attached photos (for photo posts)
|
|
if (data.attachedPhotos && Array.isArray(data.attachedPhotos)) {
|
|
data.attachedPhotos.forEach((mediaId: number) => {
|
|
usageReferences.push({
|
|
mediaId,
|
|
contentType: 'post',
|
|
contentId: post.id,
|
|
fieldName: 'attachments'
|
|
})
|
|
})
|
|
}
|
|
|
|
// Track gallery (for album posts)
|
|
if (data.gallery && Array.isArray(data.gallery)) {
|
|
data.gallery.forEach((mediaId: number) => {
|
|
usageReferences.push({
|
|
mediaId,
|
|
contentType: 'post',
|
|
contentId: post.id,
|
|
fieldName: 'gallery'
|
|
})
|
|
})
|
|
}
|
|
|
|
// Track media in post content
|
|
const contentIds = extractMediaIds({ content: postContent }, 'content')
|
|
contentIds.forEach((mediaId) => {
|
|
usageReferences.push({
|
|
mediaId,
|
|
contentType: 'post',
|
|
contentId: post.id,
|
|
fieldName: 'content'
|
|
})
|
|
})
|
|
|
|
if (usageReferences.length > 0) {
|
|
await trackMediaUsage(usageReferences)
|
|
}
|
|
} catch (error) {
|
|
logger.warn('Failed to track media usage for post', { postId: post.id, error })
|
|
}
|
|
|
|
logger.info('Post created', { id: post.id, title: post.title })
|
|
return jsonResponse(post)
|
|
} catch (error) {
|
|
logger.error('Failed to create post', error as Error)
|
|
return errorResponse('Failed to create post', 500)
|
|
}
|
|
}
|