Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"snyk.advanced.autoSelectOrganization": true
}
57 changes: 57 additions & 0 deletions src/app/api/delete-year/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/***************************************************************
*
* /api/delete-year/route.ts
*
* Author: Anne & Zander
* Date: 2/20/2026
*
* Summary: API to delete all data for a given year
*
**************************************************************/

import { NextRequest, NextResponse } from "next/server";
import { db } from "@/lib/db";
import {
projects,
yearlySchoolParticipation,
yearlyTeacherParticipation,
} from "@/lib/schema";
import { eq } from "drizzle-orm";

/**
* Deletes all data for the specified year.
*
* @param req NextRequest object
* @returns JSON response with success or error
*/
export async function DELETE(req: NextRequest) {
try {
const { searchParams } = new URL(req.url);
const yearString = searchParams.get("year");
const currentYear = Number(yearString);

if (!currentYear || isNaN(currentYear)) {
return NextResponse.json(
{ message: "Invalid year parameter" },
{ status: 400 },
);
}

// Delete rows for the specified year
await db.delete(projects).where(eq(projects.year, currentYear));
await db
.delete(yearlySchoolParticipation)
.where(eq(yearlySchoolParticipation.year, currentYear));
await db
.delete(yearlyTeacherParticipation)
.where(eq(yearlyTeacherParticipation.year, currentYear));

// Return a success response so fetch.ok becomes true
return NextResponse.json({ success: true });
} catch (error) {
return NextResponse.json(
{ error: "Internal server error: " + (error as Error).message },
{ status: 500 },
);
}
}
108 changes: 108 additions & 0 deletions src/app/api/schools/[name]/gateway/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/***************************************************************
*
* /api/schools/[name]/gateway/route.ts
*
* Author: Zander & Anne
* Date: 3/1/2026
*
* Summary: Endpoint to fetch or update a school's
* "gateway" flag in the database.
*
**************************************************************/

import { NextRequest, NextResponse } from "next/server";
import { db } from "@/lib/db";
import { schools } from "@/lib/schema";
import { eq, sql } from "drizzle-orm";

/**
* Fetch the gateway status of a school.
*
* @param req NextRequest object
* @param params Promise containing the school `name` param
* @returns JSON response with { gateway: boolean } or error
*/
export async function GET(
req: NextRequest,
{ params }: { params: Promise<{ name: string }> },
) {
try {
const { name } = await params;
const searchName = name.replace(/-/g, " ");

const schoolResult = await db
.select({ id: schools.id, gateway: schools.gateway })
.from(schools)
.where(eq(sql`LOWER(${schools.name})`, searchName.toLowerCase()))
.limit(1);

if (!schoolResult || schoolResult.length === 0) {
return NextResponse.json(
{ error: "School not found" },
{ status: 404 },
);
}

return NextResponse.json({ gateway: schoolResult[0].gateway });
} catch (error) {
return NextResponse.json(
{ error: "Internal server error: " + (error as Error).message },
{ status: 500 },
);
}
}

/**
* Update the gateway status of a school.
*
* @param req NextRequest object
* @param params Promise containing the school `name` param
* @returns JSON response with { message, gateway } or error
*/
export async function PATCH(
req: NextRequest,
{ params }: { params: Promise<{ name: string }> },
) {
try {
const { name } = await params;
const searchName = name.replace(/-/g, " ");

const body = await req.json();
const { gateway } = body;

if (typeof gateway !== "boolean") {
return NextResponse.json(
{ error: "Invalid gateway value" },
{ status: 400 },
);
}

const schoolResult = await db
.select({ id: schools.id })
.from(schools)
.where(eq(sql`LOWER(${schools.name})`, searchName.toLowerCase()))
.limit(1);

if (!schoolResult || schoolResult.length === 0) {
return NextResponse.json(
{ error: "School not found" },
{ status: 404 },
);
}

await db
.update(schools)
.set({ gateway })
.where(eq(schools.id, schoolResult[0].id));

return NextResponse.json({
message: "Gateway updated successfully",
gateway,
});
} catch (error) {
return NextResponse.json(
{ error: "Internal server error: " + (error as Error).message },
{ status: 500 },
);
}
}
13 changes: 12 additions & 1 deletion src/app/api/schools/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,25 @@ export async function GET(req: NextRequest) {

// Lightweight list mode: returns id, name, lat, lng for all schools
if (searchParams.get("list") === "true") {
const allSchools = await db
const gatewayParam = searchParams.get("gateway");
const isGateway = gatewayParam === "true"; // boolean

const query = db
.select({
id: schools.id,
name: schools.name,
latitude: schools.latitude,
longitude: schools.longitude,
gateway: schools.gateway,
})
.from(schools);

// Only filter if gateway=true is explicitly passed
if (isGateway) {
query.where(eq(schools.gateway, true));
}

const allSchools = await query;
return NextResponse.json(allSchools);
}

Expand Down
10 changes: 10 additions & 0 deletions src/app/api/upload/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,16 @@ export async function POST(req: NextRequest) {
// Remove header row and filter out empty rows
const filteredRows = rawData.slice(1).filter((row) => row.length > 0);

// Delete any existing data before uploading new data
const res = await fetch(`/api/delete-year?year=${year}`, {
method: "DELETE",
});

if (!res.ok) {
const errData = await res.json();
throw new Error(errData?.error || "Failed to delete previous data");
}

let insertedCount = 0;
for (const row of filteredRows) {
// Find or create school using schoolId
Expand Down
35 changes: 35 additions & 0 deletions src/app/api/years-with-data/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/***************************************************************
*
* /api/get-existing-years/route.ts
*
* Author: Anne Wu & Zander Barba
* Date: 2/20/2026
*
* Summary: api to fetch all unique years of participation
*
***************************************************************/

import { NextResponse } from "next/server";
import { db } from "@/lib/db";
import { yearlySchoolParticipation } from "@/lib/schema";

export async function GET() {
try {
const result = await db
.select({
year: yearlySchoolParticipation.year,
})
.from(yearlySchoolParticipation)
.groupBy(yearlySchoolParticipation.year)
.orderBy(yearlySchoolParticipation.year);

const years = result.map((row) => row.year);

return NextResponse.json({ years }, { status: 200 });
} catch (error) {
return NextResponse.json(
{ error: "Internal server error: " + (error as Error).message },
{ status: 500 },
);
}
}
Loading