cofi/app/api/accommodation-availability/route.ts

120 lines
3.6 KiB
TypeScript

import { NextResponse } from "next/server"
import { getGoogleSheetsClient } from "@/lib/google-sheets"
import { BOOKING_CRITERIA } from "@/lib/event.config"
const BOOKING_SHEET_ID = process.env.BOOKING_SHEET_ID
const BOOKING_SHEET_NAME = process.env.BOOKING_SHEET_NAME || "Sheet1"
// In-memory cache (2-min TTL)
let cache: { data: Record<string, boolean> | null; timestamp: number } = { data: null, timestamp: 0 }
const CACHE_TTL_MS = 2 * 60 * 1000
interface BedRow {
venue: string
bedType: string
occupied: boolean
}
function parseBedsFromSheet(data: string[][]): BedRow[] {
const beds: BedRow[] = []
let currentVenue = ""
let bedTypeCol = -1
let dateColumnIndices: number[] = []
let inDataSection = false
const venueNames = [...new Set(Object.values(BOOKING_CRITERIA).map((c) => c.venue.toLowerCase()))]
for (const row of data) {
if (!row || row.length === 0) {
inDataSection = false
continue
}
const firstCell = (row[0] || "").trim().toLowerCase()
const matchedVenue = venueNames.find((v) => firstCell.includes(v))
if (matchedVenue) {
const criteria = Object.values(BOOKING_CRITERIA).find((c) => c.venue.toLowerCase() === matchedVenue)
currentVenue = criteria?.venue || row[0].trim()
inDataSection = false
continue
}
if (!currentVenue) continue
const lowerRow = row.map((c) => (c || "").trim().toLowerCase())
const roomIdx = lowerRow.findIndex((c) => c === "room" || c === "room #" || c === "room number")
const bedIdx = lowerRow.findIndex((c) => c === "bed type" || c === "bed" || c === "type" || c === "bed/type")
if (roomIdx !== -1 && bedIdx !== -1) {
bedTypeCol = bedIdx
dateColumnIndices = []
for (let j = bedIdx + 1; j < row.length; j++) {
if ((row[j] || "").trim()) dateColumnIndices.push(j)
}
inDataSection = true
continue
}
if (inDataSection && bedTypeCol !== -1) {
let bedType = (row[bedTypeCol] || "").trim().toLowerCase()
if (!bedType) continue
if (bedType.includes("(") && !bedType.includes(")")) bedType += ")"
const occupied = dateColumnIndices.some((colIdx) => (row[colIdx] || "").trim().length > 0)
beds.push({ venue: currentVenue, bedType, occupied })
}
}
return beds
}
async function checkAvailability(): Promise<Record<string, boolean> | null> {
if (!BOOKING_SHEET_ID) return null
const now = Date.now()
if (cache.data && now - cache.timestamp < CACHE_TTL_MS) {
return cache.data
}
try {
const sheets = getGoogleSheetsClient()
const response = await sheets.spreadsheets.values.get({
spreadsheetId: BOOKING_SHEET_ID,
range: BOOKING_SHEET_NAME,
})
const sheetData = response.data.values || []
if (sheetData.length === 0) return null
const beds = parseBedsFromSheet(sheetData)
const availability: Record<string, boolean> = {}
for (const [type, criteria] of Object.entries(BOOKING_CRITERIA)) {
availability[type] = beds.some(
(bed) =>
bed.venue === criteria.venue &&
criteria.bedTypes.includes(bed.bedType) &&
!bed.occupied &&
(!criteria.roomFilter || true) // roomFilter not applicable to availability-only check
)
}
cache.data = availability
cache.timestamp = now
return availability
} catch (error) {
console.error("[Availability] Error checking availability:", error)
return null
}
}
export async function GET() {
const availability = await checkAvailability()
if (!availability) {
return NextResponse.json({ error: "Booking sheet not configured" }, { status: 503 })
}
return NextResponse.json(availability)
}