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
70 changes: 70 additions & 0 deletions app/api/circuits/active/route.ts
Original file line number Diff line number Diff line change
@@ -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 }
)
}
}
13 changes: 13 additions & 0 deletions content/endpoints.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
]
}
]
},
Expand Down
113 changes: 113 additions & 0 deletions content/examples.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
68 changes: 68 additions & 0 deletions tests/api/circuits/circuits-active.spec.ts
Original file line number Diff line number Diff line change
@@ -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,
})
})
})