mirror of
https://github.com/mudabbir-ahmad/peworkshop.git
synced 2026-10-07 19:50:20 +00:00
fixed the map, made it so the map is scrollable and synced the timing between the admin page and map page. They now share the same elapsed time. need to fix map css for altering the svg.
Grey = cant click Red = current Green = complete blue = skipped? Need to add Clue page Need to fix Leaderboard.
This commit is contained in:
1 parent
e7ad53643c
commit
16319f1333
14 files changed
+791
-472
No files matched your search
@@ -1,70 +1,97 @@
|
||||
import { queryOne, run } from "@/lib/db";
|
||||
import { cookies } from "next/headers";
|
||||
import { verifyToken } from "@/app/api/auth/[...nextauth]/route";
|
||||
import { queryOne } from "@/lib/db";
|
||||
import { getToken } from "next-auth/jwt";
|
||||
|
||||
export const dynamic = "force-dynamic"; // Disable caching
|
||||
|
||||
// GET endpoint to retrieve a specific clue
|
||||
export async function GET(request, { params }) {
|
||||
const { clueId } = params;
|
||||
|
||||
export async function GET(request, context) {
|
||||
try {
|
||||
// Check if the hunt is active first
|
||||
const huntActiveSetting = await queryOne(
|
||||
"SELECT value FROM settings WHERE key = 'hunt_active'"
|
||||
);
|
||||
const huntActive = huntActiveSetting && huntActiveSetting.value === "true";
|
||||
const token = await getToken({
|
||||
req: request,
|
||||
secret:
|
||||
process.env.NEXTAUTH_SECRET ||
|
||||
"your-fallback-secret-should-be-at-least-32-chars",
|
||||
});
|
||||
|
||||
// Only verify authentication when hunt is not active
|
||||
if (!huntActive) {
|
||||
const cookieStore = await cookies();
|
||||
const sessionToken =
|
||||
cookieStore.get("next-auth.session-token")?.value ||
|
||||
cookieStore.get("__Secure-next-auth.session-token")?.value;
|
||||
|
||||
if (!sessionToken) {
|
||||
return Response.json(
|
||||
{ error: "Unauthorized", clue: null },
|
||||
{ status: 401, headers: { "Content-Type": "application/json" } }
|
||||
);
|
||||
}
|
||||
|
||||
const userData = verifyToken(sessionToken);
|
||||
if (!userData) {
|
||||
return Response.json(
|
||||
{ error: "Invalid session", clue: null },
|
||||
{ status: 401, headers: { "Content-Type": "application/json" } }
|
||||
);
|
||||
}
|
||||
|
||||
// Admin can see clues even if hunt is not active
|
||||
if (userData.role !== "admin") {
|
||||
return Response.json(
|
||||
{ error: "Hunt is not active", clue: null },
|
||||
{ headers: { "Content-Type": "application/json" } }
|
||||
);
|
||||
}
|
||||
if (!token) {
|
||||
return Response.json({ error: "Unauthorized" }, { status: 401 });
|
||||
}
|
||||
|
||||
// Fetch the specific clue
|
||||
const clue = await queryOne("SELECT * FROM clues WHERE id = ?", [clueId]);
|
||||
|
||||
if (!clue) {
|
||||
// Check if user has a team
|
||||
if (!token.teamId) {
|
||||
return Response.json(
|
||||
{ error: "Clue not found", clue: null },
|
||||
{ status: 404, headers: { "Content-Type": "application/json" } }
|
||||
{ error: "You must be part of a team to view clues" },
|
||||
{ status: 403 }
|
||||
);
|
||||
}
|
||||
|
||||
const { clueId } = context.params;
|
||||
|
||||
// Validate clueId is a number
|
||||
if (isNaN(parseInt(clueId))) {
|
||||
return Response.json({ error: "Invalid clue ID" }, { status: 400 });
|
||||
}
|
||||
|
||||
// Get the specific clue
|
||||
const clue = await queryOne(
|
||||
"SELECT id, title, description, location, x_coord, y_coord FROM clues WHERE id = ?",
|
||||
[clueId]
|
||||
);
|
||||
|
||||
if (!clue) {
|
||||
return Response.json({ error: "Clue not found" }, { status: 404 });
|
||||
}
|
||||
|
||||
// Check if this team has found this clue before
|
||||
const found = await queryOne(
|
||||
"SELECT * FROM team_clues WHERE team_id = ? AND clue_id = ?",
|
||||
[token.teamId, clueId]
|
||||
);
|
||||
|
||||
// If not found yet, mark it as found now
|
||||
if (!found) {
|
||||
try {
|
||||
// First ensure the team_clues table exists
|
||||
await queryOne(`
|
||||
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),
|
||||
FOREIGN KEY (clue_id) REFERENCES clues(id)
|
||||
)
|
||||
`);
|
||||
|
||||
// Record that the team found this clue
|
||||
await queryOne(
|
||||
"INSERT INTO team_clues (team_id, clue_id) VALUES (?, ?)",
|
||||
[token.teamId, clueId]
|
||||
);
|
||||
} catch (err) {
|
||||
console.error("Error marking clue as found:", err);
|
||||
// Continue even if there's an error recording the find
|
||||
}
|
||||
}
|
||||
|
||||
return Response.json(
|
||||
{ clue },
|
||||
{ headers: { "Content-Type": "application/json" } }
|
||||
{
|
||||
...clue,
|
||||
found: !!found,
|
||||
foundNow: !found,
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
"Cache-Control": "no-store, no-cache, must-revalidate",
|
||||
Pragma: "no-cache",
|
||||
Expires: "0",
|
||||
},
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("Error fetching clue details:", error);
|
||||
console.error("Error fetching clue:", error);
|
||||
return Response.json(
|
||||
{ error: "Failed to fetch clue details: " + error.message, clue: null },
|
||||
{ status: 500, headers: { "Content-Type": "application/json" } }
|
||||
{ error: "Failed to fetch clue: " + error.message },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
+43
-107
@@ -1,127 +1,63 @@
|
||||
import { query, queryOne, run } from "@/lib/db";
|
||||
import { cookies } from "next/headers";
|
||||
import { verifyToken } from "@/app/api/auth/[...nextauth]/route";
|
||||
import { query, queryOne } from "@/lib/db";
|
||||
import { getToken } from "next-auth/jwt";
|
||||
|
||||
export const dynamic = "force-dynamic"; // Disable caching
|
||||
|
||||
// Helper function to ensure the clues table exists
|
||||
async function ensureCluesTableExists() {
|
||||
try {
|
||||
await run(`
|
||||
CREATE TABLE IF NOT EXISTS clues (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
title TEXT NOT NULL,
|
||||
description TEXT NOT NULL,
|
||||
location TEXT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
`);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error("Error ensuring clues table exists:", error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to extract numeric value from clue title
|
||||
function extractClueNumber(title) {
|
||||
// Look for patterns like "Clue 1", "Clue 10", etc.
|
||||
const match = title.match(/Clue\s+(\d+)/i);
|
||||
// If we find a match, return the number, otherwise return Infinity (to sort to the end)
|
||||
return match ? parseInt(match[1], 10) : Infinity;
|
||||
}
|
||||
|
||||
// GET endpoint to retrieve all clues
|
||||
export async function GET(request) {
|
||||
try {
|
||||
// First check if the hunt is active
|
||||
const huntActiveSetting = await queryOne(
|
||||
"SELECT value FROM settings WHERE key = 'hunt_active'"
|
||||
);
|
||||
const huntActive = huntActiveSetting && huntActiveSetting.value === "true";
|
||||
const token = await getToken({
|
||||
req: request,
|
||||
secret:
|
||||
process.env.NEXTAUTH_SECRET ||
|
||||
"your-fallback-secret-should-be-at-least-32-chars",
|
||||
});
|
||||
|
||||
// If hunt is active, allow access without authentication
|
||||
if (!huntActive) {
|
||||
// Only verify authentication when hunt is not active
|
||||
const cookieStore = await cookies();
|
||||
const sessionToken =
|
||||
cookieStore.get("next-auth.session-token")?.value ||
|
||||
cookieStore.get("__Secure-next-auth.session-token")?.value;
|
||||
|
||||
if (!sessionToken) {
|
||||
return Response.json(
|
||||
{ error: "Unauthorized", clues: [] },
|
||||
{ status: 401, headers: { "Content-Type": "application/json" } }
|
||||
);
|
||||
}
|
||||
|
||||
const userData = verifyToken(sessionToken);
|
||||
if (!userData) {
|
||||
return Response.json(
|
||||
{ error: "Invalid session", clues: [] },
|
||||
{ status: 401, headers: { "Content-Type": "application/json" } }
|
||||
);
|
||||
}
|
||||
|
||||
// Admin can see clues even if hunt is not active
|
||||
if (userData.role !== "admin") {
|
||||
return Response.json(
|
||||
{ error: "Hunt is not active", clues: [] },
|
||||
{ headers: { "Content-Type": "application/json" } }
|
||||
);
|
||||
}
|
||||
if (!token) {
|
||||
return Response.json({ error: "Unauthorized" }, { status: 401 });
|
||||
}
|
||||
|
||||
// Ensure the clues table exists
|
||||
await ensureCluesTableExists();
|
||||
|
||||
// Fetch all clues without sorting in SQL
|
||||
const clues = await query("SELECT * FROM clues");
|
||||
|
||||
// If no clues are found, insert sample clues (for testing purposes)
|
||||
if (!clues || clues.length === 0) {
|
||||
// Insert sample clues
|
||||
await run(
|
||||
"INSERT INTO clues (title, description, location) VALUES (?, ?, ?)",
|
||||
["Clue 1", "This is the first clue description", "Location 1"]
|
||||
);
|
||||
await run(
|
||||
"INSERT INTO clues (title, description, location) VALUES (?, ?, ?)",
|
||||
["Clue 2", "This is the second clue description", "Location 2"]
|
||||
);
|
||||
await run(
|
||||
"INSERT INTO clues (title, description, location) VALUES (?, ?, ?)",
|
||||
["Clue 3", "This is the final clue description", "Location 3"]
|
||||
);
|
||||
|
||||
// Fetch the inserted clues
|
||||
const sampleClues = await query("SELECT * FROM clues");
|
||||
|
||||
// Sort clues based on numbering in title
|
||||
sampleClues.sort(
|
||||
(a, b) => extractClueNumber(a.title) - extractClueNumber(b.title)
|
||||
);
|
||||
|
||||
// Check if user has a team
|
||||
if (!token.teamId) {
|
||||
return Response.json(
|
||||
{ clues: sampleClues },
|
||||
{ headers: { "Content-Type": "application/json" } }
|
||||
{ error: "You must be part of a team to view clues" },
|
||||
{ status: 403 }
|
||||
);
|
||||
}
|
||||
|
||||
// Sort clues based on numbering in title
|
||||
clues.sort(
|
||||
(a, b) => extractClueNumber(a.title) - extractClueNumber(b.title)
|
||||
// Get all clues
|
||||
const clues = await query(`
|
||||
SELECT id, title, description, location, x_coord, y_coord
|
||||
FROM clues
|
||||
ORDER BY id ASC
|
||||
`);
|
||||
|
||||
// For each clue, check if the team has found it
|
||||
const cluesWithStatus = await Promise.all(
|
||||
clues.map(async (clue) => {
|
||||
const found = await queryOne(
|
||||
"SELECT 1 FROM team_clues WHERE team_id = ? AND clue_id = ?",
|
||||
[token.teamId, clue.id]
|
||||
);
|
||||
|
||||
return {
|
||||
...clue,
|
||||
found: !!found,
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
return Response.json(
|
||||
{ clues },
|
||||
{ headers: { "Content-Type": "application/json" } }
|
||||
);
|
||||
return Response.json(cluesWithStatus, {
|
||||
headers: {
|
||||
"Cache-Control": "no-store, no-cache, must-revalidate",
|
||||
Pragma: "no-cache",
|
||||
Expires: "0",
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error fetching clues:", error);
|
||||
return Response.json(
|
||||
{ error: "Failed to fetch clues: " + error.message, clues: [] },
|
||||
{ status: 500, headers: { "Content-Type": "application/json" } }
|
||||
{ error: "Failed to fetch clues: " + error.message },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user