feat: ability to edit suggestions when accepting

This commit is contained in:
2026-02-04 21:37:57 -08:00
parent 729f410443
commit 800b9f6c2d
5 changed files with 383 additions and 11 deletions
+34
View File
@@ -137,6 +137,40 @@ export default async function (app: FastifyInstance): Promise<void> {
}
);
// Accept a suggestion with edits (admin only)
app.put<{ Params: { id: string }; Body: any }>(
"/:id/accept-with-edits",
{
preHandler: [app.authenticate, adminGuard, app.csrfProtection],
},
async (request, reply) => {
const { id } = request.params;
const editedData = request.body;
try {
const suggestion = await SuggestionService.acceptSuggestionWithEdits(id, editedData);
await AuditService.log(
{
action: AuditAction.ENTRY_UPDATE,
category: AuditCategory.ADMIN,
resourceType: "Suggestion",
resourceId: suggestion.id,
details: `Accepted ${suggestion.entityType} suggestion with edits: ${suggestion.title}`,
success: true,
},
request
);
reply.send(suggestion);
} catch (error) {
return reply.badRequest(
error instanceof Error ? error.message : "Failed to accept suggestion with edits"
);
}
}
);
// Decline a suggestion (admin only)
app.put<{ Params: { id: string }; Body: DeclineSuggestionDto }>(
"/:id/decline",
@@ -340,6 +340,95 @@ export const SuggestionService = {
return mapSuggestion(updatedSuggestion);
},
async acceptSuggestionWithEdits(id: string, editedData: any): Promise<Suggestion> {
const suggestion = await prisma.suggestion.findUnique({
where: { id },
include: { user: true },
});
if (!suggestion) {
throw new Error("Suggestion not found");
}
if (suggestion.status !== "UNREVIEWED") {
throw new Error("Suggestion has already been reviewed");
}
// Create the entity based on type with edited data
switch (suggestion.entityType) {
case "GAME": {
await gameService.createGame({
title: editedData.title || suggestion.title,
platform: editedData.platform,
status: GameStatus.backlog,
notes: editedData.notes,
coverImage: editedData.coverImage,
});
break;
}
case "BOOK": {
await bookService.createBook({
title: editedData.title || suggestion.title,
author: editedData.author || "Unknown",
isbn: editedData.isbn,
status: BookStatus.toRead,
notes: editedData.notes,
coverImage: editedData.coverImage,
});
break;
}
case "MUSIC": {
await musicService.createMusic({
title: editedData.title || suggestion.title,
artist: editedData.artist || "Unknown",
type: getMusicType(editedData.type),
status: MusicStatus.wantToListen,
notes: editedData.notes,
coverArt: editedData.coverArt || editedData.coverImage,
});
break;
}
case "ART": {
await artService.createArt({
title: editedData.title || suggestion.title,
artist: editedData.artist || "Unknown",
description: editedData.description,
imageUrl: editedData.imageUrl || "",
});
break;
}
case "SHOW": {
await showService.createShow({
title: editedData.title || suggestion.title,
type: getShowType(editedData.type),
status: ShowStatus.wantToWatch,
notes: editedData.notes,
coverImage: editedData.coverImage,
});
break;
}
case "MANGA": {
await mangaService.createManga({
title: editedData.title || suggestion.title,
author: editedData.author || "Unknown",
status: MangaStatus.wantToRead,
notes: editedData.notes,
coverImage: editedData.coverImage,
});
break;
}
}
// Update suggestion status
const updatedSuggestion = await prisma.suggestion.update({
where: { id },
data: { status: "ACCEPTED" },
include: { user: true },
});
return mapSuggestion(updatedSuggestion);
},
async declineSuggestion(id: string, reason?: string): Promise<Suggestion> {
const suggestion = await prisma.suggestion.findUnique({
where: { id },