bzha0038au-ops/probabilistic-reward-distribution-system
Full-stack reward platform focused on transactional wallet flows, weighted draws, payout controls, and admin-grade operations.
Prize Pool & Probability Engine System
A full-stack reward and draw system with wallet accounting, prize-pool controls, admin operations, and audit-friendly financial flows.
This repo is designed as a practical system skeleton for products such as spin-the-wheel, prize-pool, or reward-center apps, where financial correctness matters more than demo-only UI.
Why This Repo
- Transaction-safe wallet flows for top-up, draw, and withdrawal paths
- Separate user and admin frontends, so product logic and operations logic stay isolated
- Backend-owned financial mutations with ledger-style records and DB transaction boundaries
- Shared contracts, schema, and migrations inside one workspace, so changes move together
Quick Start
If this is your first time in the repo, follow this section exactly. It is the shortest reliable path to a working local environment.
Prerequisites
- Node.js 20+
- pnpm 9+
- Docker
- Free local ports:
3000,4000,5173,5433,6379
1. Install dependencies
pnpm install2. Create local env files
cp apps/database/.env.example apps/database/.env
cp apps/backend/.env.example apps/backend/.env
cp apps/frontend/.env.example apps/frontend/.env
cp apps/admin/.env.example apps/admin/.env3. Fill the minimum local values
apps/database/.env
DATABASE_URL=postgresql://postgres:postgres@127.0.0.1:5433/reward_local
POSTGRES_URL=postgresql://postgres:postgres@127.0.0.1:5433/reward_local
POSTGRES_SSL=falseapps/backend/.env
DATABASE_URL=postgresql://postgres:postgres@127.0.0.1:5433/reward_local
POSTGRES_URL=postgresql://postgres:postgres@127.0.0.1:5433/reward_local
ADMIN_JWT_SECRET=local_admin_secret_change_me_123456
USER_JWT_SECRET=local_user_secret_change_me_123456
WEB_BASE_URL=http://localhost:3000
ADMIN_BASE_URL=http://localhost:5173
PORT=4000
REDIS_URL=redis://127.0.0.1:6379apps/frontend/.env
AUTH_SECRET=local_frontend_auth_secret_change_me_123456
API_BASE_URL=http://localhost:4000
NEXT_PUBLIC_API_BASE_URL=http://localhost:4000apps/admin/.env
API_BASE_URL=http://localhost:4000
ADMIN_JWT_SECRET=local_admin_secret_change_me_123456Important:
ADMIN_JWT_SECRETmust match betweenapps/backend/.envandapps/admin/.env- For local development, placeholder secrets are fine; for production, use real 32+ character secrets
4. Start Postgres and Redis
pnpm db:up5. Run migrations
pnpm db:migrate6. Start all apps
pnpm dev7. Open the local apps
- User app: http://localhost:3000
- Admin app: http://localhost:5173
- Backend health check: http://localhost:4000/health
Useful next commands
pnpm db:seed:manual
pnpm test
pnpm test:integration
pnpm build
pnpm db:resetManual QA Data
If you want the UI populated with realistic test records instead of starting from an empty database:
pnpm db:seed:manualThis inserts:
- 1 admin account
- 4 user accounts
- prizes, draw history, deposits, withdrawals
- audit events, admin actions, freeze records, suspicious account data
Default local accounts:
- Admin:
admin.manual@example.com/Admin123! - User:
alice.manual@example.com/User123! - User:
bob.manual@example.com/User123! - User:
carol.manual@example.com/User123! - User:
frozen.manual@example.com/User123!
Project At A Glance
- User app:
apps/frontend - Admin app:
apps/admin - Backend and financial logic:
apps/backend - Database schema and migrations:
apps/database - Shared API contracts:
apps/shared-types
If you want the architecture view after bootstrapping, start with docs/architecture.md.
System Map
flowchart LR
A["User Frontend<br/>Next.js"] --> C["Backend API<br/>Fastify"]
B["Admin Frontend<br/>SvelteKit"] --> C
C --> D["PostgreSQL"]
C --> E["Redis"]
C --> F["Shared Contracts<br/>Zod + TypeScript"]
What This Project Does
- Lets users register, log in, top up, withdraw, draw rewards, and inspect wallet history
- Gives operators a separate admin console to manage prizes, update runtime config, inspect finance data, and review audit/security records
- Keeps draw execution and balance mutation logic inside the backend, protected by DB transactions and ledger entries
- Keeps schema, migrations, and shared contracts inside the same workspace so the system evolves together
The highest-risk path is executeDraw(userId): debit the draw cost, evaluate prize eligibility, write ledger entries, update the house account, and persist the result inside one transaction.
Highlights
- Weighted draw execution with prize eligibility checks
- Prize-pool and payout controls in the backend
- Wallet ledger and transaction boundaries for financial flows
- Admin audit and finance surfaces separated from the public app
- Workspace-level tests plus backend integration tests against local Postgres
Workspace Map
| Path | Role |
|---|---|
apps/frontend |
User-facing product UI |
apps/admin |
Internal operations and finance console |
apps/backend |
HTTP API, auth, wallet flows, draw engine |
apps/database |
Drizzle schema and migrations |
apps/shared-types |
Shared request/response contracts |
docs |
Architecture, environment, deployment, and test docs |
Tech Stack
| Layer | Choice |
|---|---|
| User web | Next.js App Router |
| Admin console | SvelteKit |
| Backend API | Fastify |
| Database | PostgreSQL |
| ORM / schema | Drizzle ORM |
| Shared contracts | TypeScript + Zod |
| Tooling | pnpm workspace, Vitest, GitHub Actions |
Why Are There Two Frontends?
The main reason is logical isolation.
- The user app and the admin app serve different audiences and different risk levels
- The user app is public-facing and optimized for customer flows
- The admin app is an internal tool for higher-risk actions like finance review, config changes, and operations work
- Keeping them separate prevents admin auth, admin dependencies, and admin UI complexity from leaking into the public product
- It also makes deployment, performance tuning, and incident blast radius easier to control
This is a system-boundary decision, not a framework collection exercise.
Why So Many Languages?
The repo looks polyglot, but the main business logic is still TypeScript. The other languages exist because each layer has a different job.
| Language | Why it exists here |
|---|---|
| TypeScript | Services, routes, business rules, shared contracts |
| SQL | Migrations and schema changes |
| Svelte / TSX / JSX | UI code in each frontend |
| JSON | Locale files and structured configuration |
| CSS | Styling |
| YAML | CI and deployment workflows |
The point is directness, not variety for its own sake.
Common Commands
Run from the repo root:
pnpm dev
pnpm build
pnpm check
pnpm lint
pnpm test
pnpm test:integration
pnpm db:generate
pnpm db:migrate
pnpm db:studio
pnpm db:seed:manual
pnpm db:up
pnpm db:down
pnpm db:resetEnvironment
Minimum required values:
- Backend:
DATABASE_URLorPOSTGRES_URL,ADMIN_JWT_SECRET,USER_JWT_SECRET - Frontend:
AUTH_SECRET,API_BASE_URL,NEXT_PUBLIC_API_BASE_URL - Admin:
ADMIN_JWT_SECRET,API_BASE_URL
Full details live in docs/environment.md.
Testing
pnpm test: workspace-level testspnpm test:integration: backend integration tests against local Postgres
Test coverage is intentionally backend-heavy because the biggest risk in this system is financial correctness, not visual polish. See docs/test-strategy.md.
Deployment
- CI:
.github/workflows/ci.yml - Manual deploy workflow:
.github/workflows/deploy.yml - Checklist:
docs/deployment-checklist.md
Troubleshooting
- If admin login works in the backend but fails in the admin UI, check that
ADMIN_JWT_SECRETmatches inapps/backend/.envandapps/admin/.env. - If the frontend shows session or auth decryption errors, clear browser cookies for
localhost:3000and make sureAUTH_SECREThas not changed. - If
pnpm test:integrationfails immediately, make sure Docker is running andpnpm db:uphas already started Postgres on5433.
Reference Docs
- Architecture:
docs/architecture.md - API outline:
docs/api-outline.md - Environment:
docs/environment.md - Config reference:
docs/config-reference.md - Observability:
docs/observability.md