From 3815da2d3f30b858928de5915813860d8b5a99e5 Mon Sep 17 00:00:00 2001 From: josh Date: Thu, 23 Apr 2026 21:43:59 -0400 Subject: [PATCH] refactor: make frontend a pure presentation layer fetching from backend API Server components now fetch composed data from the backend instead of directly querying SQLite and external APIs. Removes better-sqlite3 dependency from the frontend entirely. Co-Authored-By: Claude Opus 4.6 --- app/api/parks/route.ts | 59 --- app/page.tsx | 128 +---- app/park/[id]/page.tsx | 92 ++-- backend/src/db/queries.ts | 7 +- components/HomePageClient.tsx | 2 +- components/MobileCardList.tsx | 2 +- components/ParkCard.tsx | 2 +- components/ParkMonthCalendar.tsx | 2 +- components/WeekCalendar.tsx | 2 +- lib/db.ts | 288 ------------ lib/types.ts | 5 + next.config.ts | 2 - package-lock.json | 780 +------------------------------ package.json | 2 - tsconfig.json | 2 +- 15 files changed, 55 insertions(+), 1320 deletions(-) delete mode 100644 app/api/parks/route.ts delete mode 100644 lib/db.ts create mode 100644 lib/types.ts diff --git a/app/api/parks/route.ts b/app/api/parks/route.ts deleted file mode 100644 index c8079fc..0000000 --- a/app/api/parks/route.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { NextRequest, NextResponse } from "next/server"; -import { PARKS } from "@/lib/parks"; -import { openDb, getMonthCalendar } from "@/lib/db"; -import type { Park } from "@/lib/scrapers/types"; - -export interface ParksApiResponse { - parks: Park[]; - calendar: Record; - month: string; - daysInMonth: number; -} - -function getDaysInMonth(year: number, month: number): number { - return new Date(year, month, 0).getDate(); -} - -function parseMonthParam( - monthParam: string | null -): { year: number; month: number } | null { - if (!monthParam) return null; - const match = monthParam.match(/^(\d{4})-(\d{2})$/); - if (!match) return null; - const year = parseInt(match[1], 10); - const month = parseInt(match[2], 10); - if (month < 1 || month > 12) return null; - return { year, month }; -} - -export async function GET(request: NextRequest): Promise { - const monthParam = request.nextUrl.searchParams.get("month"); - const parsed = parseMonthParam(monthParam); - - if (!parsed) { - return NextResponse.json( - { error: "Invalid or missing ?month=YYYY-MM parameter" }, - { status: 400 } - ); - } - - const { year, month } = parsed; - const daysInMonth = getDaysInMonth(year, month); - - const db = openDb(); - const calendar = getMonthCalendar(db, year, month); - db.close(); - - const response: ParksApiResponse = { - parks: PARKS, - calendar, - month: `${year}-${String(month).padStart(2, "0")}`, - daysInMonth, - }; - - return NextResponse.json(response, { - headers: { - "Cache-Control": "public, s-maxage=3600, stale-while-revalidate=86400", - }, - }); -} diff --git a/app/page.tsx b/app/page.tsx index f2bbb53..f1f7fde 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,12 +1,7 @@ import { HomePageClient } from "@/components/HomePageClient"; -import { PARKS } from "@/lib/parks"; -import { openDb, getDateRange } from "@/lib/db"; -import { getTodayLocal, isWithinOperatingWindow, getOperatingStatus } from "@/lib/env"; -import { fetchLiveRides } from "@/lib/scrapers/queuetimes"; -import { fetchToday } from "@/lib/scrapers/sixflags"; -import { QUEUE_TIMES_IDS } from "@/lib/queue-times-map"; -import { getCoasterSet, hasCoasterData } from "@/lib/coaster-data"; -import type { DayData } from "@/lib/db"; +import { getTodayLocal } from "@/lib/env"; + +const BACKEND_URL = process.env.BACKEND_URL ?? "http://localhost:3001"; interface PageProps { searchParams: Promise<{ week?: string }>; @@ -26,121 +21,14 @@ function getWeekStart(param: string | undefined): string { return d.toISOString().slice(0, 10); } -function getWeekDates(sundayIso: string): string[] { - return Array.from({ length: 7 }, (_, i) => { - const d = new Date(sundayIso + "T00:00:00"); - d.setDate(d.getDate() + i); - return d.toISOString().slice(0, 10); - }); -} - -function getCurrentWeekStart(): string { - const todayIso = getTodayLocal(); - const d = new Date(todayIso + "T00:00:00"); - d.setDate(d.getDate() - d.getDay()); - return d.toISOString().slice(0, 10); -} - export default async function HomePage({ searchParams }: PageProps) { const params = await searchParams; const weekStart = getWeekStart(params.week); - const weekDates = getWeekDates(weekStart); - const endDate = weekDates[6]; - const today = getTodayLocal(); - const isCurrentWeek = weekStart === getCurrentWeekStart(); - const db = openDb(); - const data = getDateRange(db, weekStart, endDate); + const data = await fetch( + `${BACKEND_URL}/api/calendar/week?start=${weekStart}`, + { next: { revalidate: 120 } }, + ).then((r) => r.json()); - // Merge live today data from the Six Flags API (dateless endpoint, 5-min ISR cache). - // This ensures weather delays, early closures, and hour changes surface within 5 minutes - // without waiting for the next scheduled scrape. Only fetched when viewing the current week. - if (weekDates.includes(today)) { - const todayResults = await Promise.all( - PARKS.map(async (p) => { - const live = await fetchToday(p.apiId, 300); // 5-min ISR cache - return live ? { parkId: p.id, live } : null; - }) - ); - for (const result of todayResults) { - if (!result) continue; - const { parkId, live } = result; - if (!data[parkId]) data[parkId] = {}; - data[parkId][today] = { - isOpen: live.isOpen, - hoursLabel: live.hoursLabel ?? null, - specialType: live.specialType ?? null, - } satisfies DayData; - } - } - - db.close(); - - const scrapedCount = Object.values(data).reduce( - (sum, parkData) => sum + Object.keys(parkData).length, - 0 - ); - - const coasterDataAvailable = hasCoasterData(); - - let rideCounts: Record = {}; - let coasterCounts: Record = {}; - let closingParkIds: string[] = []; - let openParkIds: string[] = []; - let weatherDelayParkIds: string[] = []; - if (weekDates.includes(today)) { - // Parks within operating hours right now (for open dot — independent of ride counts) - const openTodayParks = PARKS.filter((p) => { - const dayData = data[p.id]?.[today]; - if (!dayData?.isOpen || !dayData.hoursLabel) return false; - return isWithinOperatingWindow(dayData.hoursLabel, p.timezone); - }); - openParkIds = openTodayParks.map((p) => p.id); - closingParkIds = openTodayParks - .filter((p) => { - const dayData = data[p.id]?.[today]; - return dayData?.hoursLabel - ? getOperatingStatus(dayData.hoursLabel, p.timezone) === "closing" - : false; - }) - .map((p) => p.id); - // Only fetch ride counts for parks that have queue-times coverage - const trackedParks = openTodayParks.filter((p) => QUEUE_TIMES_IDS[p.id]); - const results = await Promise.all( - trackedParks.map(async (p) => { - const coasterSet = getCoasterSet(p.id); - const result = await fetchLiveRides(QUEUE_TIMES_IDS[p.id], coasterSet, 300); - const rideCount = result ? result.rides.filter((r) => r.isOpen).length : null; - const coasterCount = result ? result.rides.filter((r) => r.isOpen && r.isCoaster).length : 0; - return { id: p.id, rideCount, coasterCount }; - }) - ); - // Parks with queue-times coverage but 0 open rides = likely weather delay - weatherDelayParkIds = results - .filter(({ rideCount }) => rideCount === 0) - .map(({ id }) => id); - rideCounts = Object.fromEntries( - results.filter(({ rideCount }) => rideCount != null && rideCount > 0).map(({ id, rideCount }) => [id, rideCount!]) - ); - coasterCounts = Object.fromEntries( - results.filter(({ coasterCount }) => coasterCount > 0).map(({ id, coasterCount }) => [id, coasterCount]) - ); - } - - return ( - - ); + return ; } diff --git a/app/park/[id]/page.tsx b/app/park/[id]/page.tsx index 93a28ce..b2e2fd6 100644 --- a/app/park/[id]/page.tsx +++ b/app/park/[id]/page.tsx @@ -1,33 +1,27 @@ -import Link from "next/link"; import { BackToCalendarLink } from "@/components/BackToCalendarLink"; import { notFound } from "next/navigation"; import { PARK_MAP } from "@/lib/parks"; -import { openDb, getParkMonthData } from "@/lib/db"; -import { scrapeRidesForDay } from "@/lib/scrapers/sixflags"; -import { fetchLiveRides } from "@/lib/scrapers/queuetimes"; -import { fetchToday } from "@/lib/scrapers/sixflags"; -import { QUEUE_TIMES_IDS } from "@/lib/queue-times-map"; -import { getCoasterSet } from "@/lib/coaster-data"; import { ParkMonthCalendar } from "@/components/ParkMonthCalendar"; import { LiveRidePanel } from "@/components/LiveRidePanel"; import type { RideStatus, RidesFetchResult } from "@/lib/scrapers/sixflags"; -import type { LiveRidesResult } from "@/lib/scrapers/queuetimes"; // used as prop type below -import { getTodayLocal, isWithinOperatingWindow } from "@/lib/env"; +import type { LiveRidesResult } from "@/lib/scrapers/queuetimes"; +import { getTodayLocal } from "@/lib/env"; + +const BACKEND_URL = process.env.BACKEND_URL ?? "http://localhost:3001"; interface PageProps { params: Promise<{ id: string }>; searchParams: Promise<{ month?: string }>; } -function parseMonthParam(param: string | undefined): { year: number; month: number } { +function parseMonthParam(param: string | undefined): string { if (param && /^\d{4}-\d{2}$/.test(param)) { const [y, m] = param.split("-").map(Number); if (y >= 2020 && y <= 2030 && m >= 1 && m <= 12) { - return { year: y, month: m }; + return param; } } - const [y, m] = getTodayLocal().split("-").map(Number); - return { year: y, month: m }; + return getTodayLocal().slice(0, 7); } export default async function ParkPage({ params, searchParams }: PageProps) { @@ -37,54 +31,30 @@ export default async function ParkPage({ params, searchParams }: PageProps) { const park = PARK_MAP.get(id); if (!park) notFound(); - const today = getTodayLocal(); - const { year, month } = parseMonthParam(monthParam); + const monthStr = parseMonthParam(monthParam); + const [year, month] = monthStr.split("-").map(Number); - const db = openDb(); - const monthData = getParkMonthData(db, id, year, month); - db.close(); + const [calendarData, ridesData] = await Promise.all([ + fetch(`${BACKEND_URL}/api/calendar/${id}/month?month=${monthStr}`, { + next: { revalidate: 300 }, + }).then((r) => r.json()), + fetch(`${BACKEND_URL}/api/parks/${id}/rides`, { + next: { revalidate: 60 }, + }).then((r) => r.json()), + ]); - const liveToday = await fetchToday(park.apiId, 300).catch(() => null); - const todayData = liveToday - ? { isOpen: liveToday.isOpen, hoursLabel: liveToday.hoursLabel ?? null, specialType: liveToday.specialType ?? null } - : monthData[today]; - const parkOpenToday = todayData?.isOpen && todayData?.hoursLabel; - - // ── Ride data: try live Queue-Times first, fall back to schedule ────────── - const queueTimesId = QUEUE_TIMES_IDS[id]; - const coasterSet = getCoasterSet(id); - - let liveRides: LiveRidesResult | null = null; - let ridesResult: RidesFetchResult | null = null; - - // Determine if we're within the 1h-before-open to 1h-after-close window. - const withinWindow = todayData?.hoursLabel - ? isWithinOperatingWindow(todayData.hoursLabel, park.timezone) - : false; - - if (queueTimesId) { - const raw = await fetchLiveRides(queueTimesId, coasterSet); - if (raw) { - // Outside the window: show the ride list but force all rides closed - liveRides = withinWindow - ? raw - : { - ...raw, - rides: raw.rides.map((r) => ({ ...r, isOpen: false, waitMinutes: 0 })), - }; - } - } - - // Weather delay: park is within operating hours but queue-times shows 0 open rides - const isWeatherDelay = - withinWindow && - liveRides !== null && - liveRides.rides.length > 0 && - liveRides.rides.every((r) => !r.isOpen); - - if (!liveRides) { - ridesResult = await scrapeRidesForDay(park.apiId, today); - } + const { monthData, today } = calendarData; + const { + parkOpenToday, + isWeatherDelay, + liveRides, + scheduleFallback: ridesResult, + }: { + parkOpenToday: boolean; + isWeatherDelay: boolean; + liveRides: LiveRidesResult | null; + scheduleFallback: RidesFetchResult | null; + } = ridesData; return (
@@ -162,13 +132,13 @@ export default async function ParkPage({ params, searchParams }: PageProps) { {liveRides ? ( ) : ( )} diff --git a/backend/src/db/queries.ts b/backend/src/db/queries.ts index 61c1fc6..f5df86a 100644 --- a/backend/src/db/queries.ts +++ b/backend/src/db/queries.ts @@ -1,11 +1,8 @@ import type Database from "better-sqlite3"; import { getDb } from "./index"; -export interface DayData { - isOpen: boolean; - hoursLabel: string | null; - specialType: string | null; -} +import type { DayData } from "../../../lib/types"; +export type { DayData }; interface DayRow { park_id: string; diff --git a/components/HomePageClient.tsx b/components/HomePageClient.tsx index 0b64b24..ba2f425 100644 --- a/components/HomePageClient.tsx +++ b/components/HomePageClient.tsx @@ -8,7 +8,7 @@ import { WeekNav } from "./WeekNav"; import { Legend } from "./Legend"; import { EmptyState } from "./EmptyState"; import { PARKS, groupByRegion } from "@/lib/parks"; -import type { DayData } from "@/lib/db"; +import type { DayData } from "@/lib/types"; const REFRESH_INTERVAL_MS = 2 * 60 * 1000; // 2 minutes const OPEN_REFRESH_BUFFER_MS = 30_000; // 30s after opening time before hitting the API diff --git a/components/MobileCardList.tsx b/components/MobileCardList.tsx index cce0e93..5958be6 100644 --- a/components/MobileCardList.tsx +++ b/components/MobileCardList.tsx @@ -1,5 +1,5 @@ import type { Park } from "@/lib/scrapers/types"; -import type { DayData } from "@/lib/db"; +import type { DayData } from "@/lib/types"; import type { Region } from "@/lib/parks"; import { ParkCard } from "./ParkCard"; diff --git a/components/ParkCard.tsx b/components/ParkCard.tsx index 3fc0bc7..2421a7d 100644 --- a/components/ParkCard.tsx +++ b/components/ParkCard.tsx @@ -1,6 +1,6 @@ import Link from "next/link"; import type { Park } from "@/lib/scrapers/types"; -import type { DayData } from "@/lib/db"; +import type { DayData } from "@/lib/types"; import { getTimezoneAbbr } from "@/lib/env"; interface ParkCardProps { diff --git a/components/ParkMonthCalendar.tsx b/components/ParkMonthCalendar.tsx index 86cbd82..274222c 100644 --- a/components/ParkMonthCalendar.tsx +++ b/components/ParkMonthCalendar.tsx @@ -1,5 +1,5 @@ import Link from "next/link"; -import type { DayData } from "@/lib/db"; +import type { DayData } from "@/lib/types"; import { getTimezoneAbbr } from "@/lib/env"; interface ParkMonthCalendarProps { diff --git a/components/WeekCalendar.tsx b/components/WeekCalendar.tsx index 40ff56e..da14821 100644 --- a/components/WeekCalendar.tsx +++ b/components/WeekCalendar.tsx @@ -1,7 +1,7 @@ import { Fragment } from "react"; import Link from "next/link"; import type { Park } from "@/lib/scrapers/types"; -import type { DayData } from "@/lib/db"; +import type { DayData } from "@/lib/types"; import type { Region } from "@/lib/parks"; import { getTodayLocal, getTimezoneAbbr } from "@/lib/env"; diff --git a/lib/db.ts b/lib/db.ts deleted file mode 100644 index b94846a..0000000 --- a/lib/db.ts +++ /dev/null @@ -1,288 +0,0 @@ -import Database from "better-sqlite3"; -import path from "path"; -import fs from "fs"; - -const DATA_DIR = path.join(process.cwd(), "data"); -const DB_PATH = path.join(DATA_DIR, "parks.db"); - -export type DbInstance = Database.Database; - -export function openDb(): Database.Database { - fs.mkdirSync(DATA_DIR, { recursive: true }); - const db = new Database(DB_PATH); - db.pragma("journal_mode = WAL"); - db.exec(` - CREATE TABLE IF NOT EXISTS park_days ( - park_id TEXT NOT NULL, - date TEXT NOT NULL, -- YYYY-MM-DD - is_open INTEGER NOT NULL DEFAULT 0, - hours_label TEXT, - special_type TEXT, -- 'passholder_preview' | null - scraped_at TEXT NOT NULL, - PRIMARY KEY (park_id, date) - ); - CREATE TABLE IF NOT EXISTS park_api_ids ( - park_id TEXT PRIMARY KEY, - api_id INTEGER NOT NULL, - api_abbreviation TEXT, - api_name TEXT, - discovered_at TEXT NOT NULL - ) - `); - // Migrate existing databases that predate the special_type column - try { - db.exec(`ALTER TABLE park_days ADD COLUMN special_type TEXT`); - } catch { - // Column already exists — safe to ignore - } - return db; -} - -export function upsertDay( - db: Database.Database, - parkId: string, - date: string, - isOpen: boolean, - hoursLabel?: string, - specialType?: string -) { - // Today and future dates: full upsert — hours can change (e.g. weather delays, - // early closures) and the dateless API endpoint now returns today's live data. - // - // Past dates: INSERT-only — never overwrite once the day has passed. - db.prepare(` - INSERT INTO park_days (park_id, date, is_open, hours_label, special_type, scraped_at) - VALUES (?, ?, ?, ?, ?, ?) - ON CONFLICT (park_id, date) DO UPDATE SET - is_open = excluded.is_open, - hours_label = excluded.hours_label, - special_type = excluded.special_type, - scraped_at = excluded.scraped_at - WHERE park_days.date >= date('now') - `).run(parkId, date, isOpen ? 1 : 0, hoursLabel ?? null, specialType ?? null, new Date().toISOString()); -} - -export interface DayData { - isOpen: boolean; - hoursLabel: string | null; - specialType: string | null; -} - -/** - * Returns scraped data for all parks across a date range. - * Shape: { parkId: { 'YYYY-MM-DD': DayData } } - * Missing dates mean that date hasn't been scraped yet (not necessarily closed). - */ -export function getDateRange( - db: Database.Database, - startDate: string, - endDate: string -): Record> { - const rows = db - .prepare( - `SELECT park_id, date, is_open, hours_label, special_type - FROM park_days - WHERE date >= ? AND date <= ?` - ) - .all(startDate, endDate) as { - park_id: string; - date: string; - is_open: number; - hours_label: string | null; - special_type: string | null; - }[]; - - const result: Record> = {}; - for (const row of rows) { - if (!result[row.park_id]) result[row.park_id] = {}; - result[row.park_id][row.date] = { - isOpen: row.is_open === 1, - hoursLabel: row.hours_label, - specialType: row.special_type, - }; - } - return result; -} - -/** - * Returns scraped DayData for a single park for an entire month. - * Shape: { 'YYYY-MM-DD': DayData } - */ -export function getParkMonthData( - db: Database.Database, - parkId: string, - year: number, - month: number, -): Record { - const prefix = `${year}-${String(month).padStart(2, "0")}`; - const rows = db - .prepare( - `SELECT date, is_open, hours_label, special_type - FROM park_days - WHERE park_id = ? AND date LIKE ? || '-%' - ORDER BY date` - ) - .all(parkId, prefix) as { - date: string; - is_open: number; - hours_label: string | null; - special_type: string | null; - }[]; - - const result: Record = {}; - for (const row of rows) { - result[row.date] = { - isOpen: row.is_open === 1, - hoursLabel: row.hours_label, - specialType: row.special_type, - }; - } - return result; -} - -/** Returns a map of parkId → boolean[] (index 0 = day 1) for a given month. */ -export function getMonthCalendar( - db: Database.Database, - year: number, - month: number -): Record { - const prefix = `${year}-${String(month).padStart(2, "0")}`; - const rows = db - .prepare( - `SELECT park_id, date, is_open - FROM park_days - WHERE date LIKE ? || '-%' - ORDER BY date` - ) - .all(prefix) as { park_id: string; date: string; is_open: number }[]; - - const result: Record = {}; - for (const row of rows) { - if (!result[row.park_id]) result[row.park_id] = []; - const day = parseInt(row.date.slice(8), 10); - result[row.park_id][day - 1] = row.is_open === 1; - } - return result; -} - -import { parseStalenessHours } from "./env"; -const STALE_AFTER_MS = parseStalenessHours(process.env.PARK_HOURS_STALENESS_HOURS, 72) * 60 * 60 * 1000; - -/** - * Returns true when the scraper should skip this park+month. - * - * Two reasons to skip: - * 1. The month is entirely in the past — the API will never return data for - * those dates again, so re-scraping wastes a call and risks nothing but - * wasted time. Historical records are preserved forever by upsertDay. - * 2. The month was scraped within the last 7 days — data is still fresh. - */ -export function isMonthScraped( - db: Database.Database, - parkId: string, - year: number, - month: number -): boolean { - // Compute the last calendar day of this month (avoids timezone issues). - const daysInMonth = new Date(year, month, 0).getDate(); - const lastDay = `${year}-${String(month).padStart(2, "0")}-${String(daysInMonth).padStart(2, "0")}`; - const today = new Date().toISOString().slice(0, 10); - - // Past month — history is locked in, no API data available, always skip. - if (lastDay < today) return true; - - // Current/future month — skip only if recently scraped. - const prefix = `${year}-${String(month).padStart(2, "0")}`; - const row = db - .prepare( - `SELECT MAX(scraped_at) AS last_scraped - FROM park_days - WHERE park_id = ? AND date LIKE ? || '-%'` - ) - .get(parkId, prefix) as { last_scraped: string | null }; - - if (!row.last_scraped) return false; - const ageMs = Date.now() - new Date(row.last_scraped).getTime(); - return ageMs < STALE_AFTER_MS; -} - -export function getApiId(db: Database.Database, parkId: string): number | null { - const row = db - .prepare("SELECT api_id FROM park_api_ids WHERE park_id = ?") - .get(parkId) as { api_id: number } | undefined; - return row?.api_id ?? null; -} - -export function setApiId( - db: Database.Database, - parkId: string, - apiId: number, - apiAbbreviation?: string, - apiName?: string -) { - db.prepare(` - INSERT INTO park_api_ids (park_id, api_id, api_abbreviation, api_name, discovered_at) - VALUES (?, ?, ?, ?, ?) - ON CONFLICT (park_id) DO UPDATE SET - api_id = excluded.api_id, - api_abbreviation = excluded.api_abbreviation, - api_name = excluded.api_name, - discovered_at = excluded.discovered_at - `).run( - parkId, - apiId, - apiAbbreviation ?? null, - apiName ?? null, - new Date().toISOString() - ); -} - -/** - * Find the next park+month to scrape. - * Priority: never-scraped first, then oldest scraped_at. - * Considers current month through monthsAhead months into the future. - */ -export function getNextScrapeTarget( - db: Database.Database, - parkIds: string[], - monthsAhead = 12 -): { parkId: string; year: number; month: number } | null { - const now = new Date(); - - const candidates: { - parkId: string; - year: number; - month: number; - lastScraped: string | null; - }[] = []; - - for (const parkId of parkIds) { - for (let i = 0; i < monthsAhead; i++) { - const d = new Date(now.getFullYear(), now.getMonth() + i, 1); - const year = d.getFullYear(); - const month = d.getMonth() + 1; - const prefix = `${year}-${String(month).padStart(2, "0")}`; - - const row = db - .prepare( - `SELECT MAX(scraped_at) AS last_scraped - FROM park_days - WHERE park_id = ? AND date LIKE ? || '-%'` - ) - .get(parkId, prefix) as { last_scraped: string | null }; - - candidates.push({ parkId, year, month, lastScraped: row.last_scraped }); - } - } - - // Never-scraped (null) first, then oldest scraped_at - candidates.sort((a, b) => { - if (!a.lastScraped && !b.lastScraped) return 0; - if (!a.lastScraped) return -1; - if (!b.lastScraped) return 1; - return a.lastScraped.localeCompare(b.lastScraped); - }); - - const top = candidates[0]; - return top ? { parkId: top.parkId, year: top.year, month: top.month } : null; -} diff --git a/lib/types.ts b/lib/types.ts new file mode 100644 index 0000000..d3bd32f --- /dev/null +++ b/lib/types.ts @@ -0,0 +1,5 @@ +export interface DayData { + isOpen: boolean; + hoursLabel: string | null; + specialType: string | null; +} diff --git a/next.config.ts b/next.config.ts index ca5a4ce..a5365d9 100644 --- a/next.config.ts +++ b/next.config.ts @@ -11,8 +11,6 @@ const CSP = [ ].join("; "); const nextConfig: NextConfig = { - // better-sqlite3 is a native module — must not be bundled by webpack - serverExternalPackages: ["better-sqlite3"], output: "standalone", async headers() { diff --git a/package-lock.json b/package-lock.json index 37f5130..71ef1c4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,16 +8,12 @@ "name": "sixflags-super-calendar", "version": "0.1.0", "dependencies": { - "better-sqlite3": "^12.8.0", - "cheerio": "^1.0.0", "next": "^15.3.0", - "playwright": "^1.59.1", "react": "^19.0.0", "react-dom": "^19.0.0" }, "devDependencies": { "@tailwindcss/postcss": "^4", - "@types/better-sqlite3": "^7.6.13", "@types/node": "^22", "@types/react": "^19", "@types/react-dom": "^19", @@ -1810,16 +1806,6 @@ "tslib": "^2.4.0" } }, - "node_modules/@types/better-sqlite3": { - "version": "7.6.13", - "resolved": "https://registry.npmjs.org/@types/better-sqlite3/-/better-sqlite3-7.6.13.tgz", - "integrity": "sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -2739,66 +2725,6 @@ "dev": true, "license": "MIT" }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/better-sqlite3": { - "version": "12.8.0", - "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-12.8.0.tgz", - "integrity": "sha512-RxD2Vd96sQDjQr20kdP+F+dK/1OUNiVOl200vKBZY8u0vTwysfolF6Hq+3ZK2+h8My9YvZhHsF+RSGZW2VYrPQ==", - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "bindings": "^1.5.0", - "prebuild-install": "^7.1.1" - }, - "engines": { - "node": "20.x || 22.x || 23.x || 24.x || 25.x" - } - }, - "node_modules/bindings": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", - "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", - "license": "MIT", - "dependencies": { - "file-uri-to-path": "1.0.0" - } - }, - "node_modules/bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "license": "MIT", - "dependencies": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, - "node_modules/boolbase": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", - "license": "ISC" - }, "node_modules/brace-expansion": { "version": "1.1.13", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", @@ -2823,30 +2749,6 @@ "node": ">=8" } }, - "node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, "node_modules/call-bind": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", @@ -2944,54 +2846,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/cheerio": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.2.0.tgz", - "integrity": "sha512-WDrybc/gKFpTYQutKIK6UvfcuxijIZfMfXaYm8NMsPQxSYvf+13fXUJ4rztGGbJcBQ/GF55gvrZ0Bc0bj/mqvg==", - "license": "MIT", - "dependencies": { - "cheerio-select": "^2.1.0", - "dom-serializer": "^2.0.0", - "domhandler": "^5.0.3", - "domutils": "^3.2.2", - "encoding-sniffer": "^0.2.1", - "htmlparser2": "^10.1.0", - "parse5": "^7.3.0", - "parse5-htmlparser2-tree-adapter": "^7.1.0", - "parse5-parser-stream": "^7.1.2", - "undici": "^7.19.0", - "whatwg-mimetype": "^4.0.0" - }, - "engines": { - "node": ">=20.18.1" - }, - "funding": { - "url": "https://github.com/cheeriojs/cheerio?sponsor=1" - } - }, - "node_modules/cheerio-select": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", - "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", - "license": "BSD-2-Clause", - "dependencies": { - "boolbase": "^1.0.0", - "css-select": "^5.1.0", - "css-what": "^6.1.0", - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3", - "domutils": "^3.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "license": "ISC" - }, "node_modules/client-only": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", @@ -3040,34 +2894,6 @@ "node": ">= 8" } }, - "node_modules/css-select": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", - "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", - "license": "BSD-2-Clause", - "dependencies": { - "boolbase": "^1.0.0", - "css-what": "^6.1.0", - "domhandler": "^5.0.2", - "domutils": "^3.0.1", - "nth-check": "^2.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/css-what": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", - "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", - "license": "BSD-2-Clause", - "engines": { - "node": ">= 6" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, "node_modules/csstype": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", @@ -3154,30 +2980,6 @@ } } }, - "node_modules/decompress-response": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", - "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", - "license": "MIT", - "dependencies": { - "mimic-response": "^3.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "license": "MIT", - "engines": { - "node": ">=4.0.0" - } - }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -3225,6 +3027,7 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "devOptional": true, "license": "Apache-2.0", "engines": { "node": ">=8" @@ -3243,61 +3046,6 @@ "node": ">=0.10.0" } }, - "node_modules/dom-serializer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", - "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", - "license": "MIT", - "dependencies": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.2", - "entities": "^4.2.0" - }, - "funding": { - "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" - } - }, - "node_modules/domelementtype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", - "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "license": "BSD-2-Clause" - }, - "node_modules/domhandler": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", - "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", - "license": "BSD-2-Clause", - "dependencies": { - "domelementtype": "^2.3.0" - }, - "engines": { - "node": ">= 4" - }, - "funding": { - "url": "https://github.com/fb55/domhandler?sponsor=1" - } - }, - "node_modules/domutils": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", - "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", - "license": "BSD-2-Clause", - "dependencies": { - "dom-serializer": "^2.0.0", - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3" - }, - "funding": { - "url": "https://github.com/fb55/domutils?sponsor=1" - } - }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -3320,28 +3068,6 @@ "dev": true, "license": "MIT" }, - "node_modules/encoding-sniffer": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.1.tgz", - "integrity": "sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw==", - "license": "MIT", - "dependencies": { - "iconv-lite": "^0.6.3", - "whatwg-encoding": "^3.1.1" - }, - "funding": { - "url": "https://github.com/fb55/encoding-sniffer?sponsor=1" - } - }, - "node_modules/end-of-stream": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", - "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", - "license": "MIT", - "dependencies": { - "once": "^1.4.0" - } - }, "node_modules/enhanced-resolve": { "version": "5.20.1", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.1.tgz", @@ -3356,18 +3082,6 @@ "node": ">=10.13.0" } }, - "node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, "node_modules/es-abstract": { "version": "1.24.1", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.1.tgz", @@ -4008,15 +3722,6 @@ "node": ">=0.10.0" } }, - "node_modules/expand-template": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", - "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", - "license": "(MIT OR WTFPL)", - "engines": { - "node": ">=6" - } - }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -4091,12 +3796,6 @@ "node": ">=16.0.0" } }, - "node_modules/file-uri-to-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", - "license": "MIT" - }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -4164,26 +3863,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/fs-constants": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", - "license": "MIT" - }, - "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -4305,12 +3984,6 @@ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, - "node_modules/github-from-package": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", - "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", - "license": "MIT" - }, "node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -4468,69 +4141,6 @@ "node": ">= 0.4" } }, - "node_modules/htmlparser2": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.1.0.tgz", - "integrity": "sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==", - "funding": [ - "https://github.com/fb55/htmlparser2?sponsor=1", - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "license": "MIT", - "dependencies": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3", - "domutils": "^3.2.2", - "entities": "^7.0.1" - } - }, - "node_modules/htmlparser2/node_modules/entities": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", - "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "BSD-3-Clause" - }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -4568,18 +4178,6 @@ "node": ">=0.8.19" } }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "license": "ISC" - }, - "node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "license": "ISC" - }, "node_modules/internal-slot": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", @@ -5506,18 +5104,6 @@ "node": ">=8.6" } }, - "node_modules/mimic-response": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", - "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/minimatch": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", @@ -5535,17 +5121,12 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/mkdirp-classic": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", - "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", - "license": "MIT" - }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -5571,12 +5152,6 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "node_modules/napi-build-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", - "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", - "license": "MIT" - }, "node_modules/napi-postinstall": { "version": "0.3.4", "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz", @@ -5680,18 +5255,6 @@ "node": "^10 || ^12 || >=14" } }, - "node_modules/node-abi": { - "version": "3.89.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.89.0.tgz", - "integrity": "sha512-6u9UwL0HlAl21+agMN3YAMXcKByMqwGx+pq+P76vii5f7hTPtKDp08/H9py6DY+cfDw7kQNTGEj/rly3IgbNQA==", - "license": "MIT", - "dependencies": { - "semver": "^7.3.5" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/node-exports-info": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/node-exports-info/-/node-exports-info-1.6.0.tgz", @@ -5721,18 +5284,6 @@ "semver": "bin/semver.js" } }, - "node_modules/nth-check": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", - "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", - "license": "BSD-2-Clause", - "dependencies": { - "boolbase": "^1.0.0" - }, - "funding": { - "url": "https://github.com/fb55/nth-check?sponsor=1" - } - }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -5856,15 +5407,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -5946,55 +5488,6 @@ "node": ">=6" } }, - "node_modules/parse5": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", - "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", - "license": "MIT", - "dependencies": { - "entities": "^6.0.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" - } - }, - "node_modules/parse5-htmlparser2-tree-adapter": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz", - "integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==", - "license": "MIT", - "dependencies": { - "domhandler": "^5.0.3", - "parse5": "^7.0.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" - } - }, - "node_modules/parse5-parser-stream": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz", - "integrity": "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==", - "license": "MIT", - "dependencies": { - "parse5": "^7.0.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" - } - }, - "node_modules/parse5/node_modules/entities": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", - "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -6041,36 +5534,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/playwright": { - "version": "1.59.1", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.59.1.tgz", - "integrity": "sha512-C8oWjPR3F81yljW9o5OxcWzfh6avkVwDD2VYdwIGqTkl+OGFISgypqzfu7dOe4QNLL2aqcWBmI3PMtLIK233lw==", - "license": "Apache-2.0", - "dependencies": { - "playwright-core": "1.59.1" - }, - "bin": { - "playwright": "cli.js" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "fsevents": "2.3.2" - } - }, - "node_modules/playwright-core": { - "version": "1.59.1", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.59.1.tgz", - "integrity": "sha512-HBV/RJg81z5BiiZ9yPzIiClYV/QMsDCKUyogwH9p3MCP6IYjUFu/MActgYAvK0oWyV9NlwM3GLBjADyWgydVyg==", - "license": "Apache-2.0", - "bin": { - "playwright-core": "cli.js" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/possible-typed-array-names": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", @@ -6110,33 +5573,6 @@ "node": "^10 || ^12 || >=14" } }, - "node_modules/prebuild-install": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", - "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", - "deprecated": "No longer maintained. Please contact the author of the relevant native addon; alternatives are available.", - "license": "MIT", - "dependencies": { - "detect-libc": "^2.0.0", - "expand-template": "^2.0.3", - "github-from-package": "0.0.0", - "minimist": "^1.2.3", - "mkdirp-classic": "^0.5.3", - "napi-build-utils": "^2.0.0", - "node-abi": "^3.3.0", - "pump": "^3.0.0", - "rc": "^1.2.7", - "simple-get": "^4.0.0", - "tar-fs": "^2.0.0", - "tunnel-agent": "^0.6.0" - }, - "bin": { - "prebuild-install": "bin.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -6159,16 +5595,6 @@ "react-is": "^16.13.1" } }, - "node_modules/pump": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", - "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", - "license": "MIT", - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -6200,30 +5626,6 @@ ], "license": "MIT" }, - "node_modules/rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", - "dependencies": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "bin": { - "rc": "cli.js" - } - }, - "node_modules/rc/node_modules/strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/react": { "version": "19.2.4", "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", @@ -6252,20 +5654,6 @@ "dev": true, "license": "MIT" }, - "node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/reflect.getprototypeof": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", @@ -6409,26 +5797,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, "node_modules/safe-push-apply": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", @@ -6464,12 +5832,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "license": "MIT" - }, "node_modules/scheduler": { "version": "0.27.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", @@ -6480,6 +5842,7 @@ "version": "7.7.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "devOptional": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -6681,51 +6044,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/simple-concat": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", - "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/simple-get": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", - "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "decompress-response": "^6.0.0", - "once": "^1.3.1", - "simple-concat": "^1.0.0" - } - }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -6756,15 +6074,6 @@ "node": ">= 0.4" } }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, "node_modules/string.prototype.includes": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz", @@ -6971,34 +6280,6 @@ "url": "https://opencollective.com/webpack" } }, - "node_modules/tar-fs": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", - "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", - "license": "MIT", - "dependencies": { - "chownr": "^1.1.1", - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^2.1.4" - } - }, - "node_modules/tar-stream": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", - "license": "MIT", - "dependencies": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/tinyglobby": { "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", @@ -7127,18 +6408,6 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", - "license": "Apache-2.0", - "dependencies": { - "safe-buffer": "^5.0.1" - }, - "engines": { - "node": "*" - } - }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -7263,15 +6532,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/undici": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.24.7.tgz", - "integrity": "sha512-H/nlJ/h0ggGC+uRL3ovD+G0i4bqhvsDOpbDv7At5eFLlj2b41L8QliGbnl2H7SnDiYhENphh1tQFJZf+MyfLsQ==", - "license": "MIT", - "engines": { - "node": ">=20.18.1" - } - }, "node_modules/undici-types": { "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", @@ -7324,34 +6584,6 @@ "punycode": "^2.1.0" } }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "license": "MIT" - }, - "node_modules/whatwg-encoding": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", - "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", - "deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation", - "license": "MIT", - "dependencies": { - "iconv-lite": "0.6.3" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/whatwg-mimetype": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", - "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", - "license": "MIT", - "engines": { - "node": ">=18" - } - }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -7467,12 +6699,6 @@ "node": ">=0.10.0" } }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "license": "ISC" - }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index d50a1b1..b9c3454 100644 --- a/package.json +++ b/package.json @@ -11,14 +11,12 @@ "test": "tsx --test tests/*.test.ts" }, "dependencies": { - "better-sqlite3": "^12.8.0", "next": "^15.3.0", "react": "^19.0.0", "react-dom": "^19.0.0" }, "devDependencies": { "@tailwindcss/postcss": "^4", - "@types/better-sqlite3": "^7.6.13", "@types/node": "^22", "@types/react": "^19", "@types/react-dom": "^19", diff --git a/tsconfig.json b/tsconfig.json index d8b9323..cd93c3b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -23,5 +23,5 @@ } }, "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], - "exclude": ["node_modules"] + "exclude": ["node_modules", "backend"] }