mirror of
https://github.com/mudabbir-ahmad/peworkshop.git
synced 2026-10-08 03:50:21 +00:00
bug fixes and added stats viewer to admin leaderboard.
This commit is contained in:
1 parent
7075a816c3
commit
d60f52fef3
7 files changed
+1046
-233
No files matched your search
@@ -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 }
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user