Files
AHMED 165efc3269 Docs: new README (Arabic flows, screenshots, architecture) + AGENT.md for AI agents
- README rewritten for the real Next.js + Payload + Postgres stack
  (old one described the dead static prototype)
- Logo + 4 live screenshots (home, articles, post, subscription)
- Arabic flow explanations with mermaid diagrams: visitor journey,
  content pipeline, request path
- Architecture table, quick start, API reference, security summary,
  deploy commands
- AGENT.md: orientation for future AI agents (data model, trust rules,
  rate limits, routes, env, deploy, house rules)
- GLOSSARY.md and docs/adr committed (linked from README)
2026-10-09 17:23:14 +03:00

8.1 KiB
Raw Permalink Blame History

AGENT.md — AnwarPi for AI agents

Orientation doc for AI agents (coding assistants, autonomous maintainers, content pipelines) working on this repository. Read this before changing anything.

What this is

AnwarPi (أنور بي آي) — an Arabic-first, RTL platform for learning practical AI at https://anwarpy.ai. Free guides for everyone, VIP-gated deep-dives, a searchable library/catalog, moderated comments, member accounts, and a Stripe subscription that is fully wired but public-facing "coming soon" until keys are set. Content is also published programmatically by an external agent pipeline through a key-authenticated API.

Canonical domain: anwarpy.ai (anwarpi.ai is legacy). Owner: Ahmed (ahmedalkhair.sa@gmail.com). Owner constraint: never change site flow/UX without explicit approval (e.g. no CAPTCHA, no aggressive limits).

Stack

Component Choice
Framework Next.js 15 (App Router, output: 'standalone'), React 19
CMS/Auth Payload 3.90 (@payloadcms/next), Lexical editor
Database PostgreSQL 16 via @payloadcms/db-postgres
Styling Plain CSS files per page (see web/src/app/(frontend)/styles/), dark-first brand (see brand-spec.md)
Email Cloudflare Email Routing (inbound) + Cloudflare Email Sending REST (outbound) — not SMTP (docs/adr/0003)
Payments Stripe SDK, webhook at /api/stripe/webhook
Edge Traefik 2/3 with Docker provider (labels in web/docker-compose.vps.yml)

Repo layout

web/                     ← the entire application (deploy unit)
  src/
    payload.config.ts    ← Payload config: collections, postgres adapter
    seed.ts              ← `npm run seed`
    middleware.ts        ← Next middleware: auth rate limits (F2)
    collections/         ← Users, Posts, Media, Files, PostLikes, Comments
    migrations/          ← Payload migrations (`payload migrate` runs on boot)
    lib/                 ← viewer.ts (session helper), mail.ts, format.ts, topics.ts, agent-*
    app/
      (frontend)/        ← public Arabic site (page.tsx per route, styles/ css)
      (payload)/         ← Payload admin + API catch-all
      api/               ← custom REST routes (posts, auth, subscribe, stripe, agent, files)
  public/                ← brand/, covers/, og-default.png
  Dockerfile             ← standalone build; CMD runs `npx payload migrate` first
  docker-compose.vps.yml ← prod: web + postgres, Traefik labels (rate limits)
GLOSSARY.md              ← canonical domain vocabulary — USE THESE TERMS
docs/adr/                ← architecture decision records — do not contradict
brand-spec.md            ← visual identity: dark blue-black, silver beams, cyan-teal accent

Data model (Payload collections)

  • Users — auth collection. role: 'free' | 'vip' | 'admin' (server-forced), emailVerified, onboarded, termsVersion. Login lockout: 5 attempts / 15 min; admin accounts never lock (hook clears lock before login).
  • Posts — guides. access: 'free' | 'vip', slug, category (وكلاء، نماذج، استرجاع، سلامة، أدوات، أعمال), cover image (1200×630), Lexical body, likes/views.
  • Comments — moderated: pending → approved | rejected. Only approved is public. Authors cannot edit/delete their own comments (moderated model, docs/adr/0002). Display name is a snapshot of fullName at submit time.
  • Media / Files — uploads (files served via /api/files/[id]).
  • PostLikes — one like per member per post.

Trust model (critical — do not break)

  1. Roles are server-forced. POST /api/users and any client write cannot set role, emailVerified, stripeCustomerId, or verificationToken — the beforeChange hook in Users.ts deletes them for untrusted operations. Only req.user.role === 'admin' or context.systemActivation === true (payment activation from server code) counts as trusted.
  2. Entitlement is derived, never stored on the user: viewer may read a post's body iff post.access === 'free' || role in ('vip','admin'). Gating happens server-side (locked body is stripped from API responses).
  3. Comments require verification (emailVerified), admins exempt.
  4. Client requests can never mint VIP (free /payment-success visits redirect to /subscription; docs/adr/0004, 0005).

Auth & rate limiting

  • Passwords: Payload local auth → PBKDF2-SHA256, 600 000 iterations, 32 bytes (NOT bcrypt — verified in payload's generatePasswordSaltHash).
  • web/src/middleware.ts (F2): per-IP in-memory buckets — POST /api/users 5/h, POST /api/users/login 10/15min, POST /api/users/forgot-password 5/h. Over limit → 429 JSON {"error":"محاولات كثيرة جدًا. حاول بعد قليل."} + Retry-After. In-memory is correct for the single-container deployment; moving to horizontal scaling requires a shared store (Redis) — do not silently assume one replica.
  • Timing equalization (F7): Users.ts beforeOperation login hook runs a dummy PBKDF2 (same params, salt anwarpy-dummy-salt-v1) when no user matches, so ghost emails are not enumerable by response time. Keep this when touching the hook.
  • Traefik edge (F1, VPS only): pages pagelimit 60/min burst 120 + connlimit 50 in-flight per IP; API apilimit 90/min burst 180.
  • Strict-Transport-Security: max-age=63072000; includeSubDomains on all routes (web/next.config.mjs). Do not add preload.

Key routes

Frontend: / (home), /articles, /catalog, /posts/[slug] (gated body, engagement, comments), /signup, /login, /forgot-password, /verify-email, /subscription, /payment-success, /payment-cancelled, /vip-onboarding, /onboarding, /account-settings, /about, /privacy, /terms. Admin UI: /admin.

Custom API (see web/src/app/api/): posts (list) + posts/[slug] (detail — strips locked bodies), posts/[slug]/{like,view,comments}, auth/{verify-email,resend-verification}, users REST is Payload's own, subscribe, stripe/webhook (returns 503 until STRIPE_SECRET_KEY + STRIPE_WEBHOOK_SECRET exist; verifies stripe-signature once configured), agent/posts (POST/PUT with x-agent-key header — external publishing), and files/[id].

Environment variables

DATABASE_URI, PAYLOAD_SECRET, NEXT_PUBLIC_SITE_URL, AGENT_API_KEY, MAIL_FROM, CLOUDFLARE_API_TOKEN, CLOUDFLARE_ACCOUNT_ID, plus optional STRIPE_SECRET_KEY / STRIPE_WEBHOOK_SECRET. Prod values live in /root/anwarpi/.env.production on the VPS — never commit .env*.

Development & verification

cd web && npm install
npm run dev        # localhost:3000, /admin for CMS
npm run build      # MUST exit 0 — typescript.ignoreBuildErrors is false
npm run seed
  • Regenerates payload-types.ts automatically on build (payload generate:types).
  • Schema changes require a migration (payload migrate:create); the container CMD runs payload migrate on every deploy — no manual DB steps.
  • Auth endpoints are rate-limited in dev too; restart the dev server to reset buckets when testing.

Deployment

Single VPS 147.79.102.201, app at /root/anwarpi, managed by docker compose -f docker-compose.vps.yml (web + postgres + Traefik proxy network). Update flow: rsync web/ → /root/anwarpi/ excluding node_modules .next .git media files-private '.env*' '*.tsbuildinfo', then docker compose -f docker-compose.vps.yml up -d --build (label changes force recreate; migration runs on boot). Traefik labels only affect the VPS.

House rules

  • Follow the reuse ladder: existing helper → stdlib → platform feature → installed dependency → then write the complete version.
  • Do not add UX changes (new limits, captchas, gates) without owner approval; thresholds are deliberately generous for humans.
  • New decisions → add an ADR in docs/adr/ (next number, NNNN-slug.md).
  • Use GLOSSARY terms exactly (عضوية/entitlement, مفتوح/unlocked, موثّق/ verified, كتابة موثوقة/trusted write).
  • Pentest context: the 2026-10-09 report items F1–F9 are closed; see PR #1 and release v1.0.0 on Gitea for what was done and verified. Do not re-implement.