# 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('<key>')` 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
