import { getToken } from "next-auth/jwt"; import { queryOne, query, run } from "@/lib/db"; export const dynamic = "force-dynamic"; // Disable caching // Ensure the user is an admin async function verifyAdmin(request) { const token = await getToken({ req: request, secret: process.env.NEXTAUTH_SECRET || "your-fallback-secret-should-be-at-least-32-chars", }); if (!token) { return { authorized: false, error: "Unauthorized", status: 401 }; } if (token.role !== "admin") { return { authorized: false, error: "Admin access required", status: 403 }; } return { authorized: true, token }; } // DELETE - Remove a specific clue export async function DELETE(request, context) { try { // Verify admin access const { authorized, error, status } = await verifyAdmin(request); if (!authorized) { return Response.json({ error }, { status }); } // Get clueId parameter - properly awaited const { clueId } = await context.params; // Validate clueId is a number if (isNaN(parseInt(clueId))) { return Response.json({ error: "Invalid clue ID" }, { status: 400 }); } // Always set shouldRenumber to true - no longer read from request body const shouldRenumber = true; // Check if clue exists and get its QR code value const clue = await queryOne("SELECT id, qr_code FROM clues WHERE id = ?", [ clueId, ]); if (!clue) { return Response.json({ error: "Clue not found" }, { status: 404 }); } console.log( `Starting deletion of clue ${clueId} with QR code ${clue.qr_code}` ); // Start a transaction for atomicity try { await run("BEGIN TRANSACTION"); console.log("Transaction started"); // First, make sure the team_clues table exists and clear any references await run(` CREATE TABLE IF NOT EXISTS team_clues ( team_id INTEGER NOT NULL, clue_id INTEGER NOT NULL, found_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (team_id, clue_id), FOREIGN KEY (team_id) REFERENCES teams(id) ON DELETE CASCADE, FOREIGN KEY (clue_id) REFERENCES clues(id) ON DELETE CASCADE ) `); // Delete any references in team_clues table const deleteTeamClues = await run( "DELETE FROM team_clues WHERE clue_id = ?", [clueId] ); console.log( `Deleted team clues result: ${deleteTeamClues.changes} references cleared` ); // Delete clue record const deleteResult = await run("DELETE FROM clues WHERE id = ?", [ clueId, ]); console.log(`Deleted clue result: ${deleteResult.changes} rows affected`); if (deleteResult.changes === 0) { throw new Error(`Clue ${clueId} was not deleted`); } // If renumbering is enabled (which is always true now) if (shouldRenumber) { // Always use the sequential renumbering approach // This ensures QR codes are sequential regardless of DB IDs console.log("Renumbering all QR codes sequentially"); // Get all remaining clues ordered by ID const allClues = await query("SELECT id FROM clues ORDER BY id ASC"); console.log(`Found ${allClues.length} clues to renumber`); // Update each clue to have sequential QR[index+1] format for (let i = 0; i < allClues.length; i++) { const clue = allClues[i]; const sequentialQrCode = `QR${i + 1}`; const updateResult = await run( "UPDATE clues SET qr_code = ? WHERE id = ?", [sequentialQrCode, clue.id] ); console.log( `Updated clue ${clue.id} to ${sequentialQrCode}: ${updateResult.changes} rows affected` ); } } // Commit the transaction await run("COMMIT"); console.log("Transaction committed successfully"); return Response.json({ success: true, message: "Clue deleted successfully", renumbered: shouldRenumber, }); } catch (transactionError) { // If any error occurs, roll back the transaction console.error("Error during deletion transaction:", transactionError); try { await run("ROLLBACK"); console.log("Transaction rolled back"); } catch (rollbackError) { console.error("Error while rolling back transaction:", rollbackError); } // Provide more specific error message based on the error type let errorMessage = "Failed to delete clue: " + transactionError.message; if (transactionError.message.includes("SQLITE_CONSTRAINT")) { errorMessage = "Database constraint violation. The clue may be referenced by other records."; } return Response.json({ error: errorMessage }, { status: 500 }); } } catch (error) { console.error("Admin clue deletion error:", error); return Response.json( { error: "Failed to delete clue: " + error.message }, { status: 500 } ); } } // GET - Get a specific clue export async function GET(request, context) { try { // Verify admin access const { authorized, error, status } = await verifyAdmin(request); if (!authorized) { return Response.json({ error }, { status }); } // Get clueId parameter - properly awaited const { clueId } = await context.params; // Validate clueId is a number if (isNaN(parseInt(clueId))) { return Response.json({ error: "Invalid clue ID" }, { status: 400 }); } // Get the specific clue - now including qr_code const clue = await queryOne( "SELECT id, title, description, location, qr_code FROM clues WHERE id = ?", [clueId] ); if (!clue) { return Response.json({ error: "Clue not found" }, { status: 404 }); } return Response.json(clue); } catch (error) { console.error("Admin clue fetch error:", error); return Response.json( { error: "Failed to fetch clue: " + error.message }, { status: 500 } ); } } // PUT - Update a specific clue export async function PUT(request, context) { try { // Verify admin access const { authorized, error, status } = await verifyAdmin(request); if (!authorized) { return Response.json({ error }, { status }); } // Get clueId parameter - properly awaited const { clueId } = await context.params; // Validate clueId is a number if (isNaN(parseInt(clueId))) { return Response.json({ error: "Invalid clue ID" }, { status: 400 }); } // Parse the request body const { title, description, location, qr_code } = await request.json(); // Validate required fields if (!title || !description) { return Response.json( { error: "Title and description are required" }, { status: 400 } ); } // Validate QR code format if provided if (qr_code && !qr_code.match(/^QR\d+$/i)) { return Response.json( { error: "QR code must follow the format QR[number], e.g. QR1, QR2" }, { status: 400 } ); } // Check if clue exists const clue = await queryOne("SELECT id, qr_code FROM clues WHERE id = ?", [ clueId, ]); if (!clue) { return Response.json({ error: "Clue not found" }, { status: 404 }); } // Allow custom QR code - no longer enforcing QR{id} format const finalQrCode = qr_code || `QR${clueId}`; // Update the clue with the provided or default QR code await run( ` UPDATE clues SET title = ?, description = ?, location = ?, qr_code = ? WHERE id = ? `, [title, description, location || "", finalQrCode, clueId] ); return Response.json({ success: true, message: "Clue updated successfully", }); } catch (error) { console.error("Admin clue update error:", error); return Response.json( { error: "Failed to update clue: " + error.message }, { status: 500 } ); } }