485 lines
22 KiB
Markdown
485 lines
22 KiB
Markdown
# Felt — Repository Structure
|
|
|
|
## Monorepo Layout
|
|
|
|
```
|
|
felt/
|
|
├── .forgejo/
|
|
│ └── workflows/ # CI/CD pipelines
|
|
│ ├── lint.yml
|
|
│ ├── test.yml
|
|
│ ├── build.yml
|
|
│ └── release.yml
|
|
│
|
|
├── cmd/ # Go entrypoints (main packages)
|
|
│ ├── felt-core/ # Core cloud service
|
|
│ │ └── main.go
|
|
│ ├── felt-leaf/ # Leaf node service
|
|
│ │ └── main.go
|
|
│ └── felt-cli/ # Management CLI tool
|
|
│ └── main.go
|
|
│
|
|
├── internal/ # Private Go packages (not importable externally)
|
|
│ ├── auth/ # Authentication (PIN, JWT, OIDC)
|
|
│ │ ├── jwt.go
|
|
│ │ ├── pin.go
|
|
│ │ ├── oidc.go
|
|
│ │ ├── middleware.go
|
|
│ │ └── roles.go
|
|
│ │
|
|
│ ├── tournament/ # Tournament engine (core domain)
|
|
│ │ ├── engine.go # Clock, state machine, level progression
|
|
│ │ ├── clock.go # Millisecond-precision countdown
|
|
│ │ ├── blinds.go # Blind structure, wizard, templates
|
|
│ │ ├── financial.go # Buy-in, rebuy, add-on, bounty, prize pool
|
|
│ │ ├── payout.go # Prize calculation, ICM, chop
|
|
│ │ ├── receipt.go # Transaction receipts
|
|
│ │ └── engine_test.go
|
|
│ │
|
|
│ ├── player/ # Player management
|
|
│ │ ├── database.go # CRUD, search, import
|
|
│ │ ├── tournament.go # In-tournament player state
|
|
│ │ ├── bust.go # Bust-out, undo, ranking
|
|
│ │ └── player_test.go
|
|
│ │
|
|
│ ├── table/ # Table & seating
|
|
│ │ ├── seating.go # Random seat, manual moves
|
|
│ │ ├── balance.go # Auto-balance algorithm
|
|
│ │ ├── layout.go # Table definitions, blueprints
|
|
│ │ └── balance_test.go
|
|
│ │
|
|
│ ├── league/ # League & points system
|
|
│ │ ├── league.go # League, season, standings
|
|
│ │ ├── formula.go # Formula parser & evaluator (sandboxed)
|
|
│ │ ├── formula_test.go
|
|
│ │ └── achievements.go
|
|
│ │
|
|
│ ├── events/ # Events & automation engine
|
|
│ │ ├── engine.go # Trigger → condition → action pipeline
|
|
│ │ ├── triggers.go # Trigger definitions
|
|
│ │ ├── actions.go # Action executors (sound, message, view change)
|
|
│ │ └── rules.go # Rule storage & builder
|
|
│ │
|
|
│ ├── display/ # Display node management
|
|
│ │ ├── registry.go # Node discovery, heartbeat, status
|
|
│ │ ├── routing.go # View assignment, cycling, multi-tournament
|
|
│ │ └── protocol.go # WebSocket protocol (state push, assign, heartbeat)
|
|
│ │
|
|
│ ├── content/ # Digital signage & info screen system
|
|
│ │ ├── editor.go # WYSIWYG content CRUD (templates, custom content)
|
|
│ │ ├── playlist.go # Playlist management, scheduling, priority
|
|
│ │ ├── renderer.go # Template → HTML/CSS bundle generation
|
|
│ │ ├── ai.go # AI-assisted content generation (prompt → layout)
|
|
│ │ └── templates/ # Built-in content templates (events, menus, promos)
|
|
│ │
|
|
│ ├── import/ # Data import from external systems
|
|
│ │ ├── tdd.go # TDD XML parser + converter (templates, players, history)
|
|
│ │ ├── csv.go # Generic CSV import (players, results)
|
|
│ │ └── wizard.go # Import wizard logic (preview, mapping, confirmation)
|
|
│ │
|
|
│ ├── sync/ # Leaf ↔ Core sync engine
|
|
│ │ ├── publisher.go # Leaf: publish events to NATS JetStream
|
|
│ │ ├── consumer.go # Core: consume events, upsert to PostgreSQL
|
|
│ │ ├── reconcile.go # Conflict resolution, reverse sync
|
|
│ │ └── messages.go # Message type definitions
|
|
│ │ # NOTE: No relay needed — Netbird reverse proxy tunnels
|
|
│ │ # player traffic directly to Leaf via WireGuard
|
|
│ │
|
|
│ ├── websocket/ # WebSocket hub
|
|
│ │ ├── hub.go # Connection manager, broadcast, rooms
|
|
│ │ ├── operator.go # Operator channel (full control)
|
|
│ │ ├── display.go # Display node channel (view data)
|
|
│ │ ├── player.go # Player channel (read-only)
|
|
│ │ └── hub_test.go
|
|
│ │
|
|
│ ├── api/ # HTTP API layer
|
|
│ │ ├── router.go # chi router setup, middleware chain
|
|
│ │ ├── tournaments.go # Tournament endpoints
|
|
│ │ ├── players.go # Player endpoints (in-tournament + database)
|
|
│ │ ├── tables.go # Table/seating endpoints
|
|
│ │ ├── displays.go # Display node endpoints
|
|
│ │ ├── leagues.go # League/standings endpoints
|
|
│ │ ├── export.go # Export endpoints (CSV, JSON, HTML)
|
|
│ │ └── health.go # Health check, version, status
|
|
│ │
|
|
│ ├── store/ # Database access layer
|
|
│ │ ├── libsql.go # LibSQL connection, migrations (Leaf)
|
|
│ │ ├── postgres.go # PostgreSQL connection, migrations (Core)
|
|
│ │ ├── migrations/ # SQL migration files
|
|
│ │ │ ├── leaf/
|
|
│ │ │ │ ├── 001_initial.sql
|
|
│ │ │ │ └── ...
|
|
│ │ │ └── core/
|
|
│ │ │ ├── 001_initial.sql
|
|
│ │ │ └── ...
|
|
│ │ ├── queries/ # SQL queries (sqlc or hand-written)
|
|
│ │ │ ├── tournaments.sql
|
|
│ │ │ ├── players.sql
|
|
│ │ │ ├── tables.sql
|
|
│ │ │ ├── transactions.sql
|
|
│ │ │ └── leagues.sql
|
|
│ │ └── store.go # Store interface (implemented by libsql + postgres)
|
|
│ │
|
|
│ ├── audit/ # Audit trail
|
|
│ │ ├── logger.go # Append-only audit record writer
|
|
│ │ └── types.go # Audit event types
|
|
│ │
|
|
│ ├── audio/ # Sound playback (Leaf only)
|
|
│ │ └── player.go # mpv subprocess, sound queue
|
|
│ │
|
|
│ └── config/ # Configuration
|
|
│ ├── config.go # Typed config struct, env var loading
|
|
│ ├── defaults.go # Default values
|
|
│ └── validate.go # Config validation
|
|
│
|
|
├── pkg/ # Public Go packages (shared types, importable)
|
|
│ ├── models/ # Shared domain models
|
|
│ │ ├── tournament.go
|
|
│ │ ├── player.go
|
|
│ │ ├── table.go
|
|
│ │ ├── transaction.go
|
|
│ │ ├── league.go
|
|
│ │ ├── display.go
|
|
│ │ └── event.go
|
|
│ │
|
|
│ └── protocol/ # WebSocket message types (shared by Go + JS)
|
|
│ ├── messages.go
|
|
│ └── messages.json # JSON Schema (for JS client codegen)
|
|
│
|
|
├── web/ # Frontend (SvelteKit)
|
|
│ ├── operator/ # Operator UI (mobile-first, served by Leaf + Core)
|
|
│ │ ├── src/
|
|
│ │ │ ├── lib/
|
|
│ │ │ │ ├── components/ # Reusable UI components
|
|
│ │ │ │ │ ├── Clock.svelte
|
|
│ │ │ │ │ ├── PlayerList.svelte
|
|
│ │ │ │ │ ├── SeatingChart.svelte
|
|
│ │ │ │ │ ├── BlindsSchedule.svelte
|
|
│ │ │ │ │ ├── QuickActions.svelte
|
|
│ │ │ │ │ ├── Toast.svelte
|
|
│ │ │ │ │ ├── Badge.svelte
|
|
│ │ │ │ │ ├── Card.svelte
|
|
│ │ │ │ │ ├── DataTable.svelte
|
|
│ │ │ │ │ └── ...
|
|
│ │ │ │ ├── stores/ # Svelte stores (WebSocket state)
|
|
│ │ │ │ │ ├── ws.ts # WebSocket connection manager
|
|
│ │ │ │ │ ├── tournament.ts # Tournament state store
|
|
│ │ │ │ │ ├── players.ts
|
|
│ │ │ │ │ ├── tables.ts
|
|
│ │ │ │ │ └── displays.ts
|
|
│ │ │ │ ├── api/ # REST API client
|
|
│ │ │ │ │ └── client.ts
|
|
│ │ │ │ ├── theme/ # Design system
|
|
│ │ │ │ │ ├── tokens.css # CSS custom properties (Catppuccin Mocha)
|
|
│ │ │ │ │ ├── typography.css
|
|
│ │ │ │ │ ├── components.css
|
|
│ │ │ │ │ └── themes.ts # Theme definitions (dark/light/custom)
|
|
│ │ │ │ └── utils/
|
|
│ │ │ │ ├── format.ts # Money, chip count, time formatting
|
|
│ │ │ │ └── localFirst.ts # Optional: try felt.local before proxy URL
|
|
│ │ │ ├── routes/
|
|
│ │ │ │ ├── +layout.svelte # Root layout (persistent header, nav)
|
|
│ │ │ │ ├── +page.svelte # Dashboard / tournament selector
|
|
│ │ │ │ ├── login/
|
|
│ │ │ │ │ └── +page.svelte # PIN / SSO login
|
|
│ │ │ │ ├── tournament/[id]/
|
|
│ │ │ │ │ ├── +layout.svelte # Tournament layout (header + tabs)
|
|
│ │ │ │ │ ├── +page.svelte # Overview dashboard
|
|
│ │ │ │ │ ├── players/
|
|
│ │ │ │ │ │ └── +page.svelte # Player list + actions
|
|
│ │ │ │ │ ├── tables/
|
|
│ │ │ │ │ │ └── +page.svelte # Seating chart
|
|
│ │ │ │ │ ├── clock/
|
|
│ │ │ │ │ │ └── +page.svelte # Clock control
|
|
│ │ │ │ │ ├── financials/
|
|
│ │ │ │ │ │ └── +page.svelte # Prize pool, transactions
|
|
│ │ │ │ │ └── settings/
|
|
│ │ │ │ │ └── +page.svelte # Tournament config
|
|
│ │ │ │ ├── displays/
|
|
│ │ │ │ │ └── +page.svelte # Display node management
|
|
│ │ │ │ ├── players/
|
|
│ │ │ │ │ └── +page.svelte # Player database
|
|
│ │ │ │ ├── leagues/
|
|
│ │ │ │ │ └── +page.svelte # League management
|
|
│ │ │ │ └── settings/
|
|
│ │ │ │ └── +page.svelte # Venue settings
|
|
│ │ │ └── app.html
|
|
│ │ ├── static/
|
|
│ │ │ ├── sounds/ # Default sound files
|
|
│ │ │ └── fonts/ # Inter, JetBrains Mono (self-hosted)
|
|
│ │ ├── svelte.config.js
|
|
│ │ ├── vite.config.js
|
|
│ │ ├── tailwind.config.js # Catppuccin color tokens
|
|
│ │ ├── package.json
|
|
│ │ └── tsconfig.json
|
|
│ │
|
|
│ ├── player/ # Player mobile PWA (shared components with operator)
|
|
│ │ ├── src/
|
|
│ │ │ ├── lib/
|
|
│ │ │ │ ├── components/ # Subset of operator components
|
|
│ │ │ │ ├── stores/
|
|
│ │ │ │ │ └── ws.ts # WebSocket with smart routing
|
|
│ │ │ │ └── theme/ # Shared theme tokens
|
|
│ │ │ ├── routes/
|
|
│ │ │ │ ├── +layout.svelte
|
|
│ │ │ │ ├── +page.svelte # Clock + blinds (default view)
|
|
│ │ │ │ ├── schedule/
|
|
│ │ │ │ │ └── +page.svelte # Blind schedule
|
|
│ │ │ │ ├── rankings/
|
|
│ │ │ │ │ └── +page.svelte # Live rankings
|
|
│ │ │ │ ├── payouts/
|
|
│ │ │ │ │ └── +page.svelte # Prize structure
|
|
│ │ │ │ ├── league/
|
|
│ │ │ │ │ └── +page.svelte # League standings
|
|
│ │ │ │ └── me/
|
|
│ │ │ │ └── +page.svelte # Personal status (PIN-gated)
|
|
│ │ │ └── app.html
|
|
│ │ ├── static/
|
|
│ │ │ └── manifest.json # PWA manifest
|
|
│ │ ├── svelte.config.js
|
|
│ │ ├── package.json
|
|
│ │ └── tsconfig.json
|
|
│ │
|
|
│ └── display/ # Display node views (vanilla HTML/CSS/JS)
|
|
│ ├── views/
|
|
│ │ ├── clock/
|
|
│ │ │ ├── index.html
|
|
│ │ │ ├── style.css
|
|
│ │ │ └── clock.js
|
|
│ │ ├── rankings/
|
|
│ │ │ ├── index.html
|
|
│ │ │ ├── style.css
|
|
│ │ │ └── rankings.js
|
|
│ │ ├── seating/
|
|
│ │ │ ├── index.html
|
|
│ │ │ ├── style.css
|
|
│ │ │ └── seating.js
|
|
│ │ ├── schedule/
|
|
│ │ │ ├── index.html
|
|
│ │ │ ├── style.css
|
|
│ │ │ └── schedule.js
|
|
│ │ ├── lobby/
|
|
│ │ │ ├── index.html
|
|
│ │ │ ├── style.css
|
|
│ │ │ └── lobby.js
|
|
│ │ ├── prizepool/
|
|
│ │ │ ├── index.html
|
|
│ │ │ ├── style.css
|
|
│ │ │ └── prizepool.js
|
|
│ │ ├── movement/
|
|
│ │ │ ├── index.html
|
|
│ │ │ ├── style.css
|
|
│ │ │ └── movement.js
|
|
│ │ ├── league/
|
|
│ │ │ ├── index.html
|
|
│ │ │ ├── style.css
|
|
│ │ │ └── league.js
|
|
│ │ └── welcome/
|
|
│ │ ├── index.html
|
|
│ │ ├── style.css
|
|
│ │ └── welcome.js
|
|
│ ├── shared/
|
|
│ │ ├── ws-client.js # WebSocket client (reconnect, interpolation)
|
|
│ │ ├── theme.css # Shared display theme (Catppuccin)
|
|
│ │ ├── typography.css
|
|
│ │ └── animations.css # Level transitions, bust-out effects
|
|
│ └── README.md
|
|
│
|
|
├── deploy/ # Deployment & infrastructure
|
|
│ ├── leaf/ # Leaf node OS image & config
|
|
│ │ ├── Makefile # Build Felt OS image (Armbian-based)
|
|
│ │ ├── overlay/ # Files overlaid on base OS
|
|
│ │ │ ├── etc/
|
|
│ │ │ │ ├── systemd/system/felt-leaf.service
|
|
│ │ │ │ ├── systemd/system/felt-nats.service
|
|
│ │ │ │ ├── nftables.conf
|
|
│ │ │ │ └── netbird/
|
|
│ │ │ └── opt/felt/
|
|
│ │ │ └── config.toml.example
|
|
│ │ └── scripts/
|
|
│ │ ├── setup-wizard.sh
|
|
│ │ └── update.sh
|
|
│ │
|
|
│ ├── display/ # Display node OS image & config
|
|
│ │ ├── Makefile
|
|
│ │ ├── overlay/
|
|
│ │ │ ├── etc/
|
|
│ │ │ │ ├── systemd/system/felt-kiosk.service
|
|
│ │ │ │ ├── chromium-flags.conf
|
|
│ │ │ │ └── netbird/
|
|
│ │ │ └── opt/felt-display/
|
|
│ │ │ └── boot.sh
|
|
│ │ └── scripts/
|
|
│ │ └── provision.sh
|
|
│ │
|
|
│ ├── core/ # Core infrastructure (PVE LXC/VM configs)
|
|
│ │ ├── ansible/ # Ansible playbooks for PVE provisioning
|
|
│ │ │ ├── inventory/
|
|
│ │ │ │ ├── dev.yml
|
|
│ │ │ │ └── production.yml
|
|
│ │ │ ├── playbooks/
|
|
│ │ │ │ ├── setup-pve.yml
|
|
│ │ │ │ ├── deploy-core-api.yml
|
|
│ │ │ │ ├── deploy-nats.yml
|
|
│ │ │ │ ├── deploy-authentik.yml
|
|
│ │ │ │ ├── deploy-netbird.yml # Unified server + reverse proxy + Traefik
|
|
│ │ │ │ ├── deploy-postgres.yml
|
|
│ │ │ │ └── deploy-pbs.yml
|
|
│ │ │ └── roles/
|
|
│ │ │ └── ...
|
|
│ │ ├── pve-templates/ # LXC/VM config templates
|
|
│ │ │ ├── felt-core-api.conf
|
|
│ │ │ ├── felt-nats.conf
|
|
│ │ │ ├── felt-postgres.conf
|
|
│ │ │ └── ...
|
|
│ │ └── docker-compose/ # For services that run in Docker (Authentik)
|
|
│ │ └── authentik/
|
|
│ │ └── docker-compose.yml
|
|
│ │
|
|
│ └── backup/ # Backup configuration
|
|
│ ├── pbs-config.md # PBS setup documentation
|
|
│ ├── wal-archive.sh # PostgreSQL WAL archiving script
|
|
│ └── restore-runbook.md
|
|
│
|
|
├── docs/ # Documentation
|
|
│ ├── spec/
|
|
│ │ └── felt_phase1_spec.md # This product spec (living document)
|
|
│ ├── architecture/
|
|
│ │ ├── decisions/ # Architecture Decision Records (ADR)
|
|
│ │ │ ├── 001-monorepo.md
|
|
│ │ │ ├── 002-go-backend.md
|
|
│ │ │ ├── 003-libsql-leaf.md
|
|
│ │ │ ├── 004-nats-sync.md
|
|
│ │ │ ├── 005-authentik-idp.md
|
|
│ │ │ ├── 006-pve-core.md
|
|
│ │ │ └── 007-sveltekit-ui.md
|
|
│ │ └── diagrams/
|
|
│ ├── api/ # API documentation (auto-generated from Go)
|
|
│ ├── operator-guide/ # End-user documentation
|
|
│ └── security/
|
|
│ ├── threat-model.md
|
|
│ └── incident-response.md
|
|
│
|
|
├── templates/ # Tournament templates & presets
|
|
│ ├── blind-structures/
|
|
│ │ ├── turbo.json
|
|
│ │ ├── standard.json
|
|
│ │ ├── deepstack.json
|
|
│ │ └── wsop-style.json
|
|
│ └── event-rules/
|
|
│ └── defaults.json
|
|
│
|
|
├── testdata/ # Test fixtures
|
|
│ ├── players.csv
|
|
│ ├── tournaments/
|
|
│ └── blind-structures/
|
|
│
|
|
├── go.mod
|
|
├── go.sum
|
|
├── Makefile # Top-level build orchestration
|
|
├── .env.example
|
|
├── .gitignore
|
|
├── LICENSE
|
|
└── README.md
|
|
```
|
|
|
|
## Build Targets (Makefile)
|
|
|
|
```makefile
|
|
# Go backends
|
|
build-leaf: Cross-compile felt-leaf for linux/arm64
|
|
build-core: Build felt-core for linux/amd64
|
|
build-cli: Build felt-cli for current platform
|
|
|
|
# Frontends
|
|
build-operator: SvelteKit → static SPA → embed in Go binary
|
|
build-player: SvelteKit → static PWA → embed in Go binary
|
|
build-display: Copy display views → embed in Go binary
|
|
|
|
# Combined
|
|
build-all: All of the above
|
|
build-leaf-full: build-operator + build-player + build-display + build-leaf
|
|
→ single felt-leaf binary with all web assets embedded
|
|
|
|
# Testing
|
|
test: Go tests + Svelte tests
|
|
test-go: go test ./...
|
|
test-web: npm test in each web/ subdirectory
|
|
lint: golangci-lint + eslint + prettier
|
|
|
|
# Database
|
|
migrate-leaf: Run LibSQL migrations
|
|
migrate-core: Run PostgreSQL migrations
|
|
|
|
# Deployment
|
|
deploy-core: Ansible playbook for Core services
|
|
deploy-leaf-image: Build Felt OS image for Leaf SBC
|
|
deploy-display-image: Build display node image
|
|
|
|
# Development
|
|
dev-leaf: Run felt-leaf locally (with hot reload via air)
|
|
dev-operator: Run SvelteKit operator UI in dev mode
|
|
dev-player: Run SvelteKit player PWA in dev mode
|
|
dev-core: Run felt-core locally
|
|
```
|
|
|
|
## Key Design Decisions
|
|
|
|
### Single Binary Deployment (Leaf)
|
|
|
|
The Leaf node ships as a single Go binary with all web assets embedded via `go:embed`:
|
|
|
|
```go
|
|
//go:embed web/operator/build web/player/build web/display
|
|
var webAssets embed.FS
|
|
```
|
|
|
|
This means:
|
|
- Flash SSD → boot → one binary serves everything
|
|
- No npm, no node_modules, no webpack on the Pi
|
|
- Atomic updates: replace one binary, restart service
|
|
- Rollback: keep previous binary, switch systemd symlink
|
|
|
|
### Shared Types Between Go and JavaScript
|
|
|
|
The `pkg/protocol/messages.json` file is a JSON Schema that defines all WebSocket message types. This is the single source of truth consumed by:
|
|
- Go: generated types via tooling
|
|
- TypeScript: generated types for Svelte stores
|
|
- Display views: lightweight type checking
|
|
|
|
### Database Abstraction
|
|
|
|
The `internal/store/store.go` defines a `Store` interface. Both LibSQL (Leaf) and PostgreSQL (Core) implement this interface. The tournament engine, player management, etc. all depend on the interface, not on a specific database.
|
|
|
|
```go
|
|
type Store interface {
|
|
// Tournaments
|
|
CreateTournament(ctx context.Context, t *models.Tournament) error
|
|
GetTournament(ctx context.Context, id string) (*models.Tournament, error)
|
|
// ...
|
|
|
|
// Players
|
|
GetPlayer(ctx context.Context, id string) (*models.Player, error)
|
|
SearchPlayers(ctx context.Context, query string) ([]*models.Player, error)
|
|
// ...
|
|
}
|
|
```
|
|
|
|
This allows the same domain logic to run on Leaf (LibSQL) and Core (PostgreSQL) without code duplication.
|
|
|
|
### Development Workflow with Claude Code
|
|
|
|
The monorepo is designed for Claude Code's agentic workflow:
|
|
|
|
1. **Context**: Claude Code can read the spec in `docs/spec/`, understand the architecture from ADRs, and see the full codebase
|
|
2. **Atomicity**: A feature that touches Go API + Svelte UI + display view is one commit
|
|
3. **Testing**: `make test` from root runs everything
|
|
4. **Building**: `make build-leaf-full` produces a deployable binary
|
|
|
|
### Forgejo CI
|
|
|
|
`.forgejo/workflows/` contains pipelines that:
|
|
- Lint Go + JS on every push
|
|
- Run tests on every PR
|
|
- Build binaries on merge to main
|
|
- Build OS images on release tag
|