Files
anwar-pi-website/AGENT.md
T
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

161 lines
8.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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
```bash
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.