From 3fc38b14440395b5e63f7245050da1fffa101b32 Mon Sep 17 00:00:00 2001 From: dionjoshualobo <23h13.joshua@sjec.ac.in> Date: Tue, 30 Jun 2026 14:11:56 +0530 Subject: [PATCH 1/3] feat: fetch calendar events from backend API instead of Google Sheets --- src/lib/get-events.ts | 99 +++++++------------------------------------ 1 file changed, 15 insertions(+), 84 deletions(-) diff --git a/src/lib/get-events.ts b/src/lib/get-events.ts index 32f8b5c..637869e 100644 --- a/src/lib/get-events.ts +++ b/src/lib/get-events.ts @@ -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 calendarApiUrl = process.env.CALENDAR_API_URL; async function readLocalEvents(): Promise { try { @@ -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 (!calendarApiUrl) { + console.warn("getEvents: Missing CALENDAR_API_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(calendarApiUrl, { + next: { revalidate: 300 }, + }); + + 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 []; } } From d75fae79e5fbb3a5e80b4076850b03fc7a85cb9c Mon Sep 17 00:00:00 2001 From: dionjoshualobo <23h13.joshua@sjec.ac.in> Date: Sun, 5 Jul 2026 11:15:31 +0530 Subject: [PATCH 2/3] feat: consolidate to single BACKEND_URL env var and add push-based calendar revalidation --- src/app/api/revalidate/route.ts | 23 +++++++++++++++++++++++ src/lib/form-submission.ts | 18 +++++++++--------- src/lib/get-events.ts | 10 +++++----- 3 files changed, 37 insertions(+), 14 deletions(-) create mode 100644 src/app/api/revalidate/route.ts diff --git a/src/app/api/revalidate/route.ts b/src/app/api/revalidate/route.ts new file mode 100644 index 0000000..4c097af --- /dev/null +++ b/src/app/api/revalidate/route.ts @@ -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 }, + ); + } +} diff --git a/src/lib/form-submission.ts b/src/lib/form-submission.ts index ffa8fcb..a3f2bc9 100644 --- a/src/lib/form-submission.ts +++ b/src/lib/form-submission.ts @@ -7,15 +7,15 @@ import type { 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(); @@ -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), }); @@ -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), }); @@ -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), }); diff --git a/src/lib/get-events.ts b/src/lib/get-events.ts index 637869e..a44abfc 100644 --- a/src/lib/get-events.ts +++ b/src/lib/get-events.ts @@ -6,7 +6,7 @@ import { endOfMonth } from "date-fns"; import type { IEvent } from "@/types"; const localEventsPath = path.join(process.cwd(), "src/data/local-events.json"); -const calendarApiUrl = process.env.CALENDAR_API_URL; +const backendUrl = process.env.BACKEND_URL; async function readLocalEvents(): Promise { try { @@ -49,14 +49,14 @@ export async function getEvents( return filterEventsByRange(localEvents, startDate, endDate); } - if (!calendarApiUrl) { - console.warn("getEvents: Missing CALENDAR_API_URL configuration."); + if (!backendUrl) { + console.warn("getEvents: Missing BACKEND_URL configuration."); return []; } try { - const response = await fetch(calendarApiUrl, { - next: { revalidate: 300 }, + const response = await fetch(`${backendUrl}/api/events`, { + next: { revalidate: 300, tags: ["calendar"] }, }); if (!response.ok) { From 4adea6570fbea9711e8a49fb63e13d31c0142a3f Mon Sep 17 00:00:00 2001 From: dionjoshualobo <23h13.joshua@sjec.ac.in> Date: Sun, 5 Jul 2026 14:05:53 +0530 Subject: [PATCH 3/3] switch to BACKEND_URL --- README.md | 3 +++ contributing.md | 4 +++- src/lib/form-submission.ts | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5f7cd90..ee7b0c2 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/contributing.md b/contributing.md index f31c465..0d9eaf6 100644 --- a/contributing.md +++ b/contributing.md @@ -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 ``` --- diff --git a/src/lib/form-submission.ts b/src/lib/form-submission.ts index a3f2bc9..691e676 100644 --- a/src/lib/form-submission.ts +++ b/src/lib/form-submission.ts @@ -1,10 +1,10 @@ "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 BACKEND_URL = process.env.BACKEND_URL;