# luobinger/perkradar architecture

> Architecture diagram of the GitHub repository luobinger/perkradar, generated by GitDiagram from its file tree, README and a sample of source files (last updated 2026-11-13). It is an AI-made overview: check details against the source.

- Interactive diagram: https://gitdiagram.com/luobinger/perkradar
- Repository: https://github.com/luobinger/perkradar

## Overview

PerkRadar aggregates fixed public deal feeds into a local SQLite-backed board. Its main workflow runs scheduled or manual refreshes, parses and classifies source items, retains valid results, and serves searchable/filterable deal and health data; users can save favorites and claimed items in browser storage. A separate documented capability publishes merged snapshots for a static viewer. The graph reflects README behavior and shown imports/calls; scheduler, storage, and snapshot publishing internals were not sampled, so those links rely on README statements where indicated.

Main entry points: [`src/components/deal-board.tsx`](https://github.com/luobinger/perkradar/blob/main/src/components/deal-board.tsx) (Deal board UI), [`src/lib/refresh.ts`](https://github.com/luobinger/perkradar/blob/main/src/lib/refresh.ts) (Refresh orchestration), [`src/lib/sources.ts`](https://github.com/luobinger/perkradar/blob/main/src/lib/sources.ts) (Source fetching), [`src/lib/board.ts`](https://github.com/luobinger/perkradar/blob/main/src/lib/board.ts) (Board state assembly)

## Diagram (Mermaid)

```mermaid
flowchart TD

subgraph group_collection["Collection and refresh"]
  node_scheduler["Refresh scheduler<br/>[scheduler.ts]"]
  node_manual_api["Manual refresh API<br/>[route.ts]"]
  node_refresh["Refresh orchestration<br/>[refresh.ts]"]
  node_sources["Source fetching<br/>[sources.ts]"]
  node_source_defs["Fixed source definitions<br/>[source-defs.ts]"]
  node_parsers["Feed parsers"]
end

subgraph group_data["Deal data and status"]
  node_retention["Retention rules<br/>[retention.ts]"]
  node_classification["Deal classification<br/>[classify.ts]"]
  node_sqlite[("SQLite database<br/>[db.ts]")]
  node_board_state["Board state assembly<br/>[board.ts]"]
  node_health["Health and freshness<br/>[health.ts]"]
end

subgraph group_web["Web board and access"]
  node_deals_api["Deals API<br/>[route.ts]"]
  node_health_api["Health API<br/>[route.ts]"]
  node_auth["Authentication<br/>[auth.ts]"]
  node_session_api["Session API<br/>[route.ts]"]
  node_home["Home page<br/>[page.tsx]"]
  node_board_ui["Deal board UI<br/>[deal-board.tsx]"]
  node_board_client["Board data client<br/>[board-client.ts]"]
  node_filter["Search and filters<br/>[filter-deals.ts]"]
  node_personal[("Browser preferences")]
end

subgraph group_snapshot["Static snapshot viewer"]
  node_snapshot_publisher["Snapshot publisher"]
  node_snapshot_files["Published snapshot files"]
  node_snapshot_model["Snapshot model<br/>[model.ts]"]
  node_snapshot_ui["Static viewer<br/>[viewer.ts]"]
end

node_user(("Browser user"))
node_feeds["Public feeds"]

node_scheduler -.->|"triggers rounds"| node_refresh
node_manual_api -->|"checks access"| node_auth
node_manual_api -->|"runs refresh"| node_refresh
node_refresh -->|"fetches sources"| node_sources
node_sources -->|"uses definitions"| node_source_defs
node_sources -.->|"requests feeds"| node_feeds
node_sources -.->|"parses responses"| node_parsers
node_refresh -->|"classifies items"| node_classification
node_refresh -->|"applies retention"| node_retention
node_refresh -->|"stores round data"| node_sqlite
node_board_state -->|"reads deal data"| node_sqlite
node_board_state -->|"classifies titles"| node_classification
node_deals_api -->|"checks access"| node_auth
node_deals_api -->|"gets board state"| node_board_state
node_health_api -->|"checks access"| node_auth
node_health_api -->|"reads board state"| node_board_state
node_health_api -->|"reports health"| node_health
node_session_api -->|"creates sessions"| node_auth
node_home -->|"checks page access"| node_auth
node_home -->|"loads board"| node_board_state
node_home -->|"renders board"| node_board_ui
node_board_ui -->|"shows and filters"| node_user
node_board_ui -->|"polls board data"| node_board_client
node_board_ui -->|"filters deals"| node_filter
node_board_ui -->|"loads preferences"| node_personal
node_board_client -.->|"requests pages"| node_deals_api
node_board_ui -.->|"requests refresh"| node_manual_api
node_snapshot_publisher -.->|"collects feed results"| node_sources
node_snapshot_publisher -.->|"publishes snapshots"| node_snapshot_files
node_snapshot_ui -->|"loads and presents"| node_snapshot_model
node_snapshot_model -.->|"reads snapshot data"| node_snapshot_files
node_user -.->|"views published deals"| node_snapshot_ui

click node_scheduler "https://github.com/luobinger/perkradar/blob/main/src/lib/scheduler.ts"
click node_manual_api "https://github.com/luobinger/perkradar/blob/main/src/app/api/refresh/route.ts"
click node_refresh "https://github.com/luobinger/perkradar/blob/main/src/lib/refresh.ts"
click node_sources "https://github.com/luobinger/perkradar/blob/main/src/lib/sources.ts"
click node_source_defs "https://github.com/luobinger/perkradar/blob/main/src/lib/source-defs.ts"
click node_parsers "https://github.com/luobinger/perkradar/tree/main/src/lib/parse"
click node_retention "https://github.com/luobinger/perkradar/blob/main/src/lib/retention.ts"
click node_classification "https://github.com/luobinger/perkradar/blob/main/src/lib/classify.ts"
click node_sqlite "https://github.com/luobinger/perkradar/blob/main/src/lib/db.ts"
click node_board_state "https://github.com/luobinger/perkradar/blob/main/src/lib/board.ts"
click node_health "https://github.com/luobinger/perkradar/blob/main/src/lib/health.ts"
click node_deals_api "https://github.com/luobinger/perkradar/blob/main/src/app/api/deals/route.ts"
click node_health_api "https://github.com/luobinger/perkradar/blob/main/src/app/api/health/route.ts"
click node_auth "https://github.com/luobinger/perkradar/blob/main/src/lib/auth.ts"
click node_session_api "https://github.com/luobinger/perkradar/blob/main/src/app/api/session/route.ts"
click node_home "https://github.com/luobinger/perkradar/blob/main/src/app/page.tsx"
click node_board_ui "https://github.com/luobinger/perkradar/blob/main/src/components/deal-board.tsx"
click node_board_client "https://github.com/luobinger/perkradar/blob/main/src/lib/board-client.ts"
click node_filter "https://github.com/luobinger/perkradar/blob/main/src/lib/filter-deals.ts"
click node_personal "https://github.com/luobinger/perkradar/blob/main/src/lib/personal-storage.ts"
click node_snapshot_publisher "https://github.com/luobinger/perkradar/blob/main/src/lib/snapshot-publish.ts"
click node_snapshot_model "https://github.com/luobinger/perkradar/blob/main/snapshot-viewer/model.ts"
click node_snapshot_ui "https://github.com/luobinger/perkradar/blob/main/snapshot-viewer/viewer.ts"

classDef toneNeutral fill:#f8fafc,stroke:#334155,stroke-width:1.5px,color:#0f172a
classDef toneBlue fill:#dbeafe,stroke:#2563eb,stroke-width:1.5px,color:#172554
classDef toneAmber fill:#fef3c7,stroke:#d97706,stroke-width:1.5px,color:#78350f
classDef toneMint fill:#dcfce7,stroke:#16a34a,stroke-width:1.5px,color:#14532d
classDef toneRose fill:#ffe4e6,stroke:#e11d48,stroke-width:1.5px,color:#881337
classDef toneIndigo fill:#e0e7ff,stroke:#4f46e5,stroke-width:1.5px,color:#312e81
classDef toneTeal fill:#ccfbf1,stroke:#0f766e,stroke-width:1.5px,color:#134e4a
class node_scheduler,node_manual_api,node_refresh,node_sources,node_source_defs,node_parsers,node_user toneBlue
class node_retention,node_classification,node_sqlite,node_board_state,node_health toneAmber
class node_deals_api,node_health_api,node_auth,node_session_api,node_home,node_board_ui,node_board_client,node_filter,node_personal toneMint
class node_snapshot_publisher,node_snapshot_files,node_snapshot_model,node_snapshot_ui toneRose
class node_feeds toneIndigo
```

## Components

### Collection and refresh

- [Refresh scheduler](https://github.com/luobinger/perkradar/blob/main/src/lib/scheduler.ts): `src/lib/scheduler.ts`
- [Manual refresh API](https://github.com/luobinger/perkradar/blob/main/src/app/api/refresh/route.ts): `src/app/api/refresh/route.ts`
- [Refresh orchestration](https://github.com/luobinger/perkradar/blob/main/src/lib/refresh.ts): `src/lib/refresh.ts`
- [Source fetching](https://github.com/luobinger/perkradar/blob/main/src/lib/sources.ts): `src/lib/sources.ts`
- [Fixed source definitions](https://github.com/luobinger/perkradar/blob/main/src/lib/source-defs.ts): `src/lib/source-defs.ts`
- [Feed parsers](https://github.com/luobinger/perkradar/tree/main/src/lib/parse): `src/lib/parse`

### Deal data and status

- [Retention rules](https://github.com/luobinger/perkradar/blob/main/src/lib/retention.ts): `src/lib/retention.ts`
- [Deal classification](https://github.com/luobinger/perkradar/blob/main/src/lib/classify.ts): `src/lib/classify.ts`
- [SQLite database](https://github.com/luobinger/perkradar/blob/main/src/lib/db.ts): `src/lib/db.ts`
- [Board state assembly](https://github.com/luobinger/perkradar/blob/main/src/lib/board.ts): `src/lib/board.ts`
- [Health and freshness](https://github.com/luobinger/perkradar/blob/main/src/lib/health.ts): `src/lib/health.ts`

### Web board and access

- [Deals API](https://github.com/luobinger/perkradar/blob/main/src/app/api/deals/route.ts): `src/app/api/deals/route.ts`
- [Health API](https://github.com/luobinger/perkradar/blob/main/src/app/api/health/route.ts): `src/app/api/health/route.ts`
- [Authentication](https://github.com/luobinger/perkradar/blob/main/src/lib/auth.ts): `src/lib/auth.ts`
- [Session API](https://github.com/luobinger/perkradar/blob/main/src/app/api/session/route.ts): `src/app/api/session/route.ts`
- [Home page](https://github.com/luobinger/perkradar/blob/main/src/app/page.tsx): `src/app/page.tsx`
- [Deal board UI](https://github.com/luobinger/perkradar/blob/main/src/components/deal-board.tsx): `src/components/deal-board.tsx`
- [Board data client](https://github.com/luobinger/perkradar/blob/main/src/lib/board-client.ts): `src/lib/board-client.ts`
- [Search and filters](https://github.com/luobinger/perkradar/blob/main/src/lib/filter-deals.ts): `src/lib/filter-deals.ts`
- [Browser preferences](https://github.com/luobinger/perkradar/blob/main/src/lib/personal-storage.ts): `src/lib/personal-storage.ts`

### Static snapshot viewer

- [Snapshot publisher](https://github.com/luobinger/perkradar/blob/main/src/lib/snapshot-publish.ts): `src/lib/snapshot-publish.ts`
- **Published snapshot files**
- [Snapshot model](https://github.com/luobinger/perkradar/blob/main/snapshot-viewer/model.ts): `snapshot-viewer/model.ts`
- [Static viewer](https://github.com/luobinger/perkradar/blob/main/snapshot-viewer/viewer.ts): `snapshot-viewer/viewer.ts`

### Other components

- **Browser user**
- **Public feeds**

## Connections

- Refresh scheduler → Refresh orchestration: triggers rounds (evidence: `README.md`)
- Manual refresh API → Authentication: checks access (evidence: `src/app/api/refresh/route.ts`)
- Manual refresh API → Refresh orchestration: runs refresh (evidence: `src/app/api/refresh/route.ts`)
- Refresh orchestration → Source fetching: fetches sources (evidence: `src/lib/refresh.ts`)
- Source fetching → Fixed source definitions: uses definitions (evidence: `src/lib/board.ts`)
- Source fetching → Public feeds: requests feeds (evidence: `README.md`)
- Source fetching → Feed parsers: parses responses (evidence: `README.md`)
- Refresh orchestration → Deal classification: classifies items (evidence: `src/lib/refresh.ts`)
- Refresh orchestration → Retention rules: applies retention (evidence: `src/lib/refresh.ts`)
- Refresh orchestration → SQLite database: stores round data (evidence: `src/lib/refresh.ts`)
- Board state assembly → SQLite database: reads deal data (evidence: `src/lib/board.ts`)
- Board state assembly → Deal classification: classifies titles (evidence: `src/lib/board.ts`)
- Deals API → Authentication: checks access (evidence: `src/app/api/deals/route.ts`)
- Deals API → Board state assembly: gets board state (evidence: `src/app/api/deals/route.ts`)
- Health API → Authentication: checks access (evidence: `src/app/api/health/route.ts`)
- Health API → Board state assembly: reads board state (evidence: `src/app/api/health/route.ts`)
- Health API → Health and freshness: reports health (evidence: `src/app/api/health/route.ts`)
- Session API → Authentication: creates sessions (evidence: `src/app/api/session/route.ts`)
- Home page → Authentication: checks page access (evidence: `src/app/page.tsx`)
- Home page → Board state assembly: loads board (evidence: `src/app/page.tsx`)
- Home page → Deal board UI: renders board (evidence: `src/app/page.tsx`)
- Deal board UI → Browser user: shows and filters (evidence: `README.md`)
- Deal board UI → Board data client: polls board data (evidence: `src/components/deal-board.tsx`)
- Deal board UI → Search and filters: filters deals (evidence: `src/components/deal-board.tsx`)
- Deal board UI → Browser preferences: loads preferences (evidence: `src/components/deal-board.tsx`)
- Board data client → Deals API: requests pages (evidence: `README.md`)
- Deal board UI → Manual refresh API: requests refresh (evidence: `README.md`)
- Snapshot publisher → Source fetching: collects feed results (evidence: `README.md`)
- Snapshot publisher → Published snapshot files: publishes snapshots (evidence: `README.md`)
- Static viewer → Snapshot model: loads and presents (evidence: `snapshot-viewer/viewer.ts`)
- Snapshot model → Published snapshot files: reads snapshot data (evidence: `README.md`)
- Browser user → Static viewer: views published deals (evidence: `README.md`)
