# معماری سامانه شهرنما

## ۱. تصویر کلی

شهرنما یک پلتفرم واحد است که پنج دامنه‌ی معمولاً جدا را در یک مدل داده و یک لایه‌ی
مجوزدهی ادغام می‌کند:

| دامنه | نقش در سامانه |
|---|---|
| خبرگزاری | تولید، بازبینی و انتشار محتوا با گردش کار و نسخه‌بندی |
| پایگاه آماری | مجموعه‌داده‌های دارای منبع و روش‌شناسی، مبنای روزنامه‌نگاری داده |
| گزارش مردمی | چرخه عمر پرونده از ثبت شهروند تا پاسخ رسمی سازمان |
| دایرکتوری نهادها | سازمان‌ها، ادارات، اصناف و کسب‌وکارها به‌عنوان موجودیت‌های درجه‌یک |
| ارتباط سازمانی | کانال‌های تلگرام/ایتا، پیام رسمی بین‌سازمانی، صف ارسال و وضعیت تحویل |

نکته‌ی معماری کلیدی: این پنج دامنه با **کلید خارجی واقعی** به هم متصل‌اند، نه با
برچسب متنی. یک خبر می‌تواند به یک مجموعه‌داده و یک سازمان ارجاع دهد؛ یک پرونده به
سازمان و اداره‌ی مسئول متصل است؛ کارنامه‌ی پاسخ‌گویی سازمان از همان پرونده‌ها
محاسبه می‌شود. به همین دلیل هیچ عددی در سامانه «تزئینی» نیست.

## ۲. لایه‌ها

```
┌───────────────────────────────────────────────────────────┐
│  Frontend — Next.js 15 (App Router, RTL, Server Components)│
│  /frontend/app         مسیرها و صفحات                      │
│  /frontend/features    منطق هر دامنه در سمت رابط کاربر     │
│  /frontend/components  کامپوننت‌های تعاملی و نمودار         │
└──────────────────────────┬────────────────────────────────┘
                           │ فقط مسیرهای نسبی /api/*
                           │ (rewrite در next.config.mjs)
┌──────────────────────────▼────────────────────────────────┐
│  Backend — Fastify 5 + Drizzle ORM + Zod                   │
│  http/routes   لایه انتقال (اعتبارسنجی ورودی، خروجی)       │
│  guards        احراز هویت، CSRF، محدودسازی نرخ             │
│  modules/*     منطق دامنه (articles, reports, search, …)   │
│  database      اسکیما، مهاجرت، کلاینت، داده نمونه          │
└──────────────────────────┬────────────────────────────────┘
                           │
┌──────────────────────────▼────────────────────────────────┐
│  Datacenter — PostgreSQL, Redis, MinIO, OpenSearch,        │
│  Workers, Prometheus/Grafana, Backup                       │
└───────────────────────────────────────────────────────────┘
```

### چرا این تفکیک؟
- **قواعد کسب‌وکار فقط در `modules/*`**: مسیرها (routes) هیچ تصمیم دامنه‌ای نمی‌گیرند.
  به همین دلیل «قانون ۱۰ روزه» یا «گذارهای مجاز پرونده» را نمی‌توان با صدا زدن یک
  endpoint دیگر دور زد؛ همه‌ی مسیرها به همان تابع دامنه می‌رسند.
- **Frontend هرگز منبع حقیقت مجوز نیست**: تابع `can()` در سمت کلاینت فقط برای
  پنهان/آشکار کردن عناصر رابط است. هر درخواست دوباره در سرور بررسی می‌شود.
- **مرورگر کاربر هیچ‌گاه مستقیماً با بک‌اند حرف نمی‌زند**: همه‌ی درخواست‌ها نسبی
  (`/api/...`) هستند و Next آن‌ها را پراکسی می‌کند. این هم برای محیط پیش‌نمایش لازم
  است و هم اجازه می‌دهد کوکی‌های `HttpOnly` و `SameSite` درست کار کنند.

## ۳. تصمیم‌های فنی و دلیل آن‌ها

| تصمیم | دلیل | بدیل رد شده |
|---|---|---|
| Fastify به‌جای Express | اعتبارسنجی و سریال‌سازی سریع‌تر، پشتیبانی درجه‌یک از TypeScript و ESM | Express (اکوسیستم قدیمی‌تر برای Zod/ESM) |
| Drizzle به‌جای Prisma | SQL شفاف، مهاجرت قابل بازبینی، امکان نوشتن پرس‌وجوهای تحلیلی خام | Prisma (لایه انتزاع بیش‌ازحد برای پرس‌وجوهای آماری) |
| جست‌وجو روی PostgreSQL | حذف وابستگی عملیاتی در گام اول، کنترل کامل روی نرمال‌سازی فارسی | نصب اجباری OpenSearch از روز اول |
| PGlite در محیط توسعه | همان اسکیما و همان مهاجرت‌ها بدون نیاز به Docker | SQLite (تفاوت گویش SQL با تولید) |
| Server Component برای صفحات محتوایی | SEO واقعی، بدون Waterfall درخواست، بدون افشای منطق | CSR کامل |

## ۴. جریان یک گزارش مردمی (End-to-End)

```
شهروند → POST /api/civic/reports
  ├── اعتبارسنجی Zod
  ├── تولید کد عمومی R-YYYY-XXXXXX
  ├── ذخیره مدارک (اعتبارسنجی Magic Number)
  └── وضعیت: submitted
        ↓
ناظر → POST /api/civic/moderation/reports/:id  {action: accept}
  ├── moderateReport()
  ├── routeReport() ← موتور مسیریابی امتیازی
  ├── ساخت پرونده C-YYYY-XXXXXX + محاسبه slaDueAt از شدت
  └── ثبت رویداد + Audit
        ↓
سازمان → POST /api/civic/cases/:id/assign / status / responses
  ├── assertCaseAccess() ← جداسازی چند-مستاجری
  ├── CASE_TRANSITIONS ← فقط گذارهای مجاز
  └── پاسخ رسمی با نشان تأیید در نمای عمومی
        ↓
کارگر sla-sweep → علامت‌گذاری نقض مهلت + تشدید + اعلان
```

## ۵. اصول ثابت پروژه

1. **هیچ داده‌ی جعلی**: امتیاز، نظر، نرخ رضایت و «تأیید» ساختگی تولید نمی‌شود.
   داده‌های نمونه با `dataSource='demo'` علامت‌گذاری و در رابط کاربر با برچسب
   «داده نمونه» نمایش داده می‌شوند.
2. **نبودِ داده = اعلام صریح**: مثلاً `organizationScorecard` وقتی پرونده‌ای وجود
   ندارد `hasData: false` برمی‌گرداند و رابط کاربر «داده کافی نیست» نشان می‌دهد.
3. **امنیت در سرور، نه در ظاهر**: هر محدودیتی که در رابط کاربر دیده می‌شود، معادل
   سروری دارد. آزمون‌های `backend/tests/critical.test.ts` دقیقاً همین را بررسی می‌کنند.
4. **قابلیت حسابرسی**: هر کنش حساس (تأیید، تغییر نقش، اصلاحیه، تغییر تنظیمات،
   تلاش ناموفق برای ویرایش محتوای قفل‌شده) در `audit_logs` ثبت می‌شود.
