Files
prompts.chat/src/app/api/prompts/[id]/route.ts
T

275 lines
7.5 KiB
TypeScript

import { NextResponse } from "next/server";
import { z } from "zod";
import { auth } from "@/lib/auth";
import { db } from "@/lib/db";
import { generatePromptEmbedding } from "@/lib/ai/embeddings";
import { generatePromptSlug } from "@/lib/slug";
const updatePromptSchema = z.object({
title: z.string().min(1).max(200).optional(),
description: z.string().max(500).optional(),
content: z.string().min(1).optional(),
type: z.enum(["TEXT", "IMAGE", "VIDEO", "AUDIO"]).optional(), // Output type only
structuredFormat: z.enum(["JSON", "YAML"]).optional().nullable(),
categoryId: z.string().optional().nullable(),
tagIds: z.array(z.string()).optional(),
contributorIds: z.array(z.string()).optional(),
isPrivate: z.boolean().optional(),
mediaUrl: z.string().url().optional().or(z.literal("")).nullable(),
requiresMediaUpload: z.boolean().optional(),
requiredMediaType: z.enum(["IMAGE", "VIDEO", "DOCUMENT"]).optional().nullable(),
requiredMediaCount: z.number().int().min(1).max(10).optional().nullable(),
});
// Get single prompt
export async function GET(
request: Request,
{ params }: { params: Promise<{ id: string }> }
) {
try {
const { id } = await params;
const session = await auth();
const prompt = await db.prompt.findUnique({
where: { id },
include: {
author: {
select: {
id: true,
name: true,
username: true,
avatar: true,
},
},
category: true,
tags: {
include: {
tag: true,
},
},
versions: {
orderBy: { version: "desc" },
take: 10,
},
},
});
if (!prompt || prompt.deletedAt) {
return NextResponse.json(
{ error: "not_found", message: "Prompt not found" },
{ status: 404 }
);
}
// Check if user can view private prompt
if (prompt.isPrivate && prompt.authorId !== session?.user?.id) {
return NextResponse.json(
{ error: "forbidden", message: "This prompt is private" },
{ status: 403 }
);
}
return NextResponse.json(prompt);
} catch (error) {
console.error("Get prompt error:", error);
return NextResponse.json(
{ error: "server_error", message: "Something went wrong" },
{ status: 500 }
);
}
}
// Update prompt
export async function PATCH(
request: Request,
{ params }: { params: Promise<{ id: string }> }
) {
try {
const { id } = await params;
const session = await auth();
if (!session?.user) {
return NextResponse.json(
{ error: "unauthorized", message: "You must be logged in" },
{ status: 401 }
);
}
// Check if prompt exists and user owns it
const existing = await db.prompt.findUnique({
where: { id },
select: { authorId: true, content: true },
});
if (!existing) {
return NextResponse.json(
{ error: "not_found", message: "Prompt not found" },
{ status: 404 }
);
}
if (existing.authorId !== session.user.id && session.user.role !== "ADMIN") {
return NextResponse.json(
{ error: "forbidden", message: "You can only edit your own prompts" },
{ status: 403 }
);
}
const body = await request.json();
const parsed = updatePromptSchema.safeParse(body);
if (!parsed.success) {
return NextResponse.json(
{ error: "validation_error", message: "Invalid input", details: parsed.error.issues },
{ status: 400 }
);
}
const { tagIds, contributorIds, categoryId, mediaUrl, title, ...data } = parsed.data;
// Regenerate slug if title changed
let newSlug: string | undefined;
if (title) {
newSlug = await generatePromptSlug(title);
}
// Convert empty strings to null for optional foreign keys
const cleanedData = {
...data,
...(title && { title }),
...(newSlug && { slug: newSlug }),
...(categoryId !== undefined && { categoryId: categoryId || null }),
...(mediaUrl !== undefined && { mediaUrl: mediaUrl || null }),
};
// Update prompt
const prompt = await db.prompt.update({
where: { id },
data: {
...cleanedData,
...(tagIds && {
tags: {
deleteMany: {},
create: tagIds.map((tagId) => ({ tagId })),
},
}),
...(contributorIds !== undefined && {
contributors: {
set: contributorIds.map((id) => ({ id })),
},
}),
},
include: {
author: {
select: {
id: true,
name: true,
username: true,
},
},
category: true,
tags: {
include: {
tag: true,
},
},
},
});
// Create new version if content changed
if (data.content && data.content !== existing.content) {
const latestVersion = await db.promptVersion.findFirst({
where: { promptId: id },
orderBy: { version: "desc" },
});
await db.promptVersion.create({
data: {
promptId: id,
version: (latestVersion?.version || 0) + 1,
content: data.content,
changeNote: "Content updated",
createdBy: session.user.id,
},
});
}
// Regenerate embedding if content, title, or description changed (non-blocking)
// Only for public prompts - the function checks if aiSearch is enabled
const contentChanged = data.content || title || data.description !== undefined;
if (contentChanged && !prompt.isPrivate) {
generatePromptEmbedding(id).catch((err) =>
console.error("Failed to regenerate embedding for prompt:", id, err)
);
}
return NextResponse.json(prompt);
} catch (error) {
console.error("Update prompt error:", error);
return NextResponse.json(
{ error: "server_error", message: "Something went wrong" },
{ status: 500 }
);
}
}
// Soft delete prompt (admin only - CC0 prompts cannot be deleted by users)
export async function DELETE(
request: Request,
{ params }: { params: Promise<{ id: string }> }
) {
try {
const { id } = await params;
const session = await auth();
if (!session?.user) {
return NextResponse.json(
{ error: "unauthorized", message: "You must be logged in" },
{ status: 401 }
);
}
// Only admins can soft-delete prompts (CC0 content is public domain)
if (session.user.role !== "ADMIN") {
return NextResponse.json(
{ error: "forbidden", message: "Prompts are released under CC0 and cannot be deleted. Contact an admin if there is an issue." },
{ status: 403 }
);
}
// Check if prompt exists
const existing = await db.prompt.findUnique({
where: { id },
select: { id: true, deletedAt: true },
});
if (!existing) {
return NextResponse.json(
{ error: "not_found", message: "Prompt not found" },
{ status: 404 }
);
}
if (existing.deletedAt) {
return NextResponse.json(
{ error: "already_deleted", message: "Prompt is already deleted" },
{ status: 400 }
);
}
// Soft delete by setting deletedAt timestamp
await db.prompt.update({
where: { id },
data: { deletedAt: new Date() },
});
return NextResponse.json({ success: true, message: "Prompt soft deleted" });
} catch (error) {
console.error("Delete prompt error:", error);
return NextResponse.json(
{ error: "server_error", message: "Something went wrong" },
{ status: 500 }
);
}
}