feat(api/server): add posts PATCH and optimistic concurrency (updatedAt) for posts and projects
This commit is contained in:
parent
3aec443534
commit
f5a440a2ca
2 changed files with 80 additions and 0 deletions
|
|
@ -52,6 +52,16 @@ export const PUT: RequestHandler = async (event) => {
|
|||
|
||||
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({
|
||||
|
|
@ -141,6 +151,60 @@ export const PUT: RequestHandler = async (event) => {
|
|||
}
|
||||
}
|
||||
|
||||
// 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)) {
|
||||
|
|
|
|||
|
|
@ -71,6 +71,14 @@ export const PUT: RequestHandler = async (event) => {
|
|||
slug = await ensureUniqueSlug(body.slug, 'project', id)
|
||||
}
|
||||
|
||||
// Concurrency control: if updatedAt provided, ensure it matches current
|
||||
if (body.updatedAt) {
|
||||
const incoming = new Date(body.updatedAt)
|
||||
if (existing.updatedAt.getTime() !== incoming.getTime()) {
|
||||
return errorResponse('Conflict: project has changed', 409)
|
||||
}
|
||||
}
|
||||
|
||||
// Update project
|
||||
const project = await prisma.project.update({
|
||||
where: { id },
|
||||
|
|
@ -197,6 +205,14 @@ export const PATCH: RequestHandler = async (event) => {
|
|||
return errorResponse('Project not found', 404)
|
||||
}
|
||||
|
||||
// Concurrency control: if updatedAt provided, ensure it matches current
|
||||
if (body.updatedAt) {
|
||||
const incoming = new Date(body.updatedAt)
|
||||
if (existing.updatedAt.getTime() !== incoming.getTime()) {
|
||||
return errorResponse('Conflict: project has changed', 409)
|
||||
}
|
||||
}
|
||||
|
||||
// Build update data object with only provided fields
|
||||
const updateData: any = {}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue