235 lines
6.6 KiB
TypeScript
235 lines
6.6 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'
|
|
import {
|
|
updateMediaUsage,
|
|
removeMediaUsage,
|
|
extractMediaIds,
|
|
trackMediaUsage,
|
|
type MediaUsageReference
|
|
} from '$lib/server/media-usage.js'
|
|
|
|
// GET /api/posts/[id] - Get a single post
|
|
export const GET: RequestHandler = async (event) => {
|
|
if (!checkAdminAuth(event)) {
|
|
return errorResponse('Unauthorized', 401)
|
|
}
|
|
|
|
try {
|
|
const id = parseInt(event.params.id)
|
|
if (isNaN(id)) {
|
|
return errorResponse('Invalid post ID', 400)
|
|
}
|
|
|
|
const post = await prisma.post.findUnique({
|
|
where: { id }
|
|
})
|
|
|
|
if (!post) {
|
|
return errorResponse('Post not found', 404)
|
|
}
|
|
|
|
logger.info('Post retrieved', { id })
|
|
return jsonResponse(post)
|
|
} catch (error) {
|
|
logger.error('Failed to retrieve post', error as Error)
|
|
return errorResponse('Failed to retrieve post', 500)
|
|
}
|
|
}
|
|
|
|
// PUT /api/posts/[id] - Update a post
|
|
export const PUT: RequestHandler = async (event) => {
|
|
if (!checkAdminAuth(event)) {
|
|
return errorResponse('Unauthorized', 401)
|
|
}
|
|
|
|
try {
|
|
const id = parseInt(event.params.id)
|
|
if (isNaN(id)) {
|
|
return errorResponse('Invalid post ID', 400)
|
|
}
|
|
|
|
const data = await event.request.json()
|
|
|
|
// Concurrency control: require matching updatedAt if provided
|
|
if (data.updatedAt) {
|
|
const existing = await prisma.post.findUnique({ where: { id }, select: { updatedAt: true } })
|
|
if (!existing) return errorResponse('Post not found', 404)
|
|
const incoming = new Date(data.updatedAt)
|
|
if (existing.updatedAt.getTime() !== incoming.getTime()) {
|
|
return errorResponse('Conflict: post has changed', 409)
|
|
}
|
|
}
|
|
|
|
// Update publishedAt if status is changing to published
|
|
if (data.status === 'published') {
|
|
const currentPost = await prisma.post.findUnique({
|
|
where: { id },
|
|
select: { status: true, publishedAt: true }
|
|
})
|
|
|
|
if (currentPost && currentPost.status !== 'published' && !currentPost.publishedAt) {
|
|
data.publishedAt = new Date()
|
|
}
|
|
}
|
|
|
|
// Use content as-is (no special handling needed)
|
|
let featuredImageId = data.featuredImage
|
|
let postContent = data.content
|
|
|
|
const post = await prisma.post.update({
|
|
where: { id },
|
|
data: {
|
|
title: data.title,
|
|
slug: data.slug,
|
|
postType: data.type,
|
|
status: data.status,
|
|
content: postContent,
|
|
featuredImage: featuredImageId,
|
|
attachments:
|
|
data.attachedPhotos && data.attachedPhotos.length > 0 ? data.attachedPhotos : null,
|
|
tags: data.tags,
|
|
publishedAt: data.publishedAt
|
|
}
|
|
})
|
|
|
|
// Update media usage tracking
|
|
try {
|
|
// Remove all existing usage for this post first
|
|
await removeMediaUsage('post', id)
|
|
|
|
// Track all current media usage in the updated post
|
|
const usageReferences: MediaUsageReference[] = []
|
|
|
|
// Track featured image
|
|
const featuredImageIds = extractMediaIds(post, 'featuredImage')
|
|
featuredImageIds.forEach((mediaId) => {
|
|
usageReferences.push({
|
|
mediaId,
|
|
contentType: 'post',
|
|
contentId: id,
|
|
fieldName: 'featuredImage'
|
|
})
|
|
})
|
|
|
|
// Track attachments
|
|
const attachmentIds = extractMediaIds(post, 'attachments')
|
|
attachmentIds.forEach((mediaId) => {
|
|
usageReferences.push({
|
|
mediaId,
|
|
contentType: 'post',
|
|
contentId: id,
|
|
fieldName: 'attachments'
|
|
})
|
|
})
|
|
|
|
// Track media in post content
|
|
const contentIds = extractMediaIds(post, 'content')
|
|
contentIds.forEach((mediaId) => {
|
|
usageReferences.push({
|
|
mediaId,
|
|
contentType: 'post',
|
|
contentId: id,
|
|
fieldName: 'content'
|
|
})
|
|
})
|
|
|
|
// Add new usage references
|
|
if (usageReferences.length > 0) {
|
|
await trackMediaUsage(usageReferences)
|
|
}
|
|
} catch (error) {
|
|
logger.warn('Failed to update media usage for post', { postId: id, error })
|
|
}
|
|
|
|
logger.info('Post updated', { id })
|
|
return jsonResponse(post)
|
|
} catch (error) {
|
|
logger.error('Failed to update post', error as Error)
|
|
return errorResponse('Failed to update post', 500)
|
|
}
|
|
}
|
|
|
|
// PATCH /api/posts/[id] - Partially update a post
|
|
export const PATCH: RequestHandler = async (event) => {
|
|
if (!checkAdminAuth(event)) {
|
|
return errorResponse('Unauthorized', 401)
|
|
}
|
|
|
|
try {
|
|
const id = parseInt(event.params.id)
|
|
if (isNaN(id)) {
|
|
return errorResponse('Invalid post ID', 400)
|
|
}
|
|
|
|
const data = await event.request.json()
|
|
|
|
// Check for existence and concurrency
|
|
const existing = await prisma.post.findUnique({ where: { id } })
|
|
if (!existing) return errorResponse('Post not found', 404)
|
|
if (data.updatedAt) {
|
|
const incoming = new Date(data.updatedAt)
|
|
if (existing.updatedAt.getTime() !== incoming.getTime()) {
|
|
return errorResponse('Conflict: post has changed', 409)
|
|
}
|
|
}
|
|
|
|
const updateData: any = {}
|
|
|
|
if (data.status !== undefined) {
|
|
updateData.status = data.status
|
|
if (data.status === 'published' && !existing.publishedAt) {
|
|
updateData.publishedAt = new Date()
|
|
} else if (data.status === 'draft') {
|
|
updateData.publishedAt = null
|
|
}
|
|
}
|
|
if (data.title !== undefined) updateData.title = data.title
|
|
if (data.slug !== undefined) updateData.slug = data.slug
|
|
if (data.type !== undefined) updateData.postType = data.type
|
|
if (data.content !== undefined) updateData.content = data.content
|
|
if (data.featuredImage !== undefined) updateData.featuredImage = data.featuredImage
|
|
if (data.attachedPhotos !== undefined)
|
|
updateData.attachments =
|
|
data.attachedPhotos && data.attachedPhotos.length > 0 ? data.attachedPhotos : null
|
|
if (data.tags !== undefined) updateData.tags = data.tags
|
|
if (data.publishedAt !== undefined) updateData.publishedAt = data.publishedAt
|
|
|
|
const post = await prisma.post.update({ where: { id }, data: updateData })
|
|
|
|
logger.info('Post partially updated', { id: post.id, fields: Object.keys(updateData) })
|
|
return jsonResponse(post)
|
|
} catch (error) {
|
|
logger.error('Failed to partially update post', error as Error)
|
|
return errorResponse('Failed to update post', 500)
|
|
}
|
|
}
|
|
|
|
// DELETE /api/posts/[id] - Delete a post
|
|
export const DELETE: RequestHandler = async (event) => {
|
|
if (!checkAdminAuth(event)) {
|
|
return errorResponse('Unauthorized', 401)
|
|
}
|
|
|
|
try {
|
|
const id = parseInt(event.params.id)
|
|
if (isNaN(id)) {
|
|
return errorResponse('Invalid post ID', 400)
|
|
}
|
|
|
|
// Remove media usage tracking first
|
|
await removeMediaUsage('post', id)
|
|
|
|
// Delete the post
|
|
await prisma.post.delete({
|
|
where: { id }
|
|
})
|
|
|
|
logger.info('Post deleted', { id })
|
|
return jsonResponse({ message: 'Post deleted successfully' })
|
|
} catch (error) {
|
|
logger.error('Failed to delete post', error as Error)
|
|
return errorResponse('Failed to delete post', 500)
|
|
}
|
|
}
|