diff --git a/.gitignore b/.gitignore index 95050e22..2d430582 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,115 @@ -.qodo +# ============================================================ +# StellarHunts — root .gitignore +# Covers: Node.js / NestJS, Next.js, Rust / Cargo, and IDEs +# ============================================================ + +# ── Environment & secrets ─────────────────────────────────── +.env +.env.* +.env.local +.env.*.local +.env.development +.env.development.local +.env.test +.env.test.local +.env.production +.env.production.local +*.pem +*.key +*.p12 +secrets/ + +# ── Node / npm / yarn / pnpm ──────────────────────────────── +node_modules/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* +.pnp +.pnp.js +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz + +# ── Build artefacts ───────────────────────────────────────── +dist/ +build/ +out/ +.next/ +.nuxt/ +.output/ +.cache/ +*.tsbuildinfo + +# ── Rust / Cargo ──────────────────────────────────────────── +target/ +**/*.rs.bk +.cargo/registry/ +.cargo/git/ + +# ── Test & coverage ───────────────────────────────────────── +coverage/ +.nyc_output/ +test_snapshots/ +*.lcov +*.snap + +# ── Logs & diagnostics ────────────────────────────────────── +logs/ +*.log +pids/ +*.pid +*.pid.lock +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# ── OS-generated ──────────────────────────────────────────── .DS_Store -node_modules -dist -.env \ No newline at end of file +.DS_Store? +._* +.Spotlight-V100 +.Trashes +Thumbs.db +ehthumbs.db +Desktop.ini + +# ── IDE — JetBrains (IntelliJ / WebStorm / CLion / …) ─────── +.idea/ +*.iml +*.iws +*.ipr +.project +.classpath +.settings/ +*.sublime-workspace +*.sublime-project + +# ── IDE — VS Code ─────────────────────────────────────────── +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json + +# ── IDE — Eclipse / generic ───────────────────────────────── +.c9/ +*.launch + +# ── Temporary files ───────────────────────────────────────── +.temp/ +.tmp/ +tmp/ +*.tmp +*.bak +*.swp +*~ + +# ── Tooling ───────────────────────────────────────────────── +.qodo +.tool-versions +.turbo/ +.parcel-cache/ + +# ── Docker (local overrides only — Dockerfiles stay tracked) ─ +docker-compose.override.yml diff --git a/docs/adr/0001-record-architecture-decisions.md b/docs/adr/0001-record-architecture-decisions.md new file mode 100644 index 00000000..bfcc39db --- /dev/null +++ b/docs/adr/0001-record-architecture-decisions.md @@ -0,0 +1,66 @@ +# ADR-0001: Record Architecture Decisions + +**Date:** 2025-07-24 +**Status:** Accepted +**Deciders:** StellarHunts core team + +--- + +## Context + +Architecture decisions in StellarHunts have historically been made in PR +descriptions, Discord threads, and informal team discussions. When a new +contributor joins, there is no single place to find *why* certain choices +were made — only *what* the current code does. This creates ramp-up +friction and leads to decisions being revisited unnecessarily. + +## Decision + +We will use Architecture Decision Records (ADRs) to capture significant +architectural and design decisions. Each ADR is a short Markdown document +stored in `docs/adr/` with a sequential four-digit prefix and a +kebab-case title. + +ADRs should be created when: +- A technology or library is selected over alternatives +- A structural pattern is established (e.g., module layout, naming) +- An existing decision is reversed or superseded +- A design has meaningful trade-offs worth documenting + +ADRs are **immutable once accepted**. Superseding an old decision means +creating a new ADR and updating the old one's status field. + +### Template + +``` +# ADR-NNNN: + +**Date:** YYYY-MM-DD +**Status:** Proposed | Accepted | Deprecated | Superseded by ADR-XXXX +**Deciders:** <team or individuals> + +--- + +## Context +<What situation or problem prompted this decision?> + +## Decision +<What was decided?> + +## Consequences +### Positive +<Benefits of this choice.> +### Negative / Trade-offs +<Costs, risks, or constraints introduced.> +``` + +## Consequences + +### Positive +- New contributors can understand system rationale without reading PRs +- Decisions are revisited deliberately rather than accidentally +- Lightweight process — one Markdown file per decision + +### Negative / Trade-offs +- Requires discipline to write an ADR *before* merging significant changes +- ADRs can go stale if not kept up to date with status changes diff --git a/docs/adr/0002-zustand-alongside-redux-toolkit.md b/docs/adr/0002-zustand-alongside-redux-toolkit.md new file mode 100644 index 00000000..f2dfee01 --- /dev/null +++ b/docs/adr/0002-zustand-alongside-redux-toolkit.md @@ -0,0 +1,62 @@ +# ADR-0002: Use Zustand as the Primary State Manager Alongside Redux Toolkit + +**Date:** 2025-07-24 +**Status:** Accepted +**Deciders:** StellarHunts frontend team + +--- + +## Context + +The StellarHunts frontend needs client-side state management for: + +1. **Game state** — current puzzle, difficulty level, completed puzzles, + score, NFT collection. This state must survive page refreshes + (localStorage persistence) and is mutated frequently during gameplay. +2. **Auth state** — current user, JWT token, wallet address. +3. **Server state** — leaderboard data, puzzle content, referral stats. + This is cached, stale-while-revalidate data fetched from the NestJS API. + +Two popular choices were on the table: **Zustand** and +**Redux Toolkit (RTK)**. + +| Criterion | Zustand | Redux Toolkit | +|-----------|---------|---------------| +| Bundle size | ~3 kB | ~20 kB | +| Boilerplate | Minimal (no actions/reducers) | Moderate (slice files) | +| Middleware / devtools | Optional, plugin-based | First-class | +| Persistence | `zustand/middleware` `persist` | `redux-persist` | +| Learning curve | Low | Medium | +| Ecosystem maturity | Stable, wide adoption | Very mature, huge ecosystem | + +`@reduxjs/toolkit` is already listed as a production dependency (version +`^2.5.1`) because it was planned for a more complex slice-based state +model. In practice the team converged on Zustand stores for all current +state needs. + +## Decision + +- **Zustand** is the **primary** state management library for game state, + auth state, and reward state (see `frontend/store/`). +- **`@reduxjs/toolkit`** remains in `package.json` and should be used if + future requirements call for complex middleware chains, time-travel + debugging, or shared state slices that benefit from RTK's code + generation patterns (e.g., `createEntityAdapter`). +- **TanStack Query** handles all *server state* — API responses, caching, + background refetching — and is not replaced by either of the above. + +## Consequences + +### Positive +- Simple, readable store definitions — a store is just a `create()` call +- `persist` middleware handles localStorage serialization out of the box +- Low bundle size contribution +- Devtools integration available via `zustand/middleware` `devtools` + +### Negative / Trade-offs +- RTK's advanced features (immer-backed reducers, RTK Query, entity + adapters) are unavailable unless RTK is wired up in the future +- Two state libraries in `package.json` can confuse new contributors — + this ADR resolves that ambiguity +- No centralized dispatcher pattern; state mutations are co-located in + store files, which can scatter business logic diff --git a/docs/adr/0003-nestjs-modular-monolith.md b/docs/adr/0003-nestjs-modular-monolith.md new file mode 100644 index 00000000..817dd4ea --- /dev/null +++ b/docs/adr/0003-nestjs-modular-monolith.md @@ -0,0 +1,68 @@ +# ADR-0003: NestJS Modular Monolith Over Microservices + +**Date:** 2025-07-24 +**Status:** Accepted +**Deciders:** StellarHunts backend team + +--- + +## Context + +StellarHunts requires a backend that handles authentication, puzzle +management, NFT claim orchestration, real-time multiplayer matchmaking, +notifications, referrals, analytics, and more. These are distinct +domains, so the question arose: should the backend be structured as a +**microservices** cluster or a **monolith**? + +The team evaluated three architectural patterns: + +| Pattern | Deployment | Scaling | Complexity | Team size fit | +|---------|-----------|---------|------------|---------------| +| Unstructured monolith | Single process | Vertical only | Low initially | Small | +| Modular monolith | Single process | Vertical + horizontal replicas | Medium | Small–medium | +| Microservices | Many processes | Per-service horizontal | High | Large | + +Key constraints at the time of decision: +- Team of fewer than 10 engineers +- Early-stage product — domain boundaries still evolving +- Single PostgreSQL instance; cross-service transactions would be complex +- Redis already required for Socket.IO adapter and rate limiting +- Need to ship quickly and iterate + +## Decision + +The backend is a **NestJS modular monolith**. + +- All domains live under `backend/src/` as NestJS feature modules + (`@Module()` decorated classes). +- Each module owns its controller, service, entities, and DTOs. +- Cross-domain calls happen through NestJS dependency injection (imported + modules), **not** via HTTP or a message bus. +- The monolith is deployed as a single Docker container / process; + horizontal scaling is achieved by running multiple replicas behind a + load balancer with Redis as the shared session/socket adapter. + +If a specific domain needs independent scaling in the future (e.g., +the multiplayer matchmaking gateway), it can be extracted into a +standalone NestJS microservice using the built-in `@nestjs/microservices` +transport layer with minimal refactoring because the module boundary +already exists. + +## Consequences + +### Positive +- Single deployment unit — simpler CI/CD and local development +- No distributed-transaction complexity; TypeORM transactions work across + all domains +- NestJS DI container enforces explicit module boundaries without the + operational overhead of separate services +- Straightforward to extract a module into a microservice later + +### Negative / Trade-offs +- A poorly written module can import anything, eroding boundaries over + time — code review must enforce the module contract +- A crash in one domain crashes the whole process (mitigated by process + managers and health checks) +- Vertical scaling limits apply; the team must monitor whether any single + domain (e.g., real-time sockets) becomes a bottleneck before extracting + it diff --git a/docs/adr/0004-soroban-over-evm.md b/docs/adr/0004-soroban-over-evm.md new file mode 100644 index 00000000..4ec9bb2c --- /dev/null +++ b/docs/adr/0004-soroban-over-evm.md @@ -0,0 +1,71 @@ +# ADR-0004: Use Soroban (Stellar) Over EVM-Compatible Chains + +**Date:** 2025-07-24 +**Status:** Accepted +**Deciders:** StellarHunts core team + +--- + +## Context + +StellarHunts awards on-chain NFT badges when players complete puzzle +levels. The team needed to select a smart-contract platform to host: + +1. **Game contract** — question lifecycle, answer validation (SHA-256), + player level progression. +2. **NFT badge contract** — per-level badge minting with role-gated + authorization. + +The two primary candidates were **Soroban on Stellar** and an +**EVM-compatible chain** (Ethereum mainnet, Polygon, or Base). + +| Criterion | Soroban / Stellar | EVM (Ethereum / Polygon) | +|-----------|-------------------|--------------------------| +| Transaction fees | Sub-cent on Stellar | Variable (gwei spikes on mainnet; low on L2s) | +| Finality | ~5 s (Stellar consensus) | ~12 s ETH / ~2 s Polygon | +| Smart contract language | Rust (soroban-sdk) | Solidity / Vyper | +| Tooling maturity | Growing (Stellar CLI, soroban-cli) | Very mature (Hardhat, Foundry, OpenZeppelin) | +| NFT standards | Custom (no ERC-721 equivalent yet) | ERC-721 / ERC-1155 well established | +| Wallet ecosystem | Freighter, Lobstr, Albedo | MetaMask, WalletConnect (broad) | +| Developer community | Smaller, niche | Large, extensive resources | +| Educational alignment | Matches project's Stellar-learning theme | Generic blockchain knowledge | + +The project's **educational mission** is to teach players about +blockchain technology — specifically the Stellar ecosystem. Using Soroban +keeps the on-chain layer consistent with the subject matter being taught. + +## Decision + +StellarHunts uses **Soroban smart contracts on the Stellar network** for +all on-chain game logic and NFT badge minting. + +- Contracts are written in **Rust** using `soroban-sdk 22.x`. +- The workspace lives in `onchain/` with a Cargo workspace manifest. +- Two production contracts exist: + - `stellar_hunts` — game logic + - `stellar_hunts_nft` — badge minting +- Local development and CI use Stellar Testnet; + `STELLAR_MODE=mock` allows the backend to run without a live network. +- Answer privacy is preserved on-chain via `env.crypto().sha256()` — + no plaintext answers are stored in contract state. + +## Consequences + +### Positive +- Aligns with the project's educational goal of teaching Stellar/Soroban +- Very low and predictable transaction fees +- Fast finality reduces wait time after puzzle completion +- Rust's type system and Soroban's sandboxed WASM runtime provide strong + safety guarantees +- `STELLAR_MODE=mock` lets the backend be developed and tested without + a live network dependency + +### Negative / Trade-offs +- Smaller developer community means fewer tutorials and third-party + tooling compared to EVM +- No standardized NFT interface (ERC-721) — badge ownership queries use + a custom `has_level_badge` function +- Freighter wallet has less browser/mobile coverage than MetaMask's + ecosystem +- Team members with an EVM background need to learn Rust and the Soroban + execution model diff --git a/docs/api.md b/docs/api.md new file mode 100644 index 00000000..8c5fa698 --- /dev/null +++ b/docs/api.md @@ -0,0 +1,407 @@ +# StellarHunts API Reference + +Base URL: `http://localhost:3001` (development) +Interactive docs: `http://localhost:3001/api/docs` (Swagger UI) + +**Authentication:** Unless noted as _Public_, all endpoints require a +`Authorization: Bearer <jwt>` header obtained from `POST /auth/login`. + +> This document was generated from the NestJS controller source. For the +> full request/response schemas, open the Swagger UI while the backend is +> running (`npm run start:dev`). + +--- + +## Table of Contents + +- [Auth](#auth) +- [Users](#users) +- [Puzzles (Game)](#puzzles-game) +- [Puzzles (Admin CRUD)](#puzzles-admin-crud) +- [Puzzle Submission](#puzzle-submission) +- [Puzzle Categories](#puzzle-categories) +- [Puzzle Dependencies](#puzzle-dependencies) +- [Puzzle Translations](#puzzle-translations) +- [Content](#content) +- [Rewards](#rewards) +- [NFT Claim](#nft-claim) +- [Reward Shop](#reward-shop) +- [Achievements](#achievements) +- [Badges](#badges) +- [Progress](#progress) +- [Streaks](#streaks) +- [Time Trial](#time-trial) +- [In-App Notifications](#in-app-notifications) +- [Referrals](#referrals) +- [Challenges](#challenges) +- [Feedback](#feedback) +- [Multiplayer Queue](#multiplayer-queue) +- [User Ranking](#user-ranking) +- [Activity](#activity) +- [Wallet](#wallet) +- [Admin](#admin) + +--- + +## Auth + +| Method | Path | Auth | Description | +|--------|------|------|-------------| +| POST | `/auth/register` | Public | Register a new user account | +| POST | `/auth/login` | Public | Log in and receive a JWT | +| GET | `/auth/profile` | JWT | Get the authenticated user's profile | +| POST | `/auth/validate-token` | JWT | Validate a JWT token | + +--- + +## Users + +| Method | Path | Auth | Description | +|--------|------|------|-------------| +| POST | `/users` | Public | Create a new user | +| PATCH | `/users/profile` | JWT | Update the authenticated user's profile | +| POST | `/users/link-wallet` | JWT | Link a Stellar wallet address to the account | +| GET | `/users/:id` | JWT | Get a user by ID | +| GET | `/users/:id/rank` | JWT | Get a user's ranking information | +| GET | `/users/:id/progress` | JWT | Get a user's overall progress | + +--- + +## Puzzles (Game) + +Handles active gameplay interactions (submit answers, request hints). + +| Method | Path | Auth | Description | +|--------|------|------|-------------| +| POST | `/puzzles/submit` | JWT | Submit an answer for a puzzle | +| POST | `/puzzles/hint` | JWT | Request a hint for the current puzzle | +| GET | `/puzzles/progress` | JWT | Get the authenticated user's puzzle progress | +| GET | `/puzzles/rate-limit-status` | JWT | Check current rate-limit status for submissions | +| GET | `/puzzles/active` | Public | List all active puzzles | + +--- + +## Puzzles (Admin CRUD) + +Full CRUD for puzzle management. Requires admin role. + +| Method | Path | Auth | Description | +|--------|------|------|-------------| +| POST | `/admin/puzzles` | JWT + Admin | Create a new puzzle | +| GET | `/admin/puzzles` | JWT + Admin | List all puzzles (admin view) | +| GET | `/admin/puzzles/:id` | JWT + Admin | Get a puzzle by ID (admin view) | +| PATCH | `/admin/puzzles/:id` | JWT + Admin | Update a puzzle | +| DELETE | `/admin/puzzles/:id` | JWT + Admin | Delete a puzzle | + +--- + +## Puzzle Submission + +Separate submission endpoint (legacy / alternative path). + +| Method | Path | Auth | Description | +|--------|------|------|-------------| +| POST | `/puzzle-submission` | JWT | Submit a puzzle answer | + +--- + +## Puzzle Categories + +Manage puzzle categories and the puzzles belonging to them. + +| Method | Path | Auth | Description | +|--------|------|------|-------------| +| GET | `/puzzle-categories/puzzles-by-category` | Public | Get puzzles grouped by category | +| GET | `/puzzle-categories/categories` | Public | List all categories | +| GET | `/puzzle-categories/categories/:id` | Public | Get a category by ID | +| GET | `/puzzle-categories/categories/slug/:slug` | Public | Get a category by slug | +| POST | `/puzzle-categories/categories` | JWT + Admin | Create a category | +| PUT | `/puzzle-categories/categories/:id` | JWT + Admin | Update a category | +| DELETE | `/puzzle-categories/categories/:id` | JWT + Admin | Delete a category | +| GET | `/puzzle-categories/puzzles` | Public | List all categorised puzzles | +| GET | `/puzzle-categories/puzzles/:id` | Public | Get a categorised puzzle by ID | +| POST | `/puzzle-categories/puzzles` | JWT + Admin | Add a puzzle to a category | +| PUT | `/puzzle-categories/puzzles/:id` | JWT + Admin | Update a puzzle's category assignment | +| DELETE | `/puzzle-categories/puzzles/:id` | JWT + Admin | Remove a puzzle from a category | +| GET | `/puzzle-categories/categories/:id/puzzles` | Public | Get all puzzles in a category | +| GET | `/puzzle-categories/puzzles/difficulty/:difficulty` | Public | Filter puzzles by difficulty | +| GET | `/puzzle-categories/puzzles/search` | Public | Search puzzles | +| POST | `/puzzle-categories/seed-categories` | JWT + Admin | Seed default categories | + +--- + +## Puzzle Dependencies + +Prerequisite / unlock chain management. + +| Method | Path | Auth | Description | +|--------|------|------|-------------| +| POST | `/puzzle-dependencies` | JWT + Admin | Create a dependency | +| GET | `/puzzle-dependencies` | JWT | List all dependencies | +| GET | `/puzzle-dependencies/puzzle/:puzzleId` | JWT | Get dependencies for a puzzle | +| GET | `/puzzle-dependencies/:id` | JWT | Get a dependency by ID | +| PATCH | `/puzzle-dependencies/:id` | JWT + Admin | Update a dependency | +| DELETE | `/puzzle-dependencies/:id` | JWT + Admin | Delete a dependency | +| DELETE | `/puzzle-dependencies/puzzle/:puzzleId` | JWT + Admin | Remove all dependencies for a puzzle | +| POST | `/puzzle-dependencies/check-eligibility` | JWT | Check if a user is eligible to attempt a puzzle | +| POST | `/puzzle-dependencies/mark-completed` | JWT | Mark a dependency as completed | +| GET | `/puzzle-dependencies/user/:userId/completed` | JWT | List completed dependencies for a user | +| GET | `/puzzle-dependencies/user/:userId/unlocked` | JWT | List unlocked puzzles for a user | +| GET | `/puzzle-dependencies/chain/:puzzleId` | JWT | Get the full prerequisite chain for a puzzle | +| GET | `/puzzle-dependencies/stats/:puzzleId` | JWT | Get dependency stats for a puzzle | + +--- + +## Puzzle Translations + +| Method | Path | Auth | Description | +|--------|------|------|-------------| +| POST | `/puzzle-translations` | JWT + Admin | Create a translation | +| PUT | `/puzzle-translations/:id` | JWT + Admin | Update a translation | +| GET | `/puzzle-translations/:puzzleId` | Public | Get all translations for a puzzle | +| GET | `/puzzle-translations/:puzzleId/lang` | Public | Get a translation for a puzzle in a specific language | + +--- + +## Content + +Educational articles and resources. + +| Method | Path | Auth | Description | +|--------|------|------|-------------| +| GET | `/content` | Public | List published content | +| GET | `/content/:id` | Public | Get a content item by ID | +| POST | `/admin/content` | JWT + Admin | Create a content item | +| GET | `/admin/content` | JWT + Admin | List all content (admin view) | +| GET | `/admin/content/:id` | JWT + Admin | Get a content item (admin view) | +| PATCH | `/admin/content/:id` | JWT + Admin | Update a content item | +| DELETE | `/admin/content/:id` | JWT + Admin | Delete a content item | + +--- + +## Rewards + +| Method | Path | Auth | Description | +|--------|------|------|-------------| +| POST | `/rewards` | JWT + Admin | Create a reward | +| GET | `/rewards` | JWT | List all rewards | +| GET | `/rewards/:id` | JWT | Get a reward by ID | +| GET | `/rewards/challenge/:challengeId` | JWT | Get rewards for a challenge | +| POST | `/rewards/claim` | JWT | Claim a reward | +| GET | `/rewards/user/:userId/claims` | JWT | List all reward claims for a user | +| GET | `/rewards/claims/:id` | JWT | Get a specific claim | +| GET | `/rewards/:id/stats` | JWT | Get claim stats for a reward | +| DELETE | `/rewards/:id` | JWT + Admin | Delete a reward | + +--- + +## NFT Claim + +On-chain Soroban NFT badge minting. + +| Method | Path | Auth | Description | +|--------|------|------|-------------| +| POST | `/nft-claim/claim` | JWT | Trigger an NFT badge mint for a completed level | + +--- + +## Reward Shop + +| Method | Path | Auth | Description | +|--------|------|------|-------------| +| GET | `/reward-shop` | JWT | List reward shop items (see Swagger for full schema) | + +--- + +## Achievements + +| Method | Path | Auth | Description | +|--------|------|------|-------------| +| GET | `/achievements/:playerId` | JWT | Get all achievements for a player | + +--- + +## Badges + +| Method | Path | Auth | Description | +|--------|------|------|-------------| +| POST | `/badges/assign` | JWT + Admin | Assign a badge to a user | +| GET | `/badges/user/:id` | JWT | Get all badges for a user | + +--- + +## Progress + +| Method | Path | Auth | Description | +|--------|------|------|-------------| +| GET | `/users/:id/progress` | JWT | Get a user's progress summary | + +--- + +## Streaks + +### Authenticated + +| Method | Path | Auth | Description | +|--------|------|------|-------------| +| POST | `/streaks/activity` | JWT | Record a streak activity event | +| GET | `/streaks/user/:userId` | JWT | Get streak data for a user | +| GET | `/streaks/my-streak` | JWT | Get the authenticated user's streak | +| GET | `/streaks/leaderboard` | JWT | Streak leaderboard | +| GET | `/streaks/history` | JWT | Authenticated user's streak history | +| GET | `/streaks/user/:userId/history` | JWT | Streak history for a user | +| POST | `/streaks/recalculate` | JWT + Admin | Recalculate streaks | +| POST | `/streaks/reset` | JWT + Admin | Reset streaks | +| GET | `/streaks/active` | JWT | List users with active streaks | + +### Public + +| Method | Path | Auth | Description | +|--------|------|------|-------------| +| GET | `/public/streaks/user/:userId` | Public | Get streak data for a user (public) | +| GET | `/public/streaks/leaderboard` | Public | Public streak leaderboard | +| GET | `/public/streaks/user/:userId/history` | Public | Public streak history for a user | +| GET | `/public/streaks/stats` | Public | Global streak statistics | + +--- + +## Time Trial + +| Method | Path | Auth | Description | +|--------|------|------|-------------| +| POST | `/time-trial/start` | JWT | Start a timed puzzle attempt | +| POST | `/time-trial/submit/:id` | JWT | Submit an answer for a time trial | +| GET | `/time-trial/results/:userId` | JWT | Get time trial results for a user | +| GET | `/time-trial/leaderboard/:puzzleId` | Public | Time trial leaderboard for a puzzle | + +--- + +## In-App Notifications + +| Method | Path | Auth | Description | +|--------|------|------|-------------| +| GET | `/in-app-notifications` | JWT | List notifications for the authenticated user | +| GET | `/in-app-notifications/unread-count` | JWT | Get unread notification count | +| POST | `/in-app-notifications` | JWT | Create a notification | +| POST | `/in-app-notifications/system` | JWT + Admin | Send a system-wide notification | +| PATCH | `/in-app-notifications/read` | JWT | Mark a notification as read | +| PATCH | `/in-app-notifications/read-all` | JWT | Mark all notifications as read | +| PATCH | `/in-app-notifications/archive` | JWT | Archive a notification | +| DELETE | `/in-app-notifications/:id` | JWT | Delete a notification | + +--- + +## Referrals + +| Method | Path | Auth | Description | +|--------|------|------|-------------| +| POST | `/referrals/codes` | JWT | Generate a referral code | +| GET | `/referrals/codes/my` | JWT | Get the authenticated user's referral code | +| POST | `/referrals/invites` | JWT | Send a referral invite | +| GET | `/referrals/stats` | JWT | Get referral statistics | +| GET | `/referrals/history` | JWT | Get referral history | +| POST | `/referrals/invites/:id/complete` | JWT | Mark a referral invite as completed | +| POST | `/referrals/register` | Public | Register via referral code | + +--- + +## Challenges + +| Method | Path | Auth | Description | +|--------|------|------|-------------| +| POST | `/challenges` | JWT + Admin | Create a challenge | +| GET | `/challenges` | JWT | List all challenges | +| GET | `/challenges/available` | JWT | List challenges available to the user | +| GET | `/challenges/daily` | JWT | Get today's daily challenge | +| GET | `/challenges/weekly` | JWT | Get the current weekly challenge | +| GET | `/challenges/:id` | JWT | Get a challenge by ID | +| GET | `/challenges/:id/stats` | JWT | Get stats for a challenge | +| PATCH | `/challenges/:id` | JWT + Admin | Update a challenge | +| DELETE | `/challenges/:id` | JWT + Admin | Delete a challenge | + +--- + +## Feedback + +| Method | Path | Auth | Description | +|--------|------|------|-------------| +| POST | `/feedback` | JWT | Submit feedback | +| GET | `/feedback/admin` | JWT + Admin | List all feedback (admin view) | +| GET | `/feedback/stats` | JWT + Admin | Feedback statistics | +| GET | `/feedback/target/:targetType` | JWT | Get feedback for a target type | +| PUT | `/feedback/admin/:id` | JWT + Admin | Update a feedback entry | +| DELETE | `/feedback/admin/:id` | JWT + Admin | Delete a feedback entry | + +--- + +## Multiplayer Queue + +Real-time matchmaking via Socket.IO. See the WebSocket gateway for event names. + +| Method | Path | Auth | Description | +|--------|------|------|-------------| +| *(Socket.IO)* | `ws://localhost:3001` | JWT | Connect to the multiplayer matchmaking gateway | + +--- + +## User Ranking + +| Method | Path | Auth | Description | +|--------|------|------|-------------| +| GET | `/users/:id/rank` | JWT | Get global ranking for a user | + +--- + +## Activity + +| Method | Path | Auth | Description | +|--------|------|------|-------------| +| GET | `/activity` | JWT | Get the social activity feed (see Swagger for full schema) | + +--- + +## Wallet + +| Method | Path | Auth | Description | +|--------|------|------|-------------| +| POST | `/wallet/link` | JWT | Link a Stellar wallet address | +| POST | `/wallet/verify-signature` | JWT | Verify a wallet signature (POST) | +| GET | `/wallet/verify-signature` | JWT | Verify a wallet signature (GET) | + +--- + +## Admin + +| Method | Path | Auth | Description | +|--------|------|------|-------------| +| POST | `/admin/login` | Public | Admin login | +| GET | `/admin/profile` | JWT + Admin | Get admin profile | +| GET | `/admin/puzzles` | JWT + Admin | List all puzzles (admin) | +| GET | `/admin/content` | JWT + Admin | List all content (admin) | + +--- + +## Error Responses + +All endpoints return standard HTTP status codes: + +| Status | Meaning | +|--------|---------| +| 200 | OK | +| 201 | Created | +| 400 | Bad Request (validation error) | +| 401 | Unauthorized (missing or invalid JWT) | +| 403 | Forbidden (insufficient role) | +| 404 | Not Found | +| 429 | Too Many Requests (rate limited) | +| 500 | Internal Server Error | + +Error bodies follow the NestJS default shape: + +```json +{ + "statusCode": 400, + "message": ["field must not be empty"], + "error": "Bad Request" +} +``` diff --git a/docs/architecture.md b/docs/architecture.md index 43652943..0dd02bfd 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -27,8 +27,8 @@ StellarHunts is a three-tier gamified blockchain application. The system consist │ │ Module │ │ Modules │ │ (StellarHandlerSvc) │ │ │ └────────────┘ └────────────┘ └──────────────────────┘ │ │ ┌────────────┐ ┌────────────┐ ┌──────────────────────┐ │ -│ │ Progress │ │ Leaderboard│ │ Multiplayer (Socket) │ │ -│ │ Module │ │ Module │ │ Module │ │ +│ │ Progress │ │ Multiplayer│ │ In-App Notifications │ │ +│ │ Module │ │ (Socket) │ │ Module │ │ │ └────────────┘ └────────────┘ └──────────────────────┘ │ │ │ │ │ ┌───────────┴───────────┐ │ @@ -71,7 +71,6 @@ StellarHunts/ | State (Global) | Zustand | Game state, user session, progress tracking | | State (Server) | TanStack Query | API caching, optimistic updates | | Auth | NextAuth.js | OAuth, wallet linking, JWT sessions | -| Forms | Formik + Yup | Form state management and validation | | Blockchain | `@stellar/stellar-sdk` + `@stellar/freighter-api` | Wallet connection, contract invocation | | HTTP | Axios | API client with interceptors | | UI Components | Radix UI + shadcn/ui | Accessible primitives, design system | @@ -81,17 +80,11 @@ StellarHunts/ ``` / Homepage /game Puzzle game interface -/leaderboard Global rankings /puzzles/roadmap Puzzle progression timeline /invite-friends Referral program /ref/[referralId] Referral landing page /admin/puzzle-review Admin puzzle management /admin/puzzle-submission Admin puzzle creation -/faq Frequently asked questions -/terms Terms of service -/privacy Privacy policy -/about About page -/profile User profile /api/auth/[...nextauth] Auth API routes /api/referrals/* Referral API routes ``` @@ -100,14 +93,16 @@ StellarHunts/ Application state is split across two concerns: -**Zustand (useGameStore)** — Persisted to localStorage for game-specific state: +**Zustand** — Persisted to localStorage for game-specific state: - User authentication status - Current puzzle difficulty and progress - Completed puzzles and difficulty levels - Score tracking and NFT collection +> `@reduxjs/toolkit` is listed as a dependency but is not currently wired up. +> See [ADR-0002](adr/0002-zustand-alongside-redux-toolkit.md) for the rationale. + **TanStack Query** — Server state caching for: -- Leaderboard data - Puzzle content - Referral statistics - API-driven data with automatic invalidation @@ -144,78 +139,66 @@ Application state is split across two concerns: NestJS modules are organized by domain concern. Each module encapsulates its controller, service, entities, DTOs, and tests. +The table below lists modules that are **actually registered** in `app.module.ts`. Modules present in `backend/src/` but not yet wired into AppModule are noted separately. + **Core / Infrastructure** - `ConfigModule` — Environment configuration loading - `TypeOrmModule` — Database connection and entity registration -- `RateLimiterModule` — Request throttling with Redis-backed guards - `AnalyticsModule` — Event tracking and usage metrics -- `MaintenanceModeModule` — Service availability control -- `MigrationModule` — Database migration orchestration -- `AuditLogModule` — System audit logging -- `TokenVerificationModule` — Token validation utilities **Authentication & Users** - `AuthModule` — JWT authentication, registration, login, wallet linking -- `UserModule` — User CRUD and profile management -- `UserSettingsModule` — User preferences -- `UserActivityLogModule` — Audit trail for user actions - `UserReportCardModule` — Per-user performance summaries -- `UserInventoryModule` — NFT and badge ownership tracking +- `UserActivityLogModule` — Audit trail for user actions - `UserRankingModule` — Ranking calculations -- `WalletModule` — Stellar wallet address management +- `UserInventoryModule` — NFT and badge ownership tracking **Puzzle & Content** - `PuzzleModule` — Core puzzle CRUD and game logic -- `PuzzleCategoryModule` — Puzzle categorization and grouping - `PuzzleSubmissionModule` — Answer submission handling - `PuzzleDependencyModule` — Prerequisite puzzle management -- `PuzzleDraftModule` — Puzzle authoring workflow -- `PuzzleVersioningModule` — Puzzle revision history -- `PuzzleReviewModule` — Admin review workflow - `PuzzleTranslationModule` — Multi-language support -- `PuzzleCommentModule` — User discussion on puzzles -- `PuzzleAccessLogModule` — Access tracking -- `PuzzleTestCaseModule` — Test case management -- `PuzzleForkModule` — Puzzle forking and remixing - `ContentModule` — Educational articles and resources - `ContentRatingModule` — User content ratings -- `QuizModule` — Quiz-style challenges **Gamification & Rewards** - `RewardsModule` — Reward distribution and claim tracking - `RewardShopModule` — Reward marketplace - `NFTClaimModule` — On-chain Soroban NFT minting orchestration (StellarHandlerService) -- `NFTMarketplaceStubModule` — Mock marketplace for testing -- `AchievementsModule` — Achievement definitions and tracking -- `BadgeModule` — Badge management -- `MilestoneModule` — Milestone progression -- `StreakModule` — Daily/consecutive activity tracking -- `DailyRewardModule` — Login bonus system - `TimeTrialModule` — Timed challenge mode -- `PromoCodeModule` — Promotional code redemption **Social & Multiplayer** - `MultiplayerQueueModule` — Socket.IO matchmaking -- `ReferralModule` — Referral program tracking - `ReportsModule` — User reporting and moderation -- `FeedbackModule` — User feedback collection - `InAppNotificationsModule` — Notification delivery - `ActivityModule` — Social activity feed - `UserReactionModule` — Emoji/like reactions -- `GeostatsModule` — Geographic player statistics -**Progress & Analytics** +**Progress & Integrations** - `ProgressModule` — User progression tracking -- `SessionModule` — Session lifecycle -- `HintModule` — Puzzle hint management - `ApiKeyModule` — API key management for integrations -- `AdminModule` — Admin dashboard backend + +**Modules present in `backend/src/` but not yet registered in AppModule** + +The following directories exist and may be under active development: +`AuditLogModule`, `BadgeModule`, `DailyRewardModule`, `FeedbackModule`, +`GeostatsModule`, `HintModule`, `MaintenanceModeModule`, `MigrationModule`, +`MilestoneModule`, `NFTMarketplaceStubModule`, `PromoCodeModule`, +`PuzzleAccessLogModule`, `PuzzleCategoryModule`, `PuzzleCommentModule`, +`PuzzleDraftModule`, `PuzzleForkModule`, `PuzzleReviewModule`, +`PuzzleTestCaseModule`, `PuzzleVersioningModule`, `QuizModule`, +`ReferralModule`, `SessionModule`, `StreakModule`, +`TokenVerificationModule`, `UserModule`, `UserSettingsModule`, +`UserTokenHistoryModule`, `WalletModule`, `AdminModule`. + +> There is **no** `LeaderboardModule` in the source tree. The leaderboard +> endpoint lives inside `StreakModule` (`GET /streaks/leaderboard`). ### Database Primary database: **PostgreSQL** managed through TypeORM with code-first entity definitions. -Key entities: `User`, `Puzzle`, `Category`, `Reward`, `RewardClaim`, `TimeTrial`, `Session`, `Progress`, `Hint`, `Achievement`, `Badge`, `Streak`, `Referral`, `Notification`, `UserActivityLog`. +Key entities: `User`, `Puzzle`, `Category`, `Reward`, `RewardClaim`, `TimeTrial`, `Progress`. Configuration via environment variables: @@ -224,17 +207,19 @@ DATABASE_HOST=localhost DATABASE_PORT=5432 DATABASE_NAME=stellarshunts DATABASE_SYNC=true # Auto-sync entities (dev only) -DATABASE_LOAD=true # Auto-load entities +DATABASE_LOAD=true # Auto-load entities ``` ### API Design - **RESTful** endpoints organized by resource (no global prefix — e.g., `/puzzle-categories`, `/rewards`, `/auth`) -- **Authentication** via JWT tokens (Bearer header) or session cookies +- **Authentication** via JWT tokens (Bearer header) - **Swagger** documentation at `http://localhost:3001/api/docs` - **Rate limiting** applied to auth and claim endpoints - **WebSocket** connections for multiplayer queue via Socket.IO +See [`docs/api.md`](api.md) for the full endpoint reference table. + ## Onchain Architecture ### Stack