# Belgian Bitcoin Embassy A Nostr-powered community website for Belgium's monthly Bitcoin meetup. ## Tech Stack - **Frontend**: Next.js 14 (App Router), TypeScript, Tailwind CSS, Shadcn/ui - **Backend**: Express.js, TypeScript, Prisma ORM - **Database**: SQLite (default), PostgreSQL supported - **Auth**: Nostr login (NIP-07), JWT sessions ## Quick Start ### 1. Clone and install ```bash # Install backend dependencies cd backend npm install # Install frontend dependencies cd ../frontend npm install ``` ### 2. Configure environment ```bash # From project root cp .env.example backend/.env cp .env.example frontend/.env.local ``` Edit `backend/.env` with your SuperAdmin pubkeys (`SUPERADMIN_PUBKEYS`) and a secure JWT secret. ### 3. Set up database ```bash cd backend npx prisma generate npx prisma db push npx prisma db seed ``` ### 4. Run development servers ```bash # Terminal 1: Backend cd backend npm run dev # Terminal 2: Frontend cd frontend npm run dev ``` - Frontend: http://localhost:3000 - Backend API: http://localhost:4000/api ## Project Structure ``` /frontend Next.js application /app App Router pages /components React components /lib Utilities and API client /hooks Custom React hooks /backend Express API server /src Source code /api Route handlers /services Business logic /middleware Auth middleware /prisma Database schema and migrations /context Design specs (reference only) ``` ## API Endpoints | Method | Path | Description | |--------|------|-------------| | POST | /api/auth/challenge | Get auth challenge | | POST | /api/auth/verify | Verify Nostr signature | | GET | /api/posts | List blog posts | | GET | /api/posts/:slug | Get post by slug | | POST | /api/posts/import | Import Nostr post | | PATCH | /api/posts/:id | Update post | | GET | /api/meetups | List meetups | | POST | /api/meetups | Create meetup | | PATCH | /api/meetups/:id | Update meetup | | POST | /api/moderation/hide | Hide content | | POST | /api/moderation/block | Block pubkey | | GET | /api/auth/me | Current user's effective role and permissions | | GET | /api/users | List users | | PUT | /api/users/:pubkey/role | Assign a role to a user | | GET | /api/admin/permissions | Permission registry (keys, labels, groups) | | GET | /api/admin/roles | Each role and its permission set | | PUT | /api/admin/roles/:role/permissions | Update a role's permissions | | GET | /api/categories | List categories | | POST | /api/categories | Create category | ## Roles and permissions Access is controlled by an ordered role hierarchy backed by a runtime-editable, granular permission system. From highest to lowest: 1. **SuperAdmin** sourced only from the `SUPERADMIN_PUBKEYS` env var. Always has every permission. Cannot be created, edited, demoted, or removed through the UI. This replaces the old "admin set in env" concept. If `SUPERADMIN_PUBKEYS` is unset, the app falls back to the legacy `ADMIN_PUBKEYS` variable. 2. **Admin** highest role assignable through the dashboard. 3. **Moderator** 4. **Writer** 5. **Guest** the fallback for any logged-in pubkey with no assigned role. Guest is not a stored record, it is simply the absence of a role. Each assignable role (Admin, Moderator, Writer) maps to a set of permission keys stored in the `RolePermission` table. SuperAdmins manage these from the dashboard **Roles** page, which renders a permission-by-role matrix. The dashboard nav and actions are gated on the current user's resolved permissions, not on role names. ### Safeguards - SuperAdmin is never assignable through the UI or API. Only the env var grants it. - A user cannot assign a role at or above their own, or modify a SuperAdmin. - A user cannot grant a role a permission they do not themselves hold. - A user cannot remove their own `users.assign_role` or `roles.edit_permissions`. SuperAdmin (env-sourced) is always the recovery path. - Unknown role values and permission keys are rejected by the server. ### Adding a new permission key 1. Add an entry to `PERMISSIONS` in [backend/src/constants/permissions.ts](backend/src/constants/permissions.ts) with a `key`, `label`, and `group`. 2. The key automatically appears in `GET /admin/permissions` and the Roles matrix page, so no UI changes are needed. 3. Apply it to the relevant endpoints with the `requires('')` guard. 4. Optionally grant it to roles by default in `DEFAULT_ROLE_PERMISSIONS` (used by the seed) and in the migration so fresh and existing databases get it. ## License MIT