> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/igorek05m/daily-geogame/llms.txt
> Use this file to discover all available pages before exploring further.

# System architecture

> High-level overview of Daily GeoGame's architecture, including frontend, backend, database, and data flow

Daily GeoGame is built as a full-stack Next.js application using the App Router pattern. The architecture follows a client-server model with React Server Components and API routes.

## High-level overview

The application is structured as a monolithic Next.js app with:

* **Frontend**: React 19 components with TypeScript
* **Backend**: Next.js 15 API routes
* **Database**: MongoDB Atlas for persistent storage
* **External APIs**: RestCountries and CIA World Factbook
* **Session management**: HTTP-only cookies with UUID-based sessions

## Frontend architecture

### Component hierarchy

The main page (`app/page.tsx`) uses a suspense-wrapped component structure:

```tsx theme={null}
Home (Suspense boundary)
└── GameContent
    ├── GameHeader (stats, navigation)
    ├── WorldMap (D3-based SVG map)
    ├── GuessInput (country autocomplete)
    ├── LastHint (latest unlocked hint)
    ├── GuessList (user guesses with feedback)
    ├── HintGrid (6 hint packages)
    ├── Footer
    └── StatsModal (game results)
```

References:

* Main page: `app/page.tsx:15-119`
* Components in `app/components/`

### State management

The `useDailyGame` hook (`app/hooks/useDailyGame.ts`) centralizes all game state:

```typescript theme={null}
const {
  gameDate,        // Current game date (YYYY-MM-DD)
  dayNumber,       // Days since GAME_START_DATE
  guesses,         // Array of Country objects
  targetCountry,   // Today's answer (null until game over)
  gameOver,        // Boolean flag
  hasWon,          // Boolean flag
  hintPackages,    // Array of 6 hint packages
  loading,         // Initial load state
  userStats,       // Personal stats
  globalStats,     // Daily leaderboard stats
  submitGuess,     // Async function
  changeDate,      // Navigate to prev/next day
} = useDailyGame();
```

Key responsibilities:

* Fetches game data on mount via parallel API calls
* Manages URL query params (`?date=YYYY-MM-DD`)
* Handles guess submission with optimistic updates
* Calculates day numbers from `GAME_START_DATE`

Reference: `app/hooks/useDailyGame.ts:6-235`

### Data fetching pattern

The hook uses `Promise.all` for parallel requests on load:

```typescript theme={null}
const [progRes, gameRes, statsRes] = await Promise.all([
  fetch(`/api/progress?date=${gameDate}`),
  fetch(`/api/daily?date=${gameDate}`),
  fetch(`/api/stats?date=${gameDate}`)
]);
```

Reference: `app/hooks/useDailyGame.ts:89-93`

## Backend architecture

### API routes

Four Next.js API routes handle all server-side logic:

| Route           | Method   | Purpose                                         |
| --------------- | -------- | ----------------------------------------------- |
| `/api/daily`    | GET      | Returns daily game data (target country, hints) |
| `/api/progress` | GET/POST | Loads/saves user progress for a date            |
| `/api/stats`    | GET      | Fetches global stats for a date                 |
| `/api/country`  | GET      | Proxies RestCountries API with caching          |

All routes are located in `app/api/*/route.ts`.

### Daily game generation

The `/api/daily` route generates a new game if one doesn't exist for the requested date:

```typescript theme={null}
// 1. Check if game exists in database
let game = await collection.findOne({ date: targetDate });

if (!game) {
  // 2. Randomly select a country
  const candidate = countries[Math.floor(Math.random() * countries.length)];
  
  // 3. Fetch RestCountries data
  const details = await fetchCountryDetails(candidate.alpha2);
  
  // 4. Fetch CIA Factbook data
  const factbookData = await fetchFactbookData(fipsCode);
  
  // 5. Generate 6 hint packages
  const hintPackages = generateHintPackages(factbookData, details);
  
  // 6. Save to database
  await collection.insertOne({ date, targetCountry, hintPackages });
}
```

The algorithm retries up to 5 times if a country lacks Factbook data, falling back to Poland if all attempts fail.

Reference: `app/api/daily/route.ts:72-136`

### Hint reveal logic

Hints are progressively revealed based on guess count:

```typescript theme={null}
const guessesCount = progress?.guesses?.length || 0;
const allowedHintsCount = guessesCount + 1; // Start with 1 hint

const hints = game.hintPackages.map((pkg, index) => {
  if (isGameOver || index < allowedHintsCount) {
    return pkg; // Show full hint
  }
  return { ...pkg, hints: [{ label: "???", value: "???" }] }; // Hide
});
```

Reference: `app/api/daily/route.ts:139-156`

### Guess processing

The `/api/progress` POST endpoint calculates distance, bearing, and connection for each guess:

```typescript theme={null}
guesses = guesses.map((g) => {
  const distance = getDistanceFromLatLonInKm(g.latlng, target.latlng);
  const bearing = getBearingAngle(g.latlng, target.latlng);
  
  let connection = "none";
  if (target.alpha3Code === g.alpha3Code) connection = "guess";
  else if (target.borders.includes(g.alpha3Code)) connection = "neighbor";
  else if (target.subregion === g.subregion) connection = "subregion";
  else if (target.region === g.region) connection = "region";
  
  return { ...g, distance, bearing, connection };
});
```

Reference: `app/api/progress/route.ts:35-63`

## Database schema

### MongoDB collections

#### `daily_games`

Stores one document per game date:

```typescript theme={null}
{
  date: "2026-03-02",           // ISO date string (unique index)
  targetCountry: Country,        // Full country object
  hintPackages: HintPackage[],   // Array of 6 hints
  createdAt: Date                // Timestamp
}
```

#### `user_progress`

Tracks progress for each session + date combination:

```typescript theme={null}
{
  sessionId: "uuid-v4",          // From geo_session cookie
  date: "2026-03-02",            // ISO date string
  guesses: Country[],            // Array with distance/bearing
  won: boolean,                  // True if correct guess made
  lastPlayed: Date               // Last update timestamp
}
```

Compound index: `{ sessionId: 1, date: 1 }`

### Connection pooling

The MongoDB client uses a singleton pattern to reuse connections:

```typescript theme={null}
// app/lib/mongodb.ts
let client: MongoClient;
let clientPromise: Promise<MongoClient>;

client = new MongoClient(process.env.MONGODB_URI);
clientPromise = client.connect();

export default clientPromise;
```

All API routes import `clientPromise` to share the same connection pool.

Reference: `app/lib/mongodb.ts:1-22`

## Data flow

### User guess submission

```mermaid theme={null}
sequenceDiagram
    participant User
    participant GuessInput
    participant useDailyGame
    participant API Country
    participant API Progress
    participant MongoDB
    
    User->>GuessInput: Selects country
    GuessInput->>useDailyGame: submitGuess("France")
    useDailyGame->>API Country: GET /api/country?code=FR
    API Country->>RestCountries: fetch country details
    RestCountries-->>API Country: Country data
    API Country-->>useDailyGame: Country object
    useDailyGame->>API Progress: POST /api/progress (guesses)
    API Progress->>MongoDB: Update user_progress
    API Progress->>API Progress: Calculate distance/bearing
    MongoDB-->>API Progress: Updated progress
    API Progress-->>useDailyGame: {won, guesses}
    useDailyGame->>useDailyGame: Update state
    useDailyGame-->>User: Show guess feedback
```

### Page load sequence

1. **Initial render**: Show loading state
2. **Parallel fetch**: Request `/api/progress`, `/api/daily`, `/api/stats`
3. **Progress check**: Restore previous guesses from MongoDB
4. **Daily game**: Get target country and hints (masked if not game over)
5. **Stats fetch**: Load global player statistics
6. **State update**: Render game with all data

Reference: `app/hooks/useDailyGame.ts:84-133`

## Session management

Users are identified by a `geo_session` cookie containing a UUIDv4:

```typescript theme={null}
const cookieStore = await cookies();
let sessionId = cookieStore.get('geo_session')?.value;

if (!sessionId) {
  sessionId = uuidv4(); // Generate new session
}

response.cookies.set('geo_session', sessionId, {
  secure: process.env.NODE_ENV === 'production',
  httpOnly: true,
  sameSite: 'strict',
  maxAge: 60 * 60 * 24 * 365 * 10 // 10 years
});
```

The session cookie:

* Is created on first guess submission
* Persists for 10 years
* Links all user progress across games
* Enables anonymous stat tracking

Reference: `app/api/progress/route.ts:17-86`

## External integrations

### RestCountries API

Fetched via the `/api/country` proxy route with Next.js caching:

```typescript theme={null}
const res = await fetch(`https://restcountries.com/v3.1/alpha/${code}`, {
  cache: 'force-cache' // Indefinite cache
});
```

Reference: `app/api/country/route.ts:11-13`

### CIA World Factbook

Fetched server-side during game generation. The `fetchFactbookData` function tries 11 regional endpoints:

```typescript theme={null}
for (const region of REGIONS) {
  const url = `${BASE_URL}/${region}/${fipsCode}.json`;
  const response = await axios.get(url);
  if (response.status === 200) return response.data;
}
```

No caching is applied, but games are stored in MongoDB after generation.

Reference: `app/lib/factbook.ts:28-42`

## Performance considerations

* **Parallel fetching**: All initial data loads use `Promise.all`
* **Static map data**: SVG map bundled at build time
* **API caching**: RestCountries responses cached indefinitely
* **Database indexes**: Compound index on `sessionId + date`
* **Connection pooling**: Shared MongoDB client across requests
* **Suspense boundaries**: Prevent layout shifts during loading

## Error handling

The app gracefully degrades when external services fail:

* **MongoDB unavailable**: Game generation works in-memory
* **RestCountries timeout**: Falls back to local `countries.json`
* **Factbook missing**: Retries with different countries
* **Hint generation error**: Returns empty array

Reference: `app/api/daily/route.ts:40-44`
