-
Notifications
You must be signed in to change notification settings - Fork 0
Feat/integration events page #83
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
00b5b50
feat: event layout api integration partial doneness
kr1shap 5783d5b
feat: events api integration into page
kr1shap 9c0f3f8
feat: added error state component
kr1shap 8df7520
feat: enhanced events page and api routing
kr1shap 6d58fb3
fix: fixed based on copilot suggestions on pr
kr1shap ffe3a19
fix: removed unused vars in error prop
kr1shap 69fcc0a
fix: GET /events/id router issue and package build issues
kr1shap d7ad975
chore: merge from main branch
kr1shap 554eb8d
fix: fix linting issues from main branch
kr1shap File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| /** | ||
| * Route: GET /api/events/:id | ||
| * Purpose: Return one event by its identifier. | ||
| * Behavior: | ||
| * - Validates Supabase service-role credentials. | ||
| * - Reads the id from route params and fetches a single record. | ||
| * - Returns 404 when the event is not found. | ||
| */ | ||
| import { NextRequest, NextResponse } from "next/server"; | ||
| import { fetchEventById } from "../service"; | ||
| import { | ||
| getSupabaseCredentials, | ||
| hasMissingServiceCredentials, | ||
| } from "../../utils/supabase"; | ||
|
|
||
| type RouteContext = { | ||
| params: Promise<{ | ||
| id: string; | ||
| }>; | ||
| }; | ||
|
|
||
| export async function GET(_request: NextRequest, context: RouteContext) { | ||
| try { | ||
| const params = await context.params; | ||
| const credentials = getSupabaseCredentials(); | ||
|
|
||
| if (hasMissingServiceCredentials(credentials)) { | ||
| return NextResponse.json( | ||
| { error: "Supabase credentials missing" }, | ||
| { status: 500 }, | ||
| ); | ||
| } | ||
|
|
||
| const result = await fetchEventById(credentials, params.id); | ||
| if (!result.ok) { | ||
| return NextResponse.json( | ||
| { error: result.error }, | ||
| { status: result.status }, | ||
| ); | ||
| } | ||
|
|
||
| return NextResponse.json({ event: result.event }); | ||
| } catch (err) { | ||
| const message = err instanceof Error ? err.message : "Unknown error"; | ||
| return NextResponse.json({ error: message }, { status: 500 }); | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| /** | ||
| * Route: GET /api/events/past | ||
| * Purpose: Return past events only. | ||
| * Behavior: | ||
| * - Validates Supabase anon credentials. | ||
| * - Proxies to Supabase Edge Function events/past. | ||
| * - Returns normalized events array with optional debug/error metadata. | ||
| */ | ||
| import { NextResponse } from "next/server"; | ||
| import { fetchPastEvents } from "../service"; | ||
| import { | ||
| getSupabaseCredentials, | ||
| hasMissingAnonCredentials, | ||
| } from "../../utils/supabase"; | ||
|
|
||
| export async function GET() { | ||
| try { | ||
| const credentials = getSupabaseCredentials(); | ||
|
|
||
| if (hasMissingAnonCredentials(credentials)) { | ||
| return NextResponse.json( | ||
| { events: [], error: "Supabase credentials missing" }, | ||
| { status: 500 }, | ||
| ); | ||
| } | ||
|
|
||
| const result = await fetchPastEvents(credentials); | ||
| return NextResponse.json( | ||
| { | ||
| events: result.events || [], | ||
| // debug: result.debug, | ||
| error: result.error, | ||
| }, | ||
| { status: result.ok ? 200 : result.status }, | ||
| ); | ||
|
kr1shap marked this conversation as resolved.
|
||
| } catch (err) { | ||
| const message = err instanceof Error ? err.message : "Unknown error"; | ||
| return NextResponse.json({ events: [], error: message }, { status: 500 }); | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| /** | ||
| * Route: GET /api/events | ||
| * Purpose: Return both upcoming and past events in a single response. | ||
| * Behavior: | ||
| * - Validates required Supabase anon credentials. | ||
| * - Fetches upcoming and past datasets in parallel. | ||
| * - Returns combined payload with optional debug/error metadata. | ||
| */ | ||
| import { NextResponse } from "next/server"; | ||
| import { fetchPastEvents, fetchUpcomingEvents } from "./service"; | ||
| import { | ||
| getSupabaseCredentials, | ||
| hasMissingAnonCredentials, | ||
| } from "../utils/supabase"; | ||
|
|
||
| export async function GET() { | ||
| try { | ||
| const credentials = getSupabaseCredentials(); | ||
|
|
||
| if (hasMissingAnonCredentials(credentials)) { | ||
| return NextResponse.json( | ||
| { upcoming: [], past: [], error: "Supabase credentials missing" }, | ||
| { status: 500 }, | ||
| ); | ||
| } | ||
|
|
||
| const [upcomingResult, pastResult] = await Promise.all([ | ||
| fetchUpcomingEvents(credentials), | ||
| fetchPastEvents(credentials), | ||
| ]); | ||
|
|
||
| const hasError = !upcomingResult.ok || !pastResult.ok; | ||
| return NextResponse.json( | ||
| { | ||
| upcoming: upcomingResult.events || [], | ||
| past: pastResult.events || [], | ||
| // upcomingDebug: upcomingResult.debug, | ||
| // pastDebug: pastResult.debug, | ||
| error: upcomingResult.error || pastResult.error, | ||
| }, | ||
| { status: hasError ? 500 : 200 }, | ||
| ); | ||
|
kr1shap marked this conversation as resolved.
|
||
| } catch (err) { | ||
| const message = err instanceof Error ? err.message : "Unknown error"; | ||
| return NextResponse.json( | ||
| { | ||
| upcoming: [], | ||
| past: [], | ||
| error: message, | ||
| }, | ||
| { status: 500 }, | ||
| ); | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,117 @@ | ||
| /** | ||
| * Events service layer. | ||
| * | ||
| * Purpose: | ||
| * - Centralize all event data-access calls used by API routes. | ||
| * - Keep route files focused on HTTP orchestration and status mapping. | ||
| * | ||
| * Notes: | ||
| * - This service intentionally uses Supabase Edge Functions for event reads | ||
| * (`events/upcoming`, `events/past`, `events/:id`) to keep behavior consistent | ||
| * with backend function logic. | ||
| * - Response parsing is defensive to support minor payload-shape differences | ||
| * between edge function handlers. | ||
| */ | ||
| import { | ||
| requestSupabaseEdgeFunction, | ||
| type SupabaseCredentials, | ||
| } from "../utils/supabase"; | ||
|
|
||
| type EventsTimeline = "upcoming" | "past"; | ||
|
|
||
| /** | ||
| * Fetches a single event by id through the Edge Function route. | ||
| * | ||
| * Expected successful payloads supported: | ||
| * - `{ event: {...} }` | ||
| * - `{ events: [{...}] }` | ||
| * - direct object payload | ||
| * | ||
| * Returns a normalized result with `ok`, `status`, and either `event` or `error`. | ||
| */ | ||
| export async function fetchEventById( | ||
| credentials: SupabaseCredentials, | ||
| eventId: string, | ||
| ) { | ||
| const encodedId = encodeURIComponent(eventId); | ||
| const { response, body } = await requestSupabaseEdgeFunction( | ||
| credentials.url, | ||
| `events/${encodedId}`, | ||
| credentials.anonKey, | ||
| { method: "GET" }, | ||
| ); | ||
|
|
||
| if (!response.ok) { | ||
| return { | ||
| ok: false as const, | ||
| status: response.status, | ||
| error: | ||
| (body as { error?: string; message?: string })?.error || | ||
| (body as { error?: string; message?: string })?.message || | ||
| "Failed to fetch event", | ||
| }; | ||
| } | ||
|
|
||
| const maybeObject = body as { | ||
| event?: unknown; | ||
| events?: unknown[]; | ||
| }; | ||
|
|
||
| const event = | ||
| maybeObject?.event || | ||
| (Array.isArray(maybeObject?.events) ? maybeObject.events[0] : null) || | ||
| (Array.isArray(body) ? body[0] : body); | ||
|
|
||
| if (!event) { | ||
| return { | ||
| ok: false as const, | ||
| status: 404, | ||
| error: "Event not found", | ||
| }; | ||
| } | ||
|
|
||
| return { | ||
| ok: true as const, | ||
| event, | ||
| }; | ||
| } | ||
|
|
||
| /** | ||
| * Fetches events from one timeline bucket (`upcoming` or `past`) through | ||
| * the Edge Function route and returns a normalized response contract. | ||
| */ | ||
| export async function fetchEventsByTimeline( | ||
| credentials: SupabaseCredentials, | ||
| timeline: EventsTimeline, | ||
| ) { | ||
| const { response, body } = await requestSupabaseEdgeFunction( | ||
| credentials.url, | ||
| `events/${timeline}`, | ||
| credentials.anonKey, | ||
| ); | ||
|
|
||
| return { | ||
| ok: response.ok, | ||
| status: response.status, | ||
| events: body.events || [], | ||
| debug: { | ||
| status: response.status, | ||
| body, | ||
| }, | ||
| error: (body as { error?: string })?.error, | ||
| }; | ||
| } | ||
|
|
||
| /** | ||
| * Convenience wrapper for fetching upcoming events. | ||
| */ | ||
| export async function fetchUpcomingEvents(credentials: SupabaseCredentials) { | ||
| return fetchEventsByTimeline(credentials, "upcoming"); | ||
| } | ||
|
|
||
| /** | ||
| * Convenience wrapper for fetching past events. | ||
| */ | ||
| export async function fetchPastEvents(credentials: SupabaseCredentials) { | ||
| return fetchEventsByTimeline(credentials, "past"); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| /** | ||
| * Route: GET /api/events/upcoming | ||
| * Purpose: Return upcoming events only. | ||
| * Behavior: | ||
| * - Validates Supabase anon credentials. | ||
| * - Proxies to Supabase Edge Function events/upcoming. | ||
| * - Returns normalized events array with optional debug/error metadata. | ||
| */ | ||
| import { NextResponse } from "next/server"; | ||
| import { fetchUpcomingEvents } from "../service"; | ||
| import { | ||
| getSupabaseCredentials, | ||
| hasMissingAnonCredentials, | ||
| } from "../../utils/supabase"; | ||
|
|
||
| export async function GET() { | ||
| try { | ||
| const credentials = getSupabaseCredentials(); | ||
|
|
||
| if (hasMissingAnonCredentials(credentials)) { | ||
| return NextResponse.json( | ||
| { events: [], error: "Supabase credentials missing" }, | ||
| { status: 500 }, | ||
| ); | ||
| } | ||
|
|
||
| const result = await fetchUpcomingEvents(credentials); | ||
| return NextResponse.json( | ||
| { | ||
| events: result.events || [], | ||
| // debug: result.debug, | ||
| error: result.error, | ||
| }, | ||
| { status: result.ok ? 200 : result.status }, | ||
| ); | ||
|
kr1shap marked this conversation as resolved.
|
||
| } catch (err) { | ||
| const message = err instanceof Error ? err.message : "Unknown error"; | ||
| return NextResponse.json({ events: [], error: message }, { status: 500 }); | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,90 @@ | ||
| export type SupabaseCredentials = { | ||
| url: string; | ||
| serviceKey: string; | ||
| anonKey: string; | ||
| }; | ||
|
|
||
| export type EdgeRequestOptions = { | ||
| method?: "GET" | "POST" | "PATCH" | "PUT" | "DELETE"; | ||
| body?: unknown; | ||
| }; | ||
|
|
||
| export function getSupabaseCredentials(): SupabaseCredentials { | ||
| return { | ||
| url: process.env.SUPABASE_URL || "", | ||
| serviceKey: process.env.SUPABASE_SERVICE_ROLE_KEY || "", | ||
| anonKey: process.env.SUPABASE_ANON_KEY || "", | ||
| }; | ||
| } | ||
|
|
||
| export function hasMissingAnonCredentials( | ||
| credentials: SupabaseCredentials, | ||
| ): boolean { | ||
| return !credentials.url || !credentials.anonKey; | ||
| } | ||
|
|
||
| export function hasMissingServiceCredentials( | ||
| credentials: SupabaseCredentials, | ||
| ): boolean { | ||
| return !credentials.url || !credentials.serviceKey; | ||
| } | ||
|
|
||
| function getSupabaseAnonHeaders(anonKey: string) { | ||
| return { | ||
| Authorization: `Bearer ${anonKey}`, | ||
| "Content-Type": "application/json", | ||
| }; | ||
| } | ||
|
|
||
| function getSupabaseServiceHeaders(serviceKey: string) { | ||
| return { | ||
| Authorization: `Bearer ${serviceKey}`, | ||
| apikey: serviceKey, | ||
| "Content-Type": "application/json", | ||
| }; | ||
| } | ||
|
|
||
| /** | ||
| * Core network helper for Supabase Edge Functions. | ||
| * Use this for calls to /functions/v1/* endpoints with anon-key authorization. | ||
| * Returns the raw response plus a safely parsed JSON payload (falls back to {}). | ||
| */ | ||
| export async function requestSupabaseEdgeFunction( | ||
| baseUrl: string, | ||
| path: string, | ||
| anonKey: string, | ||
| options: EdgeRequestOptions = {}, | ||
| ) { | ||
| const { method = "GET", body } = options; | ||
|
|
||
| const response = await fetch(`${baseUrl}/functions/v1/${path}`, { | ||
| method, | ||
| headers: getSupabaseAnonHeaders(anonKey), | ||
| body: body !== undefined ? JSON.stringify(body) : undefined, | ||
| }); | ||
|
|
||
| const payload = await response.json().catch(() => ({})); | ||
| return { response, body: payload }; | ||
| } | ||
|
|
||
| /** | ||
| * Core network helper for Supabase REST API. | ||
| * Use this for calls to /rest/v1/* endpoints with service-role authorization. | ||
| * Returns the raw response plus a safely parsed JSON payload (falls back to []). | ||
| */ | ||
| export async function requestSupabaseRest( | ||
| credentials: SupabaseCredentials, | ||
| path: string, | ||
| options: EdgeRequestOptions = {}, | ||
| ) { | ||
| const { method = "GET", body } = options; | ||
|
|
||
| const response = await fetch(`${credentials.url}/rest/v1/${path}`, { | ||
| method, | ||
| headers: getSupabaseServiceHeaders(credentials.serviceKey), | ||
| body: body !== undefined ? JSON.stringify(body) : undefined, | ||
| }); | ||
|
|
||
| const payload = await response.json().catch(() => []); | ||
| return { response, body: payload }; | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.