import type { FastifyInstance } from "fastify"; import { SuggestionService } from "../../services/suggestion.service"; import { AuditService } from "../../services/audit.service"; import { AchievementService } from "../../services/achievement.service"; import { AuditAction, AuditCategory, AchievementCategory } from "@library/shared-types"; import type { SuggestionStatus, SuggestionEntity, CreateSuggestionDto, DeclineSuggestionDto, AcceptWithEditsDto, } from "@library/shared-types"; import { adminGuard } from "../../middleware/admin-guard"; import { bannedGuard } from "../../middleware/banned-guard"; export default async function (app: FastifyInstance): Promise { // Get all suggestions (admin only) app.get<{ Querystring: { status?: SuggestionStatus; entityType?: SuggestionEntity }; }>( "/", { preHandler: [app.authenticate, adminGuard], }, async (request, reply) => { const { status, entityType } = request.query; const suggestions = await SuggestionService.getAllSuggestions({ status, entityType, }); reply.send(suggestions); } ); // Get current user's suggestions app.get( "/my", { preHandler: [app.authenticate], }, async (request, reply) => { const userId = request.user.id; const suggestions = await SuggestionService.getUserSuggestions(userId); reply.send(suggestions); } ); // Get a single suggestion by ID app.get<{ Params: { id: string } }>( "/:id", { preHandler: [app.authenticate], }, async (request, reply) => { const { id } = request.params; const suggestion = await SuggestionService.getSuggestionById(id); if (!suggestion) { return reply.notFound("Suggestion not found"); } // Non-admins can only view their own suggestions if (!request.user.isAdmin && suggestion.userId !== request.user.id) { return reply.forbidden("You can only view your own suggestions"); } reply.send(suggestion); } ); // Create a new suggestion (any authenticated non-banned user) app.post<{ Body: CreateSuggestionDto }>( "/", { preHandler: [app.authenticate, bannedGuard, app.csrfProtection], }, async (request, reply) => { const userId = request.user.id; try { const suggestion = await SuggestionService.createSuggestion( userId, request.body ); await AuditService.logFromRequest(request, { action: AuditAction.entryCreate, category: AuditCategory.content, resourceType: "Suggestion", resourceId: suggestion.id, details: `Created ${suggestion.entityType} suggestion: ${suggestion.title}`, success: true, }); // Check for suggestion achievements const achievementService = new AchievementService(); await achievementService.checkAchievements( userId, AchievementCategory.Suggestion, request ); reply.send(suggestion); } catch (error) { return reply.badRequest( error instanceof Error ? error.message : "Failed to create suggestion" ); } } ); // Accept a suggestion (admin only) app.put<{ Params: { id: string } }>( "/:id/accept", { preHandler: [app.authenticate, adminGuard, app.csrfProtection], }, async (request, reply) => { const { id } = request.params; try { const suggestion = await SuggestionService.acceptSuggestion(id); await AuditService.logFromRequest(request, { action: AuditAction.entryUpdate, category: AuditCategory.admin, resourceType: "Suggestion", resourceId: suggestion.id, details: `Accepted ${suggestion.entityType} suggestion: ${suggestion.title}`, success: true, }); // Check for suggestion achievements for the user who made the suggestion const achievementService = new AchievementService(); await achievementService.checkAchievements( suggestion.userId, AchievementCategory.Suggestion, request ); reply.send(suggestion); } catch (error) { return reply.badRequest( error instanceof Error ? error.message : "Failed to accept suggestion" ); } } ); // Accept a suggestion with edits (admin only) app.put<{ Params: { id: string }; Body: AcceptWithEditsDto }>( "/: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.logFromRequest(request, { action: AuditAction.entryUpdate, category: AuditCategory.admin, resourceType: "Suggestion", resourceId: suggestion.id, details: `Accepted ${suggestion.entityType} suggestion with edits: ${suggestion.title}`, success: true, }); // Check for suggestion achievements for the user who made the suggestion const achievementService = new AchievementService(); await achievementService.checkAchievements( suggestion.userId, AchievementCategory.Suggestion, 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", { preHandler: [app.authenticate, adminGuard, app.csrfProtection], }, async (request, reply) => { const { id } = request.params; const { reason } = request.body; try { const suggestion = await SuggestionService.declineSuggestion(id, reason); await AuditService.logFromRequest(request, { action: AuditAction.entryUpdate, category: AuditCategory.admin, resourceType: "Suggestion", resourceId: suggestion.id, details: `Declined ${suggestion.entityType} suggestion: ${suggestion.title}${reason ? ` (Reason: ${reason})` : ""}`, success: true, }); reply.send(suggestion); } catch (error) { return reply.badRequest( error instanceof Error ? error.message : "Failed to decline suggestion" ); } } ); // Delete a suggestion (owner or admin only, only if unreviewed) app.delete<{ Params: { id: string } }>( "/:id", { preHandler: [app.authenticate, app.csrfProtection], }, async (request, reply) => { const { id } = request.params; const userId = request.user.id; const isAdmin = request.user.isAdmin; try { const suggestion = await SuggestionService.deleteSuggestion(id, userId, isAdmin); await AuditService.logFromRequest(request, { action: AuditAction.entryDelete, category: isAdmin ? AuditCategory.admin : AuditCategory.content, resourceType: "Suggestion", resourceId: suggestion.id, details: `Deleted ${suggestion.entityType} suggestion: ${suggestion.title}`, success: true, }); reply.send({ success: true }); } catch (error) { return reply.badRequest( error instanceof Error ? error.message : "Failed to delete suggestion" ); } } ); }