Skip to content
Merged
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 README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ Create a `.env.local` file in the root directory:

```env
NEXT_PUBLIC_SITE_URL=http://localhost:3000
BACKEND_URL=http://localhost:8080
BACKEND_SECRET_KEY=replace-me
REVALIDATION_SECRET=replace-me

# PostHog OpenTelemetry logs
NEXT_PUBLIC_POSTHOG_KEY=phc_xxxxxxxxxxxxxxxxx
Expand Down
4 changes: 3 additions & 1 deletion contributing.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,9 @@ Create a `.env.local` file in the root directory:

```env
NEXT_PUBLIC_SITE_URL=http://localhost:3000
# Add other environment variables as needed
BACKEND_URL=http://localhost:8080
BACKEND_SECRET_KEY=replace-me
REVALIDATION_SECRET=replace-me
```

---
Expand Down
23 changes: 23 additions & 0 deletions src/app/api/revalidate/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { revalidateTag } from "next/cache";
import { NextResponse } from "next/server";

export async function POST(request: Request) {
try {
const body = (await request.json()) as { secret?: string };
const secret = process.env.REVALIDATION_SECRET;

if (!secret || body.secret !== secret) {
return NextResponse.json({ error: "Invalid secret" }, { status: 401 });
}

revalidateTag("calendar");

return NextResponse.json({ revalidated: true });
} catch (error) {
console.error("Revalidation error:", error);
return NextResponse.json(
{ error: "Failed to revalidate" },
{ status: 500 },
);
}
}
20 changes: 10 additions & 10 deletions src/lib/form-submission.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
"use server";
import { SeverityNumber } from "@opentelemetry/api-logs";
import type {
CollegeFormValues,
EventSubmissionFormValues,
IndividualFormValues,
} from "@/lib/forms-config";
import { SeverityNumber } from "@opentelemetry/api-logs";
import { emitServerLog, flushOtelLogs } from "@/lib/otel-logger";

const WEBHOOK_URL = process.env.WEBHOOK_URL;
const BACKEND_URL = process.env.BACKEND_URL;

export async function submitFormData(
name: "individual" | "community" | "event",
data: IndividualFormValues | CollegeFormValues | EventSubmissionFormValues,
) {
try {
if (!WEBHOOK_URL || !process.env.WEBHOOK_SECRET_KEY)
throw new Error("WEBHOOK_URL not set");
if (!BACKEND_URL || !process.env.BACKEND_SECRET_KEY)
throw new Error("BACKEND_URL not set");
// if (!doc) throw new Error("Doc not found!");
// await doc.loadInfo();

Expand Down Expand Up @@ -44,11 +44,11 @@ export async function submitFormData(
// "──────────────────────────────",
// };

const res = await fetch(`${WEBHOOK_URL}new-applicant`, {
const res = await fetch(`${BACKEND_URL}/api/new-applicant`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Secret-Key": process.env.WEBHOOK_SECRET_KEY,
"X-Secret-Key": process.env.BACKEND_SECRET_KEY,
},
body: JSON.stringify(data),
});
Expand All @@ -73,11 +73,11 @@ export async function submitFormData(
// "──────────────────────────────",
// };

const res = await fetch(`${WEBHOOK_URL}new-college-applicant`, {
const res = await fetch(`${BACKEND_URL}/api/new-college-applicant`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Secret-Key": process.env.WEBHOOK_SECRET_KEY,
"X-Secret-Key": process.env.BACKEND_SECRET_KEY,
},
body: JSON.stringify(data),
});
Expand All @@ -86,11 +86,11 @@ export async function submitFormData(
throw new Error("Error submitting to webhook");
}
} else if (name === "event") {
const res = await fetch(`${WEBHOOK_URL}new-event`, {
const res = await fetch(`${BACKEND_URL}/api/new-event`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Secret-Key": process.env.WEBHOOK_SECRET_KEY,
"X-Secret-Key": process.env.BACKEND_SECRET_KEY,
},
body: JSON.stringify(data),
});
Expand Down
99 changes: 15 additions & 84 deletions src/lib/get-events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,10 @@
import { promises as fs } from "node:fs";
import path from "node:path";
import { endOfMonth } from "date-fns";
import { JWT } from "google-auth-library";
import { GoogleSpreadsheet } from "google-spreadsheet";
import type { iconsMap } from "@/constants";
import type { EventSubmissionFormValues } from "@/lib/forms-config";
import type { IEvent, TEventColor } from "@/types";

const serviceAccountAuth = new JWT({
email: process.env.GOOGLE_SERVICE_ACCOUNT_EMAIL,
key: process.env.GOOGLE_PRIVATE_KEY?.replace(/\\n/g, "\n"),
scopes: ["https://www.googleapis.com/auth/spreadsheets"],
});

const doc = new GoogleSpreadsheet(
process.env.GOOGLE_SHEET_ID ?? "",
serviceAccountAuth,
);
import type { IEvent } from "@/types";

const localEventsPath = path.join(process.cwd(), "src/data/local-events.json");
const backendUrl = process.env.BACKEND_URL;

async function readLocalEvents(): Promise<IEvent[]> {
try {
Expand Down Expand Up @@ -63,80 +49,25 @@ export async function getEvents(
return filterEventsByRange(localEvents, startDate, endDate);
}

if (
!process.env.GOOGLE_SHEET_ID ||
!process.env.GOOGLE_SERVICE_ACCOUNT_EMAIL ||
!process.env.GOOGLE_PRIVATE_KEY
) {
console.warn("getEvents: Missing Google Sheets configuration.");
if (!backendUrl) {
console.warn("getEvents: Missing BACKEND_URL configuration.");
return [];
}

try {
await doc.loadInfo();
const sheet = doc.sheetsByIndex[3];
if (!sheet) throw new Error("Sheet not found");

const rows = await sheet.getRows();
const res: IEvent[] = rows
.map((row): IEvent | null => {
const eventData =
row.toObject() as unknown as EventSubmissionFormValues & {
ID: string;
};
const isDK24 = eventData.organizationName === "DK24";

const startDateTime = new Date(eventData.startDateTime);
const endDateTime = new Date(eventData.endDateTime);

if (
Number.isNaN(startDateTime.getTime()) ||
Number.isNaN(endDateTime.getTime())
) {
return null;
}

return {
id: eventData.ID,
title: eventData.eventName,
startDateTime: startDateTime.toISOString(),
endDateTime: endDateTime.toISOString(),
time: eventData.startDateTime.split("T")[1]?.substring(0, 5) || "",
location: eventData.eventLocation,
description: eventData.eventDescription,
registrationLink: eventData.registrationLink || "",
joinLink: eventData.eventWebsite || "",
icon: "calendar" as keyof typeof iconsMap,
highlight: isDK24,
youtubeLink: "",
color: (isDK24 ? "green" : "gray") as TEventColor,
organizationName: eventData.organizationName,
posterUrl: eventData.eventPosterUrl,
tags: (() => {
const raw = eventData.eventTags as unknown;
if (Array.isArray(raw)) return raw as string[];
if (typeof raw === "string") {
let s = (raw as string).trim();
if (s.startsWith("[") && s.endsWith("]")) {
s = s.slice(1, -1);
}
return s
.split(",")
.map((t: string) => t.replace(/^['"]|['"]$/g, "").trim())
.filter(Boolean) as string[];
}
return [];
})(),
};
})
.filter((event): event is IEvent => event !== null);
const response = await fetch(`${backendUrl}/api/events`, {
next: { revalidate: 300, tags: ["calendar"] },
});

if (!response.ok) {
throw new Error(`Calendar API request failed with ${response.status}`);
}

return filterEventsByRange(res, startDate, endDate);
const data = (await response.json()) as { events?: IEvent[] };
const events = Array.isArray(data.events) ? data.events : [];
return filterEventsByRange(events, startDate, endDate);
} catch (error) {
console.error(
"getEvents: Failed to load events from Google Sheets.",
error,
);
console.error("getEvents: Failed to load events from Calendar API.", error);
return [];
}
}
Expand Down