diff --git a/app/api/circuits/active/route.ts b/app/api/circuits/active/route.ts new file mode 100644 index 0000000..a94e20e --- /dev/null +++ b/app/api/circuits/active/route.ts @@ -0,0 +1,70 @@ +import { NextResponse } from "next/server" +import { SITE_URL } from "@/lib/constants" +import { apiNotFound } from "@/lib/utils" +import { BaseApiResponse } from "@/lib/definitions" + +export const revalidate = 600 + +interface ApiResponse extends BaseApiResponse { + year: string + total: number + circuits: any[] +} + +export async function GET(request: Request) { + const queryParams = new URL(request.url).searchParams + const yearParam = queryParams.get("year") + const currentYear = new Date().getFullYear() + + let year = yearParam ?? String(currentYear) + + try { + let res = await fetch(`${SITE_URL}/api/${year}`, { + cache: "no-store", + }) + + // fallback to previous year if current year is not available + if (!res.ok && !yearParam) { + year = String(currentYear - 1) + res = await fetch(`${SITE_URL}/api/${year}`, { + cache: "no-store", + }) + } + + if (!res.ok) { + return apiNotFound(request, "No races found for the given year.") + } + + const data = await res.json() + + if (!Array.isArray(data.races)) { + return NextResponse.json( + { message: "Invalid data format received from external API" }, + { status: 500 } + ) + } + + const circuits = data.races.map((race: any) => race.circuit) + + const response: ApiResponse = { + api: SITE_URL, + url: request.url, + year, + total: circuits.length, + circuits, + } + + return NextResponse.json(response, { + headers: { + "Cache-Control": "public, max-age=600, stale-while-revalidate=60", + }, + status: 200, + }) + } catch (error) { + console.log(error) + return NextResponse.json( + { message: "Server error" }, + { status: 500 } + ) + } +} diff --git a/content/endpoints.json b/content/endpoints.json index df78a09..25773ae 100644 --- a/content/endpoints.json +++ b/content/endpoints.json @@ -761,6 +761,19 @@ "title": "[circuitId]", "description": "This endpoint retrieves details about circuits in the database with the given Id.", "url": "circuitId" + }, + { + "id": "circuits-active", + "title": "circuits/active", + "description": "This endpoint retrieves all circuits used in a given Formula 1 season. If no year is provided, the API returns circuits from the latest available season.", + "url": "circuits/active?year=2025", + "params": [ + { + "name": "year", + "description": "Season year to retrieve active circuits for. If not provided, the latest available season is used.", + "default": "latest" + } + ] } ] }, diff --git a/content/examples.json b/content/examples.json index 88378d2..0a76bcc 100644 --- a/content/examples.json +++ b/content/examples.json @@ -2694,6 +2694,119 @@ } ] }, + { + "enpointId": "circuits-active", + "exampleResponse": { + "api": "https://f1api.dev", + "url": "http://localhost:3000/api/circuits/active?year=2025", + "year": "2024", + "total": 24, + "circuits": [ + { + "circuitId": "bahrain", + "circuitName": "Bahrain International Circuit", + "country": "Bahrain", + "city": "Sakhir", + "circuitLength": "5412km", + "lapRecord": "1:31:447", + "firstParticipationYear": 2004, + "corners": 15, + "fastestLapDriverId": "de_la_rosa", + "fastestLapTeamId": "mclaren", + "fastestLapYear": 2005, + "url": "http://en.wikipedia.org/wiki/Bahrain_International_Circuit" + } + ] + }, + "responseTypes": [ + { + "field": "api", + "type": "string", + "description": "The base API URL for the F1 Connect service." + }, + { + "field": "url", + "type": "string", + "description": "The URL used to fetch the circuits data with query parameters for year." + }, + { + "field": "year", + "type": "string", + "description": "The year for which circuits are being fetched." + }, + { + "field": "total", + "type": "number", + "description": "The total number of circuits available in the dataset for the given year." + }, + { + "field": "circuits", + "type": "array", + "description": "A list of circuit objects returned from the API." + }, + { + "field": "circuitId", + "type": "string", + "description": "A unique identifier for the circuit." + }, + { + "field": "circuitName", + "type": "string", + "description": "The circuit's name." + }, + { + "field": "country", + "type": "string", + "description": "The circuit's country." + }, + { + "field": "city", + "type": "string", + "description": "The circuit's city." + }, + { + "field": "circuitLength", + "type": "string", + "description": "The circuit's length (e.g., '5412km')." + }, + { + "field": "lapRecord", + "type": "string", + "description": "The lap record for the circuit." + }, + { + "field": "firstParticipationYear", + "type": "number", + "description": "The year the circuit first hosted a Grand Prix." + }, + { + "field": "corners", + "type": "number", + "description": "The number of corners on the circuit." + }, + { + "field": "fastestLapDriverId", + "type": "string", + "description": "The driverId of the fastest lap driver." + }, + { + "field": "fastestLapTeamId", + "type": "string", + "description": "The teamId of the fastest lap team." + }, + { + "field": "fastestLapYear", + "type": "number", + "description": "The year the fastest lap was set." + }, + { + "field": "url", + "type": "string", + "description": "A URL to the Wikipedia page of the circuit." + } + ] + }, + { "enpointId": "drivers-championship", "exampleResponse": { diff --git a/tests/api/circuits/circuits-active.spec.ts b/tests/api/circuits/circuits-active.spec.ts new file mode 100644 index 0000000..dd08104 --- /dev/null +++ b/tests/api/circuits/circuits-active.spec.ts @@ -0,0 +1,68 @@ +import { test, expect } from "@playwright/test" + +test.describe("GET /api/circuits/active", async () => { + test("should return active circuits for latest available year when year is not provided", async ({ + request, + }) => { + const response = await request.get(`/api/circuits/active`) + expect(response.ok()).toBeTruthy() + expect(response.status()).toBe(200) + + const data = await response.json() + + expect(data).toMatchObject({ + api: expect.any(String), + url: expect.any(String), + year: expect.any(String), + total: expect.any(Number), + circuits: expect.any(Array), + }) + + if (data.circuits.length > 0) { + const circuit = data.circuits[0] + expect(circuit).toMatchObject({ + circuitId: expect.any(String), + circuitName: expect.any(String), + country: expect.any(String), + city: expect.any(String), + url: expect.any(String), + }) + } + }) + + test("should return active circuits for a specific year", async ({ + request, + }) => { + const year = 2023 + const response = await request.get( + `/api/circuits/active?year=${year}` + ) + + expect(response.ok()).toBeTruthy() + expect(response.status()).toBe(200) + + const data = await response.json() + + expect(data.year).toBe(String(year)) + expect(Array.isArray(data.circuits)).toBeTruthy() + }) + + test("should return 404 when no races are found for the given year", async ({ + request, + }) => { + const invalidYear = 1900 + const response = await request.get( + `/api/circuits/active?year=${invalidYear}` + ) + + expect(response.status()).toBe(404) + + const data = await response.json() + expect(data).toMatchObject({ + api: expect.any(String), + url: expect.any(String), + message: expect.any(String), + status: 404, + }) + }) +}) \ No newline at end of file