mirror of
https://github.com/tiennm99/vngeoguessr.git
synced 2026-10-11 03:13:56 +00:00
- panoramas + pano_provinces tables replace 28MB bundled JSON; pano-index.js now draws via cached COUNT + ORDER BY id OFFSET with rejection sampling, composite (province,id)/(district,id) indexes support the skip - scripts/seed-pano-db.mjs validates pipeline artifacts (the old real-data vitest invariants, extracted to scripts/lib/pano-artifacts.mjs), stages into panoramas_next, verifies, renames into place in one transaction, keeps panoramas_old as backup; --province reseeds in place; --check validates only - pipeline writes gitignored data-build/panos/; pano barrel removed - tests run against PGlite mocked in at the @neondatabase/serverless boundary, mirroring the fake-upstash pattern; fixtures replace real data - infrastructure errors rethrow instead of reading as missing coverage; session ids via crypto.randomUUID (uuid package dropped)
4.8 KiB
4.8 KiB
Tech Stack
Frontend Framework
- Next.js 16: React-based full-stack framework with App Router
- React 19.2: Component architecture
- Tailwind CSS 4: Utility-first CSS framework for styling
Street View & Mapping
- Mapillary vector tiles: the z14
imagelayer is the index source. Read offline byscripts/build-pano-index.mjswith@mapbox/vector-tile+pbf, against a 50,000 requests/day cap - Mapillary Graph API: one call per round -- lookup by image id, ~230ms.
Its
/images?bbox=search is deliberately off the game path: it returns HTTP 500 in every dense district, because it counts images inside the box before applying the limit. See the header ofsrc/lib/mapillary.js.api/debug/mapillary/route.jskeeps the search as a diagnostic, and will reproduce those 500s - Leaflet: Interactive mapping library for guess placement
- OpenStreetMap: Map tile provider for base maps; Nominatim supplies the
administrative boundaries via
scripts/build-region-boundaries.mjs - @photo-sphere-viewer/core: 360° panorama viewer
Geographic Processing
- @turf/turf: distance, point-in-polygon, union, simplify, and point-to-line distance -- used both at runtime and by the offline builds
- Generated region tree:
src/data/regions/holds the nodes andsrc/data/boundaries/<province>/the simplified outlines. The per-province panorama indexes live in Postgres (see below), seeded from local pipeline artifacts indata-build/panos/(gitignored) - Client/server split:
src/lib/regions.jsis the client-safe view and imports nothing fromsrc/lib/pano-index.jsorsrc/lib/pano-db.js. That boundary is enforced by an import-graph walk intests/regions.test.js-- the panorama rows are exact answers - Server-side Calculations: All geographic processing on backend
Data Storage & Session Management
- Neon Postgres (HTTP): The panorama index -- 424k rows of id, province,
district, lat, lng -- read via
@neondatabase/serverless(DATABASE_URLorPOSTGRES_URL). One random-draw query per round, count queries cached per process. Seeded byscripts/seed-pano-db.mjs, which stages intopanoramas_next, verifies, then renames into place, keeping the previous generation aspanoramas_old - Upstash Redis (REST): Session and leaderboard storage via
@upstash/redisSDK (REST client, no sockets) - Credential Flexibility: Accepts
UPSTASH_REDIS_REST_URL+UPSTASH_REDIS_REST_TOKEN(vanilla Upstash) orKV_REST_API_URL+KV_REST_API_TOKEN(Vercel Marketplace aliases) - Multi-tenant Key Prefix: All physical keys carry
KEY_PREFIX(defaultvngeoguessr:) to safely share Upstash DB with other Vercel projects. Prefix applied centrally insrc/lib/upstash.js; callers use logical keys only. - Key Namespaces:
session:{id}(30-min TTL),leaderboard:{scope},distance:{scope}(no expiry).{scope}isvietnamfor the country andcity:{regionCode}for every province and district -- thecity:segment is kept so existing scores stay addressable - Sorted Sets: Leaderboard ranking with automatic trimming (top 200)
- UUID v4: Session identifier generation via built-in
crypto.randomUUID() - 30-minute Session Expiry: Automatic TTL-based cleanup
UI Components & Styling
- shadcn/ui: Complete component library with "new-york" style
- Radix UI: Headless component primitives -- dialog, label, slot, tabs, plus accordion (province expansion) and select (region picker)
- Lucide React: Icon library
- class-variance-authority: Component variant management
- tailwind-merge + clsx: Dynamic class name handling
Testing
- Vitest: Test runner for the logic in
src/lib/, the API routes, the generated region data, and the leaderboard migration - In-memory Upstash fake: Default Redis backing store, no service required
- PGlite: In-process Postgres (WASM) mocked in at the
@neondatabase/serverlessboundary, so the panorama queries run against real Postgres semantics with no service - SRH + Redis (Docker): Optional lane running the same tests against real Redis
- Playwright: Chromium smoke tests for the UI (
tests/e2e/), with every API call and the panorama image stubbed at the browser boundary -- no services, no env vars
Development & Analytics
- ESLint: Code linting with Next.js configuration
- Turbopack: Development server bundler
- @vercel/analytics: User analytics tracking
- @vercel/speed-insights: Performance monitoring
Key Dependencies
- @upstash/redis: REST-based Upstash client; no socket pooling, works in edge compute and serverless
- JavaScript Only: No TypeScript - pure JavaScript implementation
- Individual Parameters: Functions use separate parameters instead of object destructuring
- Note:
redis(node-redis) package not used (replaced by Upstash REST SDK)