bug fixes and added stats viewer to admin leaderboard.

This commit is contained in:
bobbert committed 2025-03-20 19:19:28 +00:00
1 parent 7075a816c3
commit d60f52fef3
7 files changed
+1046 -233

No files matched your search

+238
View File
@@ -0,0 +1,238 @@
import { getToken } from "next-auth/jwt";
import { query, queryOne, 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 };
}
// POST - Reorder a clue to a new position
export async function POST(request) {
try {
// Verify admin access
const { authorized, error, status } = await verifyAdmin(request);
if (!authorized) {
return Response.json({ error }, { status });
}
// Parse the request body
const { clueId, newPosition } = await request.json();
// Validate required parameters
if (!clueId || !newPosition) {
return Response.json(
{ error: "Clue ID and new position are required" },
{ status: 400 }
);
}
// Validate parameters are numbers
if (isNaN(parseInt(clueId)) || isNaN(parseInt(newPosition))) {
return Response.json(
{ error: "Clue ID and new position must be numbers" },
{ status: 400 }
);
}
// Get all clues to validate new position is in range
const allClues = await query(
"SELECT id, qr_code FROM clues ORDER BY id ASC"
);
console.log("All clues before reordering:", allClues);
if (parseInt(newPosition) < 1 || parseInt(newPosition) > allClues.length) {
return Response.json(
{ error: `New position must be between 1 and ${allClues.length}` },
{ status: 400 }
);
}
// Check if clue exists
const clue = await queryOne("SELECT id, title FROM clues WHERE id = ?", [
clueId,
]);
if (!clue) {
return Response.json({ error: "Clue not found" }, { status: 404 });
}
// Start a transaction for the reordering operation
await run("BEGIN TRANSACTION");
try {
// Get current position (1-based index)
const currentPosition =
allClues.findIndex((c) => c.id === parseInt(clueId)) + 1;
console.log(
`Reordering clue ${clueId} from position ${currentPosition} to ${newPosition}`
);
if (currentPosition === parseInt(newPosition)) {
// No change needed, clue is already in the desired position
await run("ROLLBACK");
return Response.json({
success: true,
message: "Clue is already in the requested position",
});
}
// Create a temporary table with all clue data
await run(`
CREATE TEMPORARY TABLE temp_clues AS
SELECT id, title, description, location, qr_code, created_at
FROM clues
ORDER BY id ASC
`);
// Create a new array with the reordered clues
const reorderedClues = [...allClues];
const [clueToMove] = reorderedClues.splice(currentPosition - 1, 1);
reorderedClues.splice(parseInt(newPosition) - 1, 0, clueToMove);
console.log(
"Reordered clues array:",
reorderedClues.map((c) => c.id)
);
// Create ID mapping before deleting anything
const idMapping = {};
for (let i = 0; i < reorderedClues.length; i++) {
idMapping[reorderedClues[i].id] = i + 1; // New sequential IDs
}
console.log("ID Mapping:", idMapping);
// Get all team_clues entries before deleting
const teamClues = await query(
"SELECT team_id, clue_id, found_at, skipped FROM team_clues"
);
console.log(`Found ${teamClues.length} team_clues records to update`);
// Reset SQLite sequence before deletion to avoid autoincrement issues
await run("DELETE FROM sqlite_sequence WHERE name='clues'");
// Clear the clues table
await run("DELETE FROM clues");
console.log("Deleted all clues for reordering");
// Insert the clues back in their new order with new IDs
for (let i = 0; i < reorderedClues.length; i++) {
const originalClue = reorderedClues[i];
// Get the full clue data from the temp table
const clueData = await queryOne(
`
SELECT title, description, location, created_at FROM temp_clues WHERE id = ?
`,
[originalClue.id]
);
if (!clueData) {
throw new Error(
`Failed to retrieve data for clue ID ${originalClue.id} from temp table`
);
}
// Determine the new QR code based on new position
const newQrCode = `QR${i + 1}`;
const newId = i + 1;
// Insert with the new ID (position)
const insertResult = await run(
`
INSERT INTO clues (id, title, description, location, qr_code, created_at)
VALUES (?, ?, ?, ?, ?, ?)
`,
[
newId,
clueData.title,
clueData.description,
clueData.location || "",
newQrCode,
clueData.created_at,
]
);
console.log(
`Inserted clue with new ID ${newId}, was previously ${originalClue.id}, result:`,
insertResult
);
}
// Delete all team_clues entries
await run("DELETE FROM team_clues");
console.log("Deleted all team_clues records for updating");
// Reinsert with updated clue_id values
let updatedCount = 0;
for (const tc of teamClues) {
if (idMapping[tc.clue_id]) {
await run(
`
INSERT INTO team_clues (team_id, clue_id, found_at, skipped)
VALUES (?, ?, ?, ?)
`,
[tc.team_id, idMapping[tc.clue_id], tc.found_at, tc.skipped]
);
updatedCount++;
}
}
console.log(
`Reinserted ${updatedCount} team_clues records with updated IDs`
);
// Drop the temporary table
await run("DROP TABLE temp_clues");
// Update SQLite sequence to match our new highest ID
await run(`INSERT INTO sqlite_sequence (name, seq) VALUES ('clues', ${reorderedClues.length})
ON CONFLICT(name) DO UPDATE SET seq = ${reorderedClues.length}`);
// Force SQLite to update all internal references
await run("VACUUM");
// Commit transaction
await run("COMMIT");
console.log("Transaction committed successfully");
// Verify the changes
const verifyClues = await query(
"SELECT id, title FROM clues ORDER BY id ASC"
);
console.log("Clues after reordering:", verifyClues);
return Response.json({
success: true,
message: `Clue successfully moved from position ${currentPosition} to position ${newPosition}`,
});
} catch (error) {
// Rollback transaction on error
await run("ROLLBACK");
console.error("Error during reordering transaction:", error);
throw error;
}
} catch (error) {
console.error("Error reordering clue:", error);
return Response.json(
{ error: "Failed to reorder clue: " + error.message },
{ status: 500 }
);
}
}