bbeandCursor 2ef68222bf feat: resolve live Nostr references in blog posts and add embeds
Support naddr/nevent/note slugs for unindexed posts, cache naddr lookups,
render Nostr embeds in markdown, and add a consistency check for events mirrors.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-29 03:57:32 +02:00
2026-04-01 02:46:53 +00:00
2026-04-01 02:46:53 +00:00

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

# Install backend dependencies
cd backend
npm install

# Install frontend dependencies
cd ../frontend
npm install

2. Configure environment

# 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

cd backend
npx prisma generate
npx prisma db push
npx prisma db seed

4. Run development servers

# Terminal 1: Backend
cd backend
npm run dev

# Terminal 2: Frontend
cd frontend
npm run dev

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 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

S
Description
Website / Nostr client of the Belgian Bitcoin Embassy btc meetups
Readme
4.1 MiB
Languages
TypeScript 93.5%
HTML 4.6%
Shell 1.2%
JavaScript 0.6%