๐Ÿ›๏ธ ุชุตู…ูŠู… ู…ุนู…ุงุฑูŠุฉ ู†ุธุงู… ู…ู†ุตุฉ “ู„ุฌู†ุฉ ุชุตุญูŠุญ ุฃูˆุถุงุน ุงู„ูŠู…ู†ูŠูŠู†” ููŠ ุงู„ู…ู…ู„ูƒุฉ ุงู„ุนุฑุจูŠุฉ ุงู„ุณุนูˆุฏูŠุฉ

๐Ÿ“Š 1 ู…ุดุงู‡ุฏุฉ

๐Ÿ›๏ธ ุชุตู…ูŠู… ู…ุนู…ุงุฑูŠุฉ ู†ุธุงู… ู…ู†ุตุฉ “ู„ุฌู†ุฉ ุชุตุญูŠุญ ุฃูˆุถุงุน ุงู„ูŠู…ู†ูŠูŠู†” ููŠ ุงู„ู…ู…ู„ูƒุฉ ุงู„ุนุฑุจูŠุฉ ุงู„ุณุนูˆุฏูŠุฉ

ุจู†ุงุกู‹ ุนู„ู‰ ุงู„ู…ุนุทูŠุงุช ุงู„ู…ู‚ุฏู…ุฉุŒ ุณุฃู‚ุฏู… ู„ูƒ ุชุตู…ูŠู…ุงู‹ ู‡ู†ุฏุณูŠุงู‹ ู…ุชูƒุงู…ู„ุงู‹ ูŠุบุทูŠ ุงู„ู…ุนู…ุงุฑูŠุฉุŒ ุงู„ุชู‚ู†ูŠุงุชุŒ ุงู„ุฃู…ุงู†ุŒ ูˆุงุฌู‡ุงุช APIุŒ ูˆุชุฌุฑุจุฉ ุงู„ู…ุณุชุฎุฏู…. ุงู„ู…ุดุฑูˆุน ุฌุฏูŠุฏ ูˆูŠุณุชู‡ุฏู ุฌู…ู‡ูˆุฑุงู‹ ู…ุญุฏู‘ุฏุงู‹ (ูŠู…ู†ูŠูˆู† ููŠ ุงู„ุณุนูˆุฏูŠุฉ) ุจู…ุชุทู„ุจุงุช ุญูƒูˆู…ูŠุฉ ุญุณู‘ุงุณุฉ.


1๏ธโƒฃ ุชุญู„ูŠู„ ุงู„ู…ุชุทู„ุจุงุช

ุงู„ู…ุชุทู„ุจุงุช ุงู„ูˆุธูŠููŠุฉ (Functional)

#ุงู„ู…ุชุทู„ุจุงู„ุฃูˆู„ูˆูŠุฉ
F1ุชุณุฌูŠู„ ุจูŠุงู†ุงุช ุงู„ู…ุณุชููŠุฏ (ู†ู…ูˆุฐุฌ ู…ุชุนุฏุฏ ุงู„ุญู‚ูˆู„)๐Ÿ”ด ุญุฑุฌุฉ
F2ุฑูุน ุงู„ู…ุฑูู‚ุงุช (ุดุฎุตูŠุฉ ุฅุฌุจุงุฑูŠุฉ + ู…ุณุชู†ุฏุงุช ุงุฎุชูŠุงุฑูŠุฉ)๐Ÿ”ด ุญุฑุฌุฉ
F3ุงู„ุชุญู‚ู‚ ู…ู† ุตุญุฉ ุงู„ุจูŠุงู†ุงุช (Validation)๐Ÿ”ด ุญุฑุฌุฉ
F4ุฅุฑุณุงู„ ุงู„ุทู„ุจ ู„ุฏูˆุฑุฉ ู…ุฑุงุฌุนุฉ (Workflow)๐Ÿ”ด ุญุฑุฌุฉ
F5ุชูˆู„ูŠุฏ ุจุทุงู‚ุฉ ุชูˆุซูŠู‚ ู…ุคู‚ุชุฉ PDF ู…ุน QR/RFID๐Ÿ”ด ุญุฑุฌุฉ
F6ุทุจุงุนุฉ ุญุฒู…ุฉ ูƒุงู…ู„ุฉ (ุจุทุงู‚ุฉ + ุจูŠุงู†ุงุช + ู…ุณุชู†ุฏุงุช + ุฅุฑุดุงุฏุงุช)๐ŸŸ  ุนุงู„ูŠุฉ
F7ุฅุนุงุฏุฉ ุทุจุงุนุฉ ุงู„ุทู„ุจ ุจุนุฏ ุงู„ุฅุฑุณุงู„๐ŸŸ  ุนุงู„ูŠุฉ
F8ุจุญุซ/ุงุณุชุนู„ุงู… ุนู† ุญุงู„ุฉ ุงู„ุทู„ุจ (ู„ู„ู…ุณุชููŠุฏ)๐ŸŸก ู…ุชูˆุณุทุฉ
F9ู„ูˆุญุฉ ุชุญูƒู… ู„ู„ู…ุฑุงุฌุนูŠู† ูˆุงู„ู…ุฏู‚ู‚ูŠู†๐Ÿ”ด ุญุฑุฌุฉ
F10ุชู‚ุงุฑูŠุฑ ูˆุฅุญุตุงุกุงุช ู„ู„ุฌู†ุฉ๐ŸŸก ู…ุชูˆุณุทุฉ

ุงู„ู…ุชุทู„ุจุงุช ุบูŠุฑ ุงู„ูˆุธูŠููŠุฉ (NFR)

  • ุงู„ุฃุฏุงุก: ุงุณุชุฌุงุจุฉ < 2s ู„ู€ 95% ู…ู† ุงู„ุทู„ุจุงุชุŒ ุฏุนู… 10,000 ู…ุณุชุฎุฏู… ู…ุชุฒุงู…ู† (ุงู„ู…ุฑุญู„ุฉ ุงู„ุฃูˆู„ู‰).
  • ุงู„ุชูˆุงูุฑ: 99.9% Uptime.
  • ุงู„ุฃู…ุงู†: ุชุดููŠุฑ TLS 1.3ุŒ AES-256 at restุŒ MFA ู„ู„ู…ุฑุงุฌุนูŠู†.
  • ุงู„ุงู…ุชุซุงู„: NCA (ุงู„ุณุนูˆุฏูŠุฉ) ECCุŒ SDAIA PDPLุŒ ISO 27001ุŒ GDPR (ู„ู„ุจูŠุงู†ุงุช ุงู„ุญุณุงุณุฉ).
  • ุงู„ู„ุบุงุช: ุงู„ุนุฑุจูŠุฉ (RTL) ุฃุณุงุณูŠุŒ ุงู„ุฅู†ุฌู„ูŠุฒูŠุฉ ุซุงู†ูˆูŠ.
  • ุงู„ุชูˆุงูู‚: ุฌูˆุงู„ ุฃูˆู„ุงู‹ (Mobile-first)ุŒ ุฏุนู… ุงู„ู…ุชุตูุญุงุช ุงู„ุญุฏูŠุซุฉ.

2๏ธโƒฃ ุงู„ู…ุนู…ุงุฑูŠุฉ ุงู„ู…ู‚ุชุฑุญุฉ (System Architecture)

ุงู„ู†ู…ุท ุงู„ู…ุนู…ุงุฑูŠ: Modular Monolith โ†’ Microservices (ุชุฏุฑูŠุฌูŠุงู‹)

text

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                    CDN + WAF (Cloudflare/Akamai)            โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                            โ”‚
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚              API Gateway (Kong / AWS API Gateway)           โ”‚
โ”‚         - Rate Limiting - Auth - Routing - Logging          โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
        โ”‚              โ”‚              โ”‚              โ”‚
   โ”Œโ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”    โ”Œโ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”    โ”Œโ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”    โ”Œโ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”
   โ”‚ Identityโ”‚    โ”‚Applic. โ”‚    โ”‚Documentโ”‚    โ”‚Workflow โ”‚
   โ”‚ Service โ”‚    โ”‚Service โ”‚    โ”‚Service โ”‚    โ”‚ Service โ”‚
   โ”‚ (Auth)  โ”‚    โ”‚(CRUD)  โ”‚    โ”‚(Files) โ”‚    โ”‚(Review) โ”‚
   โ””โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”˜    โ””โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”˜    โ””โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”˜    โ””โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”˜
        โ”‚              โ”‚              โ”‚              โ”‚
        โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                            โ”‚
        โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
        โ”‚                   โ”‚                   โ”‚
   โ”Œโ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”        โ”Œโ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”        โ”Œโ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”
   โ”‚PostgreSQLโ”‚        โ”‚  Redis  โ”‚        โ”‚ MinIO/S3โ”‚
   โ”‚ (Primary)โ”‚        โ”‚ (Cache) โ”‚        โ”‚ (Files) โ”‚
   โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜        โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜        โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                            โ”‚
                   โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
                   โ”‚  Message Queue  โ”‚
                   โ”‚ (RabbitMQ/Kafka)โ”‚
                   โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                            โ”‚
              โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
              โ”‚             โ”‚             โ”‚
        โ”Œโ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”  โ”Œโ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”  โ”Œโ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”
        โ”‚Notificationโ”‚ โ”‚ PDF Gen โ”‚  โ”‚Analytics โ”‚
        โ”‚  (SMS/Email)โ”‚ โ”‚ Service โ”‚  โ”‚ Service  โ”‚
        โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

ุทุจู‚ุงุช ุงู„ู†ุธุงู…:

  1. ุทุจู‚ุฉ ุงู„ุนุฑุถ (Presentation): ุชุทุจูŠู‚ ูˆูŠุจ (React/Next.js) + PWA ู„ู„ุฌูˆุงู„.
  2. ุทุจู‚ุฉ ุงู„ู€ API: RESTful + GraphQL ู„ู„ุงุณุชุนู„ุงู…ุงุช ุงู„ู…ุนู‚ุฏุฉ.
  3. ุทุจู‚ุฉ ุงู„ุฎุฏู…ุงุช (Business Logic): Modular Monolith ูŠุจุฏุฃ ุจู€ 4 ูˆุญุฏุงุช.
  4. ุทุจู‚ุฉ ุงู„ุจูŠุงู†ุงุช: PostgreSQL (ุจูŠุงู†ุงุช ุนู„ุงุฆู‚ูŠุฉ) + Redis (Cache) + S3 (ู…ู„ูุงุช).
  5. ุทุจู‚ุฉ ุงู„ุชูƒุงู…ู„: APIs ู…ุน ุงู„ุฃู†ุธู…ุฉ ุงู„ุณุนูˆุฏูŠุฉ (ุฃุจุดุฑ/ู†ุงุฌุฒ – ุฅู† ู„ุฒู…) + ุงู„ุฌูˆุงุฒุงุช ุงู„ูŠู…ู†ูŠุฉ ู„ู„ุชุญู‚ู‚.

3๏ธโƒฃ ุงู„ุชู‚ู†ูŠุงุช ุงู„ู…ู‚ุชุฑุญุฉ (Tech Stack)

ุงู„ูˆุงุฌู‡ุฉ ุงู„ุฃู…ุงู…ูŠุฉ (Frontend)

ุงู„ุชู‚ู†ูŠุฉุงู„ุณุจุจ
Next.js 14 (App Router)SSR + SEO + ุฃุฏุงุก ุนุงู„ู + RTL ู…ู…ุชุงุฒ
TypeScriptุฃู…ุงู† ุงู„ุฃู†ูˆุงุน ู„ุชู‚ู„ูŠู„ ุงู„ุฃุฎุทุงุก
TailwindCSS + shadcn/uiุชุตู…ูŠู… ุณุฑูŠุน + ุฏุนู… RTL
React Hook Form + Zodุฅุฏุงุฑุฉ ุงู„ู†ู…ุงุฐุฌ + Validation ู‚ูˆูŠ
i18nextุฏุนู… ู…ุชุนุฏุฏ ุงู„ู„ุบุงุช
TanStack Queryุฅุฏุงุฑุฉ ุญุงู„ุฉ ุงู„ุณูŠุฑูุฑ

ุงู„ูˆุงุฌู‡ุฉ ุงู„ุฎู„ููŠุฉ (Backend)

ุงู„ุชู‚ู†ูŠุฉุงู„ุณุจุจ
NestJS (Node.js) ุฃูˆ Spring BootModular + TypeScript + ุฌุงู‡ุฒ ู„ู„ู…ุคุณุณุงุช
PostgreSQL 16ACID + JSONB + ู…ุฑูˆู†ุฉ
Prisma / TypeORMORM ุขู…ู†
RedisCache + Sessions
MinIO (On-Prem) ุฃูˆ AWS S3ุชุฎุฒูŠู† ุงู„ู…ู„ูุงุช
RabbitMQ / Kafkaุงู„ุทูˆุงุจูŠุฑ ู„ู„ุนู…ู„ูŠุงุช ุบูŠุฑ ุงู„ู…ุชุฒุงู…ู†ุฉ
BullMQู…ุนุงู„ุฌุฉ ุงู„ู…ู‡ุงู… (PDFุŒ ุฅุดุนุงุฑุงุช)

ุงู„ุจู†ูŠุฉ ุงู„ุชุญุชูŠุฉ

ุงู„ู…ูƒูˆู†ุงู„ุชู‚ู†ูŠุฉ
ุงู„ุญุงูˆูŠุงุชDocker + Kubernetes (K8s)
CI/CDGitLab CI / GitHub Actions + ArgoCD
ุงู„ู…ุฑุงู‚ุจุฉPrometheus + Grafana + Loki
ุงู„ุชุชุจุนOpenTelemetry + Jaeger
ุฅุฏุงุฑุฉ ุงู„ุฃุณุฑุงุฑHashiCorp Vault
ุจูˆุงุจุฉ APIKong / Traefik

ุชูˆู„ูŠุฏ PDF

  • Puppeteer / Playwright (HTML โ†’ PDF): ู„ู…ุฑูˆู†ุฉ ุนุงู„ูŠุฉ ููŠ ุงู„ู‚ูˆุงู„ุจ ุงู„ุนุฑุจูŠุฉ RTL.
  • pdf-lib: ู„ู„ุชู„ุงุนุจ ุจุงู„ู€ PDF (ุฅุถุงูุฉ QRุŒ ุฎุชู…).
  • QR Code: ู…ูƒุชุจุฉ qrcode ู„ุชูˆู„ูŠุฏ ุฑู…ุฒ ุงู„ุชุญู‚ู‚.

4๏ธโƒฃ ุงู„ุฃู…ุงู† ูˆุงู„ุงู…ุชุซุงู„ (NCA + PDPL + ISO 27001)

ุงู„ุถูˆุงุจุท ุงู„ุฃุณุงุณูŠุฉ (NCA ECC-1:2018)

ุงู„ู…ุฌุงู„ุงู„ุฅุฌุฑุงุก
ุฅุฏุงุฑุฉ ุงู„ู‡ูˆูŠุฉMFA ุฅุฌุจุงุฑูŠ ู„ู„ู…ูˆุธููŠู†ุŒ OTP ู„ู„ุฌูˆุงู„ ู„ู„ู…ุณุชููŠุฏูŠู†
ุชุดููŠุฑ ุงู„ุจูŠุงู†ุงุชTLS 1.3ุŒ AES-256 at restุŒ ุชุดููŠุฑ ุงู„ุฃุนู…ุฏุฉ ุงู„ุญุณุงุณุฉ (ุฑู‚ู… ุงู„ุฌูˆุงู„ุŒ ุฑู‚ู… ุงู„ุจุทุงู‚ุฉ)
ุฅุฏุงุฑุฉ ุงู„ูˆุตูˆู„RBAC + ABACุŒ ู…ุจุฏุฃ ุฃู‚ู„ ุตู„ุงุญูŠุฉ
ุณุฌู„ุงุช ุงู„ุชุฏู‚ูŠู‚Audit Log ู„ูƒู„ ุนู…ู„ูŠุฉ (ู…ู†ุŒ ู…ุงุฐุงุŒ ู…ุชู‰ุŒ ู…ู† ุฃูŠู†)
ุญู…ุงูŠุฉ ุงู„ุจูŠุงู†ุงุช ุงู„ุดุฎุตูŠุฉPDPL: ู…ูˆุงูู‚ุฉ ุตุฑูŠุญุฉุŒ ุญู‚ ุงู„ูˆุตูˆู„/ุงู„ุญุฐูุŒ ุฅุฎูุงุก ุงู„ู‡ูˆูŠุฉ ููŠ ุงู„ุชู‚ุงุฑูŠุฑ
ุฅุฏุงุฑุฉ ุงู„ุซุบุฑุงุชSAST/DAST ููŠ CI/CDุŒ ูุญุต ุดู‡ุฑูŠ
ุงู„ุงุณุชุฌุงุจุฉ ู„ู„ุญูˆุงุฏุซSIEM (Wazuh/ELK) + ุฎุทุฉ ุงุณุชุฌุงุจุฉ
ุงู„ู†ุณุฎ ุงู„ุงุญุชูŠุงุทูŠ3-2-1ุŒ ุงุฎุชุจุงุฑ ุงุณุชุนุงุฏุฉ ุฑุจุน ุณู†ูˆูŠ

ุถูˆุงุจุท ุฎุงุตุฉ ุจุงู„ู…ุดุฑูˆุน

  • ุงู„ุชุญู‚ู‚ ู…ู† ุงู„ู‡ูˆูŠุฉ ุงู„ูŠู…ู†ูŠุฉ: ุชูƒุงู…ู„ ู…ุน ุงู„ุฌูˆุงุฒุงุช ุงู„ูŠู…ู†ูŠุฉ (ุฅู† ุฃู…ูƒู†) ุฃูˆ ุงู„ุชุญู‚ู‚ ุงู„ูŠุฏูˆูŠ.
  • ู…ู†ุน ุงู„ุชูƒุฑุงุฑ: ูุญุต ุฑู‚ู… ุงู„ุจุทุงู‚ุฉ/ุงู„ุฌูˆุงุฒ ู‚ุจู„ ุงู„ุญูุธ.
  • Watermark ุนู„ู‰ ุงู„ุจุทุงู‚ุฉ: ุงุณู… ุงู„ู„ุฌู†ุฉ + ุชุงุฑูŠุฎ + ุฑู‚ู… ุชุณู„ุณู„ูŠ.
  • QR ู„ู„ุชุญู‚ู‚: ูŠุดูŠุฑ ุฅู„ู‰ ู†ู‚ุทุฉ ุชุญู‚ู‚ ุญูƒูˆู…ูŠุฉ (Verify Endpoint).
  • ุชุณุฌูŠู„ ูƒู„ ุทุจุงุนุฉ: ู„ู…ู†ุน ุงู„ุชุฒูˆูŠุฑ.
  • Data Residency: ุงู„ุจูŠุงู†ุงุช ุฏุงุฎู„ ุงู„ุณุนูˆุฏูŠุฉ (ูˆูู‚ NCA).

5๏ธโƒฃ ุชุตู…ูŠู… ูˆุงุฌู‡ุงุช API

ู†ู…ุท RESTful ู…ุน ุฅุตุฏุงุฑุงุช

text

POST   /api/v1/applicants              # ุฅู†ุดุงุก ุทู„ุจ ุฌุฏูŠุฏ
GET    /api/v1/applicants/{id}         # ุฌู„ุจ ุทู„ุจ
PATCH  /api/v1/applicants/{id}         # ุชุนุฏูŠู„ ุฌุฒุฆูŠ
POST   /api/v1/applicants/{id}/submit  # ุฅุฑุณุงู„ ู„ู„ู…ุฑุงุฌุนุฉ
GET    /api/v1/applicants/{id}/pdf     # ุชูˆู„ูŠุฏ/ุฌู„ุจ ุญุฒู…ุฉ ุงู„ุทุจุงุนุฉ
POST   /api/v1/applicants/{id}/reprint # ุฅุนุงุฏุฉ ุทุจุงุนุฉ
GET    /api/v1/applicants/{id}/status  # ุญุงู„ุฉ ุงู„ุทู„ุจ

POST   /api/v1/files/upload            # ุฑูุน ู…ุฑูู‚ (presigned URL)
DELETE /api/v1/files/{fileId}          # ุญุฐู ู…ุฑูู‚

GET    /api/v1/admin/applications      # ู‚ุงุฆู…ุฉ ู„ู„ู…ุฑุงุฌุนูŠู† (ู…ุน ูู„ุชุฑุฉ)
POST   /api/v1/admin/applications/{id}/approve
POST   /api/v1/admin/applications/{id}/reject
GET    /api/v1/admin/reports           # ุชู‚ุงุฑูŠุฑ

POST   /api/v1/auth/login              # ุฏุฎูˆู„ ุงู„ู…ุณุชููŠุฏ (OTP)
POST   /api/v1/auth/staff/login        # ุฏุฎูˆู„ ุงู„ู…ูˆุธู (MFA)
POST   /api/v1/auth/refresh            # ุชุฌุฏูŠุฏ ุงู„ุชูˆูƒู†

GET    /api/v1/verify/{token}          # ุชุญู‚ู‚ ุนุงู… ู…ู† ุจุทุงู‚ุฉ

ู…ุซุงู„ Request: ุฅู†ุดุงุก ุทู„ุจ

json

POST /api/v1/applicants
{
  "fullName": "ู…ุญู…ุฏ ุฃุญู…ุฏ ุนู„ูŠ ุงู„ุญุณู†ูŠ",
  "idType": "national_id",           // national_id | passport | none
  "idNumber": "01234567",
  "maritalStatus": "married",
  "gender": "male",
  "occupation": "engineer",
  "birthPlace": "ุตู†ุนุงุก",
  "birthDate": "1990-05-15",
  "residencePlace": "ุงู„ุฑูŠุงุถ",
  "mobile": "966512345678",
  "email": "user@example.com",
  "region": "riyadh",
  "addressYemen": {
    "governorate": "ุตู†ุนุงุก",
    "district": "ุงู„ุณุจุนูŠู†",
    "village": "ุจูŠุช ุจูˆุณ"
  },
  "attachments": {
    "personalPhoto": "file_id_1",
    "passport": "file_id_2"
  }
}

ู…ุนุงูŠูŠุฑ ุงู„ู€ API

  • ุงู„ู…ุตุงุฏู‚ุฉ: OAuth 2.0 + JWT (Access 15min + Refresh 7d).
  • ุงู„ุฅุตุฏุงุฑุงุช: /v1/ ููŠ ุงู„ู…ุณุงุฑ.
  • ุงู„ุฃุฎุทุงุก: RFC 7807 (Problem Details).
  • Pagination: Cursor-based.
  • Rate Limiting: 100 req/min ู„ู„ู…ุณุชููŠุฏุŒ 1000 ู„ู„ู…ูˆุธู.
  • OpenAPI 3.1: ุชูˆุซูŠู‚ ุชู„ู‚ุงุฆูŠ (Swagger UI).

6๏ธโƒฃ ุชุฌุฑุจุฉ ุงู„ู…ุณุชุฎุฏู… (UX) ู„ู„ุฎุฏู…ุงุช ุงู„ุญูƒูˆู…ูŠุฉ

ู…ุจุงุฏุฆ ุงู„ุชุตู…ูŠู…

  1. ุงู„ุจุณุงุทุฉ: ุฎุทูˆุงุช ูˆุงุถุญุฉุŒ ู„ุง ู…ุตุทู„ุญุงุช ุชู‚ู†ูŠุฉ.
  2. ุงู„ุดูุงููŠุฉ: ุนุฑุถ ุณุจุจ ูƒู„ ุฎุทุฃ ููˆุฑุงู‹.
  3. RTL ุฃุตู„ูŠ: ู„ูŠุณ ุชุฑุฌู…ุฉุŒ ุจู„ ุชุตู…ูŠู… ุนุฑุจูŠ.
  4. Mobile-First: 80% ู…ู† ุงู„ู…ุณุชุฎุฏู…ูŠู† ุนู„ู‰ ุงู„ุฌูˆุงู„.
  5. ุฅู…ูƒุงู†ูŠุฉ ุงู„ูˆุตูˆู„: WCAG 2.1 AA.

ุฑุญู„ุฉ ุงู„ู…ุณุชููŠุฏ (User Journey)

text

[ุงู„ุตูุญุฉ ุงู„ุฑุฆูŠุณูŠุฉ]
    โ”‚
    โ”œโ”€โ–บ ุดุฑุญ ุงู„ุฎุฏู…ุฉ + ุชู†ุจูŠู‡ (ุฌูˆุงุฒ ูŠู…ู†ูŠ = ู„ุง ูˆุซูŠู‚ุฉ ู…ุคู‚ุชุฉ)
    โ”‚
    โ–ผ
[ู†ู…ูˆุฐุฌ ู…ุชุนุฏุฏ ุงู„ุฎุทูˆุงุช]  โ†โ”€โ”€ ุดุฑูŠุท ุชู‚ุฏู…
    โ”œโ”€ 1. ุงู„ุจูŠุงู†ุงุช ุงู„ุดุฎุตูŠุฉ
    โ”œโ”€ 2. ุงู„ุนู†ูˆุงู† (ุงู„ูŠู…ู† + ุงู„ุฅู‚ุงู…ุฉ)
    โ”œโ”€ 3. ู…ุนู„ูˆู…ุงุช ุงู„ุงุชุตุงู„
    โ”œโ”€ 4. ุงู„ู…ุฑูู‚ุงุช
    โ””โ”€ 5. ู…ุฑุงุฌุนุฉ + ู…ูˆุงูู‚ุฉ
    โ”‚
    โ–ผ
[ุฅุฑุณุงู„ ุงู„ุทู„ุจ] โ†’ ุฑู‚ู… ู…ุฑุฌุนูŠ + SMS
    โ”‚
    โ–ผ
[ุตูุญุฉ ุงู„ู†ุฌุงุญ]
    โ”œโ”€โ–บ ุทุจุงุนุฉ ุงู„ุญุฒู…ุฉ (PDF ู…ุชุนุฏุฏ ุงู„ุตูุญุงุช)
    โ”œโ”€โ–บ ุฅุนุงุฏุฉ ุงู„ุทุจุงุนุฉ ู„ุงุญู‚ุงู‹
    โ””โ”€โ–บ ุฅุฑุดุงุฏุงุช ู…ุง ุจุนุฏ ุงู„ุทุจุงุนุฉ

ุชุญุณูŠู†ุงุช UX ู…ู‚ุชุฑุญุฉ

  • ุญูุธ ุชู„ู‚ุงุฆูŠ (Draft): ู„ุง ุชูู‚ุฏ ุงู„ุจูŠุงู†ุงุช ุนู†ุฏ ุงู†ู‚ุทุงุน ุงู„ุฅู†ุชุฑู†ุช.
  • Validation ููˆุฑูŠ: ุนู†ุฏ blur ูˆู„ูŠุณ ุนู†ุฏ ุงู„ุฅุฑุณุงู„ ูู‚ุท.
  • ุฑูุน ุงู„ู…ู„ูุงุช: Drag & Drop + ูƒุงู…ูŠุฑุง ุงู„ุฌูˆุงู„ ู…ุจุงุดุฑุฉ.
  • ู…ุนุงูŠู†ุฉ ุงู„ุตูˆุฑุฉ: ู‚ุจู„ ุงู„ุญูุธ.
  • ุดุฑูŠุท ุชู‚ุฏู…: ูŠูˆุถุญ ุงู„ุฎุทูˆุฉ ุงู„ุญุงู„ูŠุฉ.
  • ุฑุณุงุฆู„ ุฎุทุฃ ูˆุงุถุญุฉ: “ุงู„ุงุณู… ูŠุฌุจ ุฃู† ูŠูƒูˆู† ุฑุจุงุนูŠุงู‹” ุจุฏู„ุงู‹ ู…ู† “ุฎุทุฃ ููŠ ุงู„ุญู‚ู„”.
  • ูˆุถุน ู„ูŠู„ูŠ: ุงุฎุชูŠุงุฑูŠ.
  • ุฏุนู… ุงู„ู‚ุงุฑุฆ ุงู„ุดุงุดูŠ: ARIA labels.

ู‚ุงู„ุจ ุญุฒู…ุฉ ุงู„ุทุจุงุนุฉ (PDF)

  1. ุงู„ุตูุญุฉ 1: ุจุทุงู‚ุฉ ุงู„ุชูˆุซูŠู‚ ุงู„ู…ุคู‚ุชุฉ (QR + ุฎุชู… + ุฑู‚ู… ุชุณู„ุณู„ูŠ).
  2. ุงู„ุตูุญุฉ 2: ุจูŠุงู†ุงุช ุงู„ู…ุณุชููŠุฏ ูƒุงู…ู„ุฉ.
  3. ุงู„ุตูุญุฉ 3+: ุงู„ู…ุณุชู†ุฏุงุช ุงู„ู…ุฑูู‚ุฉ (ุนุฏุง ุงู„ุตูˆุฑุฉ ุงู„ุดุฎุตูŠุฉ).
  4. ุงู„ุตูุญุฉ ุงู„ุฃุฎูŠุฑุฉ: ุฅุฑุดุงุฏุงุช ู…ุง ุจุนุฏ ุงู„ุทุจุงุนุฉ.

7๏ธโƒฃ ู…ุฑุงุฌุนุฉ ุงู„ูƒูˆุฏ ูˆุงู„ู…ุนู…ุงุฑูŠุฉ

ู‚ุงุฆู…ุฉ ุชุฏู‚ูŠู‚ ู„ู„ู…ุฑุงุฌุนุฉ (Checklist)

  • โ–ก ุงู„ุฃู…ุงู†: ู„ุง ุชูˆุฌุฏ ุจูŠุงู†ุงุช ุญุณุงุณุฉ ููŠ ุงู„ู€ logsุŒ ู„ุง evalุŒ ุฅุฏุฎุงู„ ู…ูุนู‚ู‘ู….
  • โ–ก ุงู„ุฃุฏุงุก: ุงุณุชุนู„ุงู…ุงุช N+1ุŒ ูู‡ุงุฑุณ (Indexes)ุŒ Cache.
  • โ–ก ุงู„ุงุฎุชุจุงุฑุงุช: Unit โ‰ฅ 80%ุŒ IntegrationุŒ E2E (Playwright).
  • โ–ก ุงู„ุชูˆุซูŠู‚: OpenAPI ู…ุญุฏู‘ุซุŒ READMEุŒ ADRs.
  • โ–ก ุงู„ุงู…ุชุซุงู„: Audit LogุŒ Data Retention Policy.
  • โ–ก ู‚ุงุจู„ูŠุฉ ุงู„ุชูˆุณุน: StatelessุŒ Horizontal Scaling.
  • โ–ก ุงู„ุชุนุงููŠ: Circuit BreakerุŒ RetryุŒ Timeout.

ุฃุฏูˆุงุช ู…ู‚ุชุฑุญุฉ

  • Linting: ESLint + Prettier + SonarQube.
  • Testing: Jest + Supertest + Playwright.
  • Security: Snyk + OWASP ZAP + Trivy.
  • Performance: k6 + Lighthouse CI.

8๏ธโƒฃ ุฎุงุฑุทุฉ ุงู„ุทุฑูŠู‚ ุงู„ู…ู‚ุชุฑุญุฉ (Roadmap)

ุงู„ู…ุฑุญู„ุฉุงู„ู…ุฏุฉุงู„ู…ุฎุฑุฌุงุช
1. Discovery2-3 ุฃุณุงุจูŠุนู…ุชุทู„ุจุงุช ู…ูุตู„ุฉุŒ WireframesุŒ ู…ุฎุทุท ERD
2. MVP8-10 ุฃุณุงุจูŠุนู†ู…ูˆุฐุฌ + ุฑูุน ู…ุฑูู‚ุงุช + ู…ุฑุงุฌุนุฉ + PDF
3. Hardening3-4 ุฃุณุงุจูŠุนุฃู…ุงู†ุŒ ุงุฎุชุจุงุฑุงุชุŒ ุชุฏู‚ูŠู‚ NCA
4. Pilot2 ุฃุณุงุจูŠุน100 ู…ุณุชุฎุฏู… ุชุฌุฑูŠุจูŠ
5. Launch1 ุฃุณุจูˆุนุฅุทู„ุงู‚ ุฑุณู…ูŠ + ู…ุฑุงู‚ุจุฉ
6. Iterateู…ุณุชู…ุฑุชุญุณูŠู†ุงุช ุจู†ุงุกู‹ ุนู„ู‰ ุงู„ุชุบุฐูŠุฉ ุงู„ุฑุงุฌุนุฉ

9๏ธโƒฃ ุชูˆุตูŠุงุช ุญุฑุฌุฉ ู„ู„ู…ุดุฑูˆุน

  1. โš–๏ธ ุงู„ุงู…ุชุซุงู„ ุฃูˆู„ุงู‹: ุงู„ุชุนุงูˆู† ุงู„ู…ุจูƒุฑ ู…ุน NCA ูˆ SDAIA ู‚ุจู„ ูƒุชุงุจุฉ ุณุทุฑ ูƒูˆุฏ.
  2. ๐Ÿ” ุงู„ุชุญู‚ู‚ ู…ู† ุงู„ู‡ูˆูŠุฉ: ุชูƒุงู…ู„ ู…ุน ุงู„ุฌู‡ุงุช ุงู„ูŠู…ู†ูŠุฉ ุฅู† ุฃู…ูƒู†ุŒ ูˆุฅู„ุง ุนู…ู„ูŠุฉ ุชุญู‚ู‚ ูŠุฏูˆูŠ ู…ูˆุซู‚ุฉ.
  3. ๐Ÿ“„ PDF ุนุฑุจูŠ: ุงุฎุชุจุฑ Puppeteer ู…ุน ุฎุทูˆุท ุนุฑุจูŠุฉ (Cairo, Tajawal) ู‚ุจู„ ุงู„ุงู„ุชุฒุงู….
  4. ๐ŸŒ Data Residency: ุงุณุชุถุงูุฉ ุฏุงุฎู„ ุงู„ุณุนูˆุฏูŠุฉ (STC Cloud, Oracle Jeddah, AWS Bahrain ู…ุน ุงุนุชุจุงุฑุงุช).
  5. ๐Ÿ”„ ุงู„ุชูƒุงู…ู„ ู…ุน ุฃุจุดุฑ: ุงุฎุชูŠุงุฑูŠ ู„ูƒู†ู‡ ูŠู‚ู„ู„ ุงู„ุงุญุชูŠุงู„.
  6. ๐Ÿ“Š ุชุญู„ูŠู„ุงุช: ู„ูˆุญุฉ KPIs (ุนุฏุฏ ุงู„ุทู„ุจุงุชุŒ ู…ุนุฏู„ ุงู„ู‚ุจูˆู„ุŒ ุฒู…ู† ุงู„ู…ุนุงู„ุฌุฉ).
  7. ๐Ÿ›ก๏ธ ุฎุทุฉ ุงุณุชุฌุงุจุฉ ู„ู„ุญูˆุงุฏุซ: ูุฑูŠู‚ + ุฃุฏูˆุงุช + ุณูŠู†ุงุฑูŠูˆู‡ุงุช.
  8. ๐Ÿ“ฑ PWA: ู„ุชู‚ู„ูŠู„ ุงู„ุงุนุชู…ุงุฏ ุนู„ู‰ App Store.

๐Ÿ”Ÿ ุงู„ุฎุทูˆุฉ ุงู„ุชุงู„ูŠุฉ

ู‡ู„ ุชุฑูŠุฏ ุฃู† ุฃุชุนู…ู‚ ููŠ ุฃุญุฏ ุงู„ุฌูˆุงู†ุจ ุงู„ุชุงู„ูŠุฉุŸ

  • ๐Ÿ“ ู…ุฎุทุท ERD ุชูุตูŠู„ูŠ ู„ู‚ุงุนุฏุฉ ุงู„ุจูŠุงู†ุงุช
  • ๐Ÿ”Œ ู…ูˆุงุตูุงุช OpenAPI ูƒุงู…ู„ุฉ ู„ุฌู…ูŠุน ุงู„ู€ endpoints
  • ๐ŸŽจ Wireframes / Mockups ู„ูˆุงุฌู‡ุฉ ุงู„ู†ู…ูˆุฐุฌ
  • ๐Ÿ”’ ู…ุตููˆูุฉ ุถูˆุงุจุท NCA ู…ูุตู„ุฉ ู„ูƒู„ ู…ูƒูˆู‘ู†
  • ๐Ÿ’ฐ ุชู‚ุฏูŠุฑ ุงู„ุชูƒุงู„ูŠู ูˆุงู„ู…ูˆุงุฑุฏ (ูุฑูŠู‚ + ุจู†ูŠุฉ ุชุญุชูŠุฉ)
  • ๐Ÿงช ุฎุทุฉ ุงุฎุชุจุงุฑุงุช ุดุงู…ู„ุฉ

ุฃุฎุจุฑู†ูŠ ุจุฃูˆู„ูˆูŠุชูƒ ูˆุณุฃุนู…ู‘ู‚ ููŠู‡ุง. ๐Ÿš€

๐Ÿ“ ู…ุฎุทุท ERD ุชูุตูŠู„ูŠ ู„ู‚ุงุนุฏุฉ ุงู„ุจูŠุงู†ุงุช

๐Ÿ“ ู…ุฎุทุท ERD ุชูุตูŠู„ูŠ ู„ู‚ุงุนุฏุฉ ุจูŠุงู†ุงุช ู…ู†ุตุฉ “ู„ุฌู†ุฉ ุชุตุญูŠุญ ุฃูˆุถุงุน ุงู„ูŠู…ู†ูŠูŠู†”

1๏ธโƒฃ ู†ุธุฑุฉ ุนุงู…ุฉ ุนู„ู‰ ุงู„ู…ุฎุทุท

ู‚ุงุนุฏุฉ ุงู„ุจูŠุงู†ุงุช ู…ุตู…ู…ุฉ ูˆูู‚ 3NF ู…ุน ุจุนุถ ุงู„ุชู†ุงุฒู„ุงุช ุงู„ู…ุฏุฑูˆุณุฉ ู„ู„ุฃุฏุงุกุŒ ูˆุชุชูƒูˆู† ู…ู† 8 ู…ุฌู…ูˆุนุงุช ุฑุฆูŠุณูŠุฉ:

text

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                    DATABASE SCHEMA GROUPS                    โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚  1. Identity & Auth      โ†’ ุงู„ู…ุณุชุฎุฏู…ูˆู† ูˆุงู„ุตู„ุงุญูŠุงุช            โ”‚
โ”‚  2. Applicants           โ†’ ุงู„ู…ุณุชููŠุฏูˆู† ูˆุจูŠุงู†ุงุชู‡ู…             โ”‚
โ”‚  3. Addresses            โ†’ ุงู„ุนู†ุงูˆูŠู† (ุงู„ูŠู…ู† + ุงู„ุฅู‚ุงู…ุฉ)        โ”‚
โ”‚  4. Attachments          โ†’ ุงู„ู…ุฑูู‚ุงุช ูˆุงู„ู…ู„ูุงุช                โ”‚
โ”‚  5. Applications         โ†’ ุงู„ุทู„ุจุงุช ูˆุฏูˆุฑุฉ ุญูŠุงุชู‡ุง             โ”‚
โ”‚  6. Workflow             โ†’ ุงู„ู…ุฑุงุฌุนุฉ ูˆุงู„ู‚ุฑุงุฑุงุช               โ”‚
โ”‚  7. Documents            โ†’ ุงู„ุจุทุงู‚ุงุช ูˆุงู„ุทุจุงุนุฉ                โ”‚
โ”‚  8. Audit & Reference    โ†’ ุงู„ุชุฏู‚ูŠู‚ ูˆุงู„ุจูŠุงู†ุงุช ุงู„ู…ุฑุฌุนูŠุฉ       โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

2๏ธโƒฃ ุงู„ู…ุฎุทุท ุงู„ุจุตุฑูŠ (ASCII ERD)

text

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”         โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚      users       โ”‚         โ”‚      roles       โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค         โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ PK id            โ”‚โ—„โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค PK id            โ”‚
โ”‚    username      โ”‚  M:N    โ”‚    name          โ”‚
โ”‚    email         โ”‚         โ”‚    permissions   โ”‚
โ”‚    password_hash โ”‚         โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
โ”‚    full_name     โ”‚                 โ–ฒ
โ”‚    role_id       โ”‚                 โ”‚
โ”‚    is_active     โ”‚         โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚    last_login    โ”‚         โ”‚  user_roles      โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜         โ”‚  (junction)      โ”‚
         โ”‚                   โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
         โ”‚ 1:N (reviewer)
         โ”‚
         โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                        applications                          โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ PK id                    โ”‚  BIGSERIAL                          โ”‚
โ”‚ UK reference_number      โ”‚  VARCHAR(20)  -- YEM-2026-000123   โ”‚
โ”‚ FK applicant_id          โ”‚  โ†’ applicants.id                    โ”‚
โ”‚    status                โ”‚  ENUM: draft, submitted, under_    โ”‚
โ”‚                          โ”‚  review, approved, rejected,        โ”‚
โ”‚                          โ”‚  printed, cancelled                 โ”‚
โ”‚    current_step          โ”‚  SMALLINT                           โ”‚
โ”‚ FK assigned_reviewer_id  โ”‚  โ†’ users.id (nullable)              โ”‚
โ”‚    submitted_at          โ”‚  TIMESTAMPTZ                        โ”‚
โ”‚    reviewed_at           โ”‚  TIMESTAMPTZ                        โ”‚
โ”‚    decided_at            โ”‚  TIMESTAMPTZ                        โ”‚
โ”‚    printed_count         โ”‚  SMALLINT DEFAULT 0                 โ”‚
โ”‚    last_printed_at       โ”‚  TIMESTAMPTZ                        โ”‚
โ”‚    notes                 โ”‚  TEXT                               โ”‚
โ”‚    created_at            โ”‚  TIMESTAMPTZ                        โ”‚
โ”‚    updated_at            โ”‚  TIMESTAMPTZ                        โ”‚
โ”‚    deleted_at            โ”‚  TIMESTAMPTZ (soft delete)          โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
           โ”‚ 1:1
           โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                        applicants                            โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ PK id                    โ”‚  BIGSERIAL                          โ”‚
โ”‚    full_name_ar          โ”‚  VARCHAR(200)  -- ุฑุจุงุนูŠ             โ”‚
โ”‚    full_name_en          โ”‚  VARCHAR(200)  (nullable)           โ”‚
โ”‚    id_type               โ”‚  ENUM: national_id, passport, none  โ”‚
โ”‚    id_number             โ”‚  VARCHAR(30)   (encrypted)          โ”‚
โ”‚    id_issue_date         โ”‚  DATE          (nullable)           โ”‚
โ”‚    id_expiry_date        โ”‚  DATE          (nullable)           โ”‚
โ”‚    marital_status        โ”‚  ENUM: single, married, divorced,   โ”‚
โ”‚                          โ”‚  widowed                            โ”‚
โ”‚    gender                โ”‚  ENUM: male, female                 โ”‚
โ”‚    occupation            โ”‚  VARCHAR(100)                       โ”‚
โ”‚    birth_place           โ”‚  VARCHAR(100)                       โ”‚
โ”‚    birth_date            โ”‚  DATE                               โ”‚
โ”‚ FK nationality_id        โ”‚  โ†’ nationalities.id                 โ”‚
โ”‚    mobile                โ”‚  VARCHAR(20)   (encrypted)          โ”‚
โ”‚    mobile_hash           โ”‚  VARCHAR(64)   -- ู„ู„ุจุญุซ ุจุฏูˆู† ููƒ    โ”‚
โ”‚    email                 โ”‚  VARCHAR(150)                       โ”‚
โ”‚    has_yemeni_passport   โ”‚  BOOLEAN DEFAULT false              โ”‚
โ”‚    created_at            โ”‚  TIMESTAMPTZ                        โ”‚
โ”‚    updated_at            โ”‚  TIMESTAMPTZ                        โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
           โ”‚
           โ”œโ”€โ”€โ”€ 1:N โ”€โ”€โ–บโ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
           โ”‚           โ”‚        addresses                    โ”‚
           โ”‚           โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
           โ”‚           โ”‚ PK id                               โ”‚
           โ”‚           โ”‚ FK applicant_id                     โ”‚
           โ”‚           โ”‚    type  ENUM: residence, yemen_    โ”‚
           โ”‚           โ”‚           origin                    โ”‚
           โ”‚           โ”‚    region_id (ู„ู„ุณุนูˆุฏูŠุฉ ูู‚ุท)         โ”‚
           โ”‚           โ”‚    governorate_id (ู„ู„ูŠู…ู† ูู‚ุท)       โ”‚
           โ”‚           โ”‚    district_id (ู„ู„ูŠู…ู† ูู‚ุท)          โ”‚
           โ”‚           โ”‚    village (ู„ู„ูŠู…ู† ูู‚ุท)              โ”‚
           โ”‚           โ”‚    street_address                   โ”‚
           โ”‚           โ”‚    is_primary                       โ”‚
           โ”‚           โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
           โ”‚
           โ”œโ”€โ”€โ”€ 1:N โ”€โ”€โ–บโ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
           โ”‚           โ”‚        attachments                  โ”‚
           โ”‚           โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
           โ”‚           โ”‚ PK id                               โ”‚
           โ”‚           โ”‚ FK applicant_id                     โ”‚
           โ”‚           โ”‚    type  ENUM: personal_photo,      โ”‚
           โ”‚           โ”‚           passport, national_id,     โ”‚
           โ”‚           โ”‚           birth_cert, family_card,  โ”‚
           โ”‚           โ”‚           other_1, other_2          โ”‚
           โ”‚           โ”‚    file_name                        โ”‚
           โ”‚           โ”‚    file_path (S3 key)               โ”‚
           โ”‚           โ”‚    file_size                        โ”‚
           โ”‚           โ”‚    mime_type                        โ”‚
           โ”‚           โ”‚    checksum_sha256                  โ”‚
           โ”‚           โ”‚    uploaded_at                      โ”‚
           โ”‚           โ”‚    is_verified                      โ”‚
           โ”‚           โ”‚ FK verified_by                     โ”‚
           โ”‚           โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
           โ”‚
           โ””โ”€โ”€โ”€ 1:N โ”€โ”€โ–บโ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
                       โ”‚     workflow_history                โ”‚
                       โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
                       โ”‚ PK id                               โ”‚
                       โ”‚ FK application_id                   โ”‚
                       โ”‚ FK actor_id (users)                 โ”‚
                       โ”‚    action                           โ”‚
                       โ”‚    from_status                      โ”‚
                       โ”‚    to_status                        โ”‚
                       โ”‚    comment                          โ”‚
                       โ”‚    created_at                       โ”‚
                       โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                      issued_documents                        โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ PK id                    โ”‚  BIGSERIAL                          โ”‚
โ”‚ FK application_id        โ”‚  โ†’ applications.id                  โ”‚
โ”‚ UK document_number       โ”‚  VARCHAR(30)  -- DOC-2026-XXXXXX    โ”‚
โ”‚    document_type         โ”‚  ENUM: temporary_id_card, full      โ”‚
โ”‚    verification_token    โ”‚  UUID (ู„ู„ู€ QR)                      โ”‚
โ”‚    qr_payload_hash       โ”‚  VARCHAR(64)                        โ”‚
โ”‚    pdf_s3_key            โ”‚  VARCHAR(500)                       โ”‚
โ”‚    issued_at             โ”‚  TIMESTAMPTZ                        โ”‚
โ”‚    expires_at            โ”‚  TIMESTAMPTZ                        โ”‚
โ”‚    printed_by            โ”‚  FK users.id (nullable)             โ”‚
โ”‚    revoked_at            โ”‚  TIMESTAMPTZ (nullable)             โ”‚
โ”‚    revocation_reason     โ”‚  TEXT                               โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                      audit_logs                              โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ PK id                    โ”‚  BIGSERIAL                          โ”‚
โ”‚    actor_type            โ”‚  ENUM: user, applicant, system      โ”‚
โ”‚    actor_id              โ”‚  BIGINT (nullable)                  โ”‚
โ”‚    action                โ”‚  VARCHAR(100)                       โ”‚
โ”‚    entity_type           โ”‚  VARCHAR(50)                        โ”‚
โ”‚    entity_id             โ”‚  BIGINT                             โ”‚
โ”‚    old_values            โ”‚  JSONB                              โ”‚
โ”‚    new_values            โ”‚  JSONB                              โ”‚
โ”‚    ip_address            โ”‚  INET                               โ”‚
โ”‚    user_agent            โ”‚  TEXT                               โ”‚
โ”‚    request_id            โ”‚  UUID                               โ”‚
โ”‚    created_at            โ”‚  TIMESTAMPTZ                        โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

3๏ธโƒฃ ุงู„ุฌุฏุงูˆู„ ุงู„ุชูุตูŠู„ูŠุฉ (DDL)

๐Ÿ”น ู…ุฌู…ูˆุนุฉ 1: ุงู„ู‡ูˆูŠุฉ ูˆุงู„ุตู„ุงุญูŠุงุช

roles

sql

CREATE TABLE roles (
    id              SERIAL PRIMARY KEY,
    name            VARCHAR(50) NOT NULL UNIQUE,  -- admin, reviewer, supervisor, viewer
    name_ar         VARCHAR(100) NOT NULL,
    description     TEXT,
    permissions     JSONB NOT NULL DEFAULT '[]',
    created_at      TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

-- Seed
INSERT INTO roles (name, name_ar, permissions) VALUES
('super_admin',  'ู…ุฏูŠุฑ ุนุงู…',      '["*"]'),
('reviewer',     'ู…ุฑุงุฌุน',         '["applications:read","applications:review"]'),
('supervisor',   'ู…ุดุฑู',          '["applications:*","reports:read"]'),
('viewer',       'ู…ุดุงู‡ุฏ',         '["applications:read"]');

users

sql

CREATE TABLE users (
    id              BIGSERIAL PRIMARY KEY,
    username        VARCHAR(50) NOT NULL UNIQUE,
    email           VARCHAR(150) NOT NULL UNIQUE,
    mobile          VARCHAR(20),
    password_hash   VARCHAR(255) NOT NULL,       -- bcrypt/argon2
    full_name       VARCHAR(200) NOT NULL,
    national_id     VARCHAR(30),                 -- ู„ู„ู…ูˆุธู ุงู„ุณุนูˆุฏูŠ/ุงู„ูŠู…ู†ูŠ
    role_id         INTEGER NOT NULL REFERENCES roles(id),
    is_active       BOOLEAN NOT NULL DEFAULT TRUE,
    is_locked       BOOLEAN NOT NULL DEFAULT FALSE,
    failed_attempts SMALLINT NOT NULL DEFAULT 0,
    mfa_enabled     BOOLEAN NOT NULL DEFAULT TRUE,
    mfa_secret      VARCHAR(255),
    last_login_at   TIMESTAMPTZ,
    last_login_ip   INET,
    password_changed_at TIMESTAMPTZ,
    created_at      TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at      TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    deleted_at      TIMESTAMPTZ
);

CREATE INDEX idx_users_email ON users(email) WHERE deleted_at IS NULL;
CREATE INDEX idx_users_role ON users(role_id) WHERE is_active = TRUE;

sessions

sql

CREATE TABLE sessions (
    id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    user_id         BIGINT REFERENCES users(id) ON DELETE CASCADE,
    applicant_id    BIGINT,  -- nullableุŒ ู„ู„ู…ุณุชููŠุฏ
    refresh_token_hash VARCHAR(255) NOT NULL,
    ip_address      INET,
    user_agent      TEXT,
    expires_at      TIMESTAMPTZ NOT NULL,
    revoked_at      TIMESTAMPTZ,
    created_at      TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE INDEX idx_sessions_user ON sessions(user_id) WHERE revoked_at IS NULL;
CREATE INDEX idx_sessions_expires ON sessions(expires_at);

otp_verifications (ู„ู„ุชุญู‚ู‚ ู…ู† ุงู„ุฌูˆุงู„/ุงู„ุจุฑูŠุฏ)

sql

CREATE TABLE otp_verifications (
    id              BIGSERIAL PRIMARY KEY,
    identifier      VARCHAR(150) NOT NULL,       -- mobile ุฃูˆ email
    identifier_type ENUM('mobile','email') NOT NULL,
    otp_hash        VARCHAR(255) NOT NULL,
    purpose         VARCHAR(50) NOT NULL,        -- login, verify_mobile, verify_email
    attempts        SMALLINT NOT NULL DEFAULT 0,
    max_attempts    SMALLINT NOT NULL DEFAULT 5,
    expires_at      TIMESTAMPTZ NOT NULL,
    consumed_at     TIMESTAMPTZ,
    created_at      TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE INDEX idx_otp_identifier ON otp_verifications(identifier, purpose) 
    WHERE consumed_at IS NULL;

๐Ÿ”น ู…ุฌู…ูˆุนุฉ 2: ุงู„ู…ุณุชููŠุฏูˆู†

nationalities

sql

CREATE TABLE nationalities (
    id          SERIAL PRIMARY KEY,
    code        CHAR(2) NOT NULL UNIQUE,   -- ISO 3166-1 alpha-2
    name_ar     VARCHAR(100) NOT NULL,
    name_en     VARCHAR(100) NOT NULL,
    is_active   BOOLEAN DEFAULT TRUE
);

applicants

sql

CREATE TABLE applicants (
    id                  BIGSERIAL PRIMARY KEY,
    full_name_ar        VARCHAR(200) NOT NULL,
    full_name_en        VARCHAR(200),
    id_type             VARCHAR(20) NOT NULL 
                        CHECK (id_type IN ('national_id','passport','none')),
    id_number_encrypted BYTEA,                    -- pgcrypto
    id_number_hash      VARCHAR(64),              -- SHA-256 ู„ู„ุจุญุซ
    id_issue_date       DATE,
    id_expiry_date      DATE,
    marital_status      VARCHAR(20) NOT NULL 
                        CHECK (marital_status IN ('single','married','divorced','widowed')),
    gender              VARCHAR(10) NOT NULL 
                        CHECK (gender IN ('male','female')),
    occupation          VARCHAR(100) NOT NULL,
    birth_place         VARCHAR(100) NOT NULL,
    birth_date          DATE NOT NULL,
    nationality_id      INTEGER REFERENCES nationalities(id),
    mobile_encrypted    BYTEA,                    -- pgcrypto
    mobile_hash         VARCHAR(64) NOT NULL,     -- SHA-256
    email               VARCHAR(150) NOT NULL,
    has_yemeni_passport BOOLEAN NOT NULL DEFAULT FALSE,
    -- ุงู„ุชุญู‚ู‚
    is_identity_verified BOOLEAN DEFAULT FALSE,
    verified_at         TIMESTAMPTZ,
    verified_by         BIGINT REFERENCES users(id),
    -- timestamps
    created_at          TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at          TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    deleted_at          TIMESTAMPTZ,
    
    CONSTRAINT chk_name_quad CHECK (
        array_length(regexp_split_to_array(trim(full_name_ar), '\s+'), 1) >= 4
    ),
    CONSTRAINT chk_id_required CHECK (
        (id_type = 'none' AND id_number_encrypted IS NULL)
        OR (id_type IN ('national_id','passport') AND id_number_encrypted IS NOT NULL)
    )
);

CREATE INDEX idx_applicants_mobile_hash ON applicants(mobile_hash);
CREATE INDEX idx_applicants_id_hash ON applicants(id_number_hash);
CREATE INDEX idx_applicants_email ON applicants(email);
CREATE UNIQUE INDEX uk_applicants_id_hash ON applicants(id_number_hash) 
    WHERE id_number_hash IS NOT NULL AND deleted_at IS NULL;
CREATE INDEX idx_applicants_created ON applicants(created_at DESC);

ู…ู„ุงุญุธุฉ ุฃู…ู†ูŠุฉ: ู†ุณุชุฎุฏู… pgcrypto ู„ุชุดููŠุฑ id_number ูˆ mobileุŒ ู…ุน ุงู„ุงุญุชูุงุธ ุจู€ hash (SHA-256 + salt) ู„ู„ุจุญุซ ุงู„ุณุฑูŠุน ุฏูˆู† ููƒ ุงู„ุชุดููŠุฑ.

addresses

sql

CREATE TABLE addresses (
    id              BIGSERIAL PRIMARY KEY,
    applicant_id    BIGINT NOT NULL REFERENCES applicants(id) ON DELETE CASCADE,
    type            VARCHAR(20) NOT NULL CHECK (type IN ('residence','yemen_origin')),
    -- ู„ู„ุณุนูˆุฏูŠุฉ
    region_id       INTEGER REFERENCES regions(id),
    -- ู„ู„ูŠู…ู†
    governorate_id  INTEGER REFERENCES governorates(id),
    district_id     INTEGER REFERENCES districts(id),
    village         VARCHAR(150),
    street_address  VARCHAR(300),
    postal_code     VARCHAR(20),
    is_primary      BOOLEAN DEFAULT TRUE,
    created_at      TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE INDEX idx_addresses_applicant ON addresses(applicant_id);
CREATE UNIQUE INDEX uk_address_applicant_type ON addresses(applicant_id, type);

๐Ÿ”น ู…ุฌู…ูˆุนุฉ 3: ุงู„ุจูŠุงู†ุงุช ุงู„ุฌุบุฑุงููŠุฉ

regions (ู…ู†ุงุทู‚ ุงู„ุณุนูˆุฏูŠุฉ)

sql

CREATE TABLE regions (
    id          SERIAL PRIMARY KEY,
    code        VARCHAR(10) NOT NULL UNIQUE,
    name_ar     VARCHAR(100) NOT NULL,
    name_en     VARCHAR(100) NOT NULL,
    is_active   BOOLEAN DEFAULT TRUE
);

-- Seed: 13 ู…ู†ุทู‚ุฉ
INSERT INTO regions (code, name_ar, name_en) VALUES
('RUH','ุงู„ุฑูŠุงุถ','Riyadh'),
('MAK','ู…ูƒุฉ ุงู„ู…ูƒุฑู…ุฉ','Makkah'),
('MED','ุงู„ู…ุฏูŠู†ุฉ ุงู„ู…ู†ูˆุฑุฉ','Madinah'),
('EAP','ุงู„ู…ู†ุทู‚ุฉ ุงู„ุดุฑู‚ูŠุฉ','Eastern Province'),
('ASR','ุนุณูŠุฑ','Asir'),
('TAB','ุชุจูˆูƒ','Tabuk'),
('HAI','ุญุงุฆู„','Hail'),
('NOR','ุงู„ุญุฏูˆุฏ ุงู„ุดู…ุงู„ูŠุฉ','Northern Borders'),
('JAZ','ุฌุงุฒุงู†','Jazan'),
('NAJ','ู†ุฌุฑุงู†','Najran'),
('BAH','ุงู„ุจุงุญุฉ','Al-Bahah'),
('JAW','ุงู„ุฌูˆู','Al-Jouf'),
('QAS','ุงู„ู‚ุตูŠู…','Qassim');

governorates (ู…ุญุงูุธุงุช ุงู„ูŠู…ู†)

sql

CREATE TABLE governorates (
    id          SERIAL PRIMARY KEY,
    code        VARCHAR(10) NOT NULL UNIQUE,
    name_ar     VARCHAR(100) NOT NULL,
    name_en     VARCHAR(100),
    is_active   BOOLEAN DEFAULT TRUE
);

-- Seed: 22 ู…ุญุงูุธุฉ ูŠู…ู†ูŠุฉ
INSERT INTO governorates (code, name_ar) VALUES
('SAN','ุตู†ุนุงุก'),('ADE','ุนุฏู†'),('TAI','ุชุนุฒ'),('HAD','ุงู„ุญุฏูŠุฏุฉ'),
('HAD2','ุญุถุฑู…ูˆุช'),('IBB','ุฅุจ'),('LAH','ู„ุญุฌ'),('ABY','ุฃุจูŠู†'),
('DHM','ุฐู…ุงุฑ'),('BAY','ุงู„ุจูŠุถุงุก'),('MAA','ู…ุฃุฑุจ'),('SAA','ุตุนุฏุฉ'),
('HAJ','ุญุฌุฉ'),('AMR','ุนู…ุฑุงู†'),('DAL','ุงู„ุถุงู„ุน'),('JOF','ุงู„ุฌูˆู'),
('MAH','ุงู„ู…ุญูˆูŠุช'),('RAY','ุฑูŠู…ุฉ'),('SHA','ุดุจูˆุฉ'),('SOC','ุฃุฑุฎุจูŠู„ ุณู‚ุทุฑู‰'),
('MWS','ุงู„ู…ู‡ุฑุฉ'),('HOD','ุญุฌุฉ');

districts (ุงู„ู…ุฏูŠุฑูŠุงุช – ~333 ู…ุฏูŠุฑูŠุฉ)

sql

CREATE TABLE districts (
    id              SERIAL PRIMARY KEY,
    governorate_id  INTEGER NOT NULL REFERENCES governorates(id),
    code            VARCHAR(15) NOT NULL UNIQUE,
    name_ar         VARCHAR(100) NOT NULL,
    name_en         VARCHAR(100),
    is_active       BOOLEAN DEFAULT TRUE
);

CREATE INDEX idx_districts_gov ON districts(governorate_id);

๐Ÿ”น ู…ุฌู…ูˆุนุฉ 4: ุงู„ู…ุฑูู‚ุงุช

attachments

sql

CREATE TABLE attachments (
    id              BIGSERIAL PRIMARY KEY,
    applicant_id    BIGINT NOT NULL REFERENCES applicants(id) ON DELETE CASCADE,
    type            VARCHAR(30) NOT NULL CHECK (type IN (
                        'personal_photo','passport','national_id',
                        'birth_cert','family_card','other_1','other_2'
                    )),
    original_name   VARCHAR(255) NOT NULL,
    file_path       VARCHAR(500) NOT NULL,      -- S3 key: applicants/{id}/{uuid}.jpg
    file_size       BIGINT NOT NULL,            -- bytes
    mime_type       VARCHAR(100) NOT NULL,
    checksum_sha256 VARCHAR(64) NOT NULL,
    width_px        INTEGER,                    -- ู„ู„ุตูˆุฑ
    height_px       INTEGER,                    -- ู„ู„ุตูˆุฑ
    is_verified     BOOLEAN DEFAULT FALSE,
    verified_by     BIGINT REFERENCES users(id),
    verified_at     TIMESTAMPTZ,
    uploaded_at     TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    deleted_at      TIMESTAMPTZ,
    
    CONSTRAINT chk_file_size CHECK (file_size <= 5 * 1024 * 1024),  -- 5 MB
    CONSTRAINT chk_mime CHECK (mime_type IN (
        'image/jpeg','image/png','application/pdf'
    ))
);

CREATE INDEX idx_attachments_applicant ON attachments(applicant_id) 
    WHERE deleted_at IS NULL;
CREATE UNIQUE INDEX uk_attachment_type ON attachments(applicant_id, type) 
    WHERE deleted_at IS NULL;

ู…ู„ุงุญุธุฉ: ุงู„ุตูˆุฑุฉ ุงู„ุดุฎุตูŠุฉ (personal_photo) ุฅุฌุจุงุฑูŠุฉุŒ ูˆุงู„ุจุงู‚ูŠ ุงุฎุชูŠุงุฑูŠ โ€” ูŠููุฑุถ ุฐู„ูƒ ููŠ ุทุจู‚ุฉ ุงู„ุชุทุจูŠู‚ุŒ ูˆู„ูŠุณ ููŠ ู‚ุงุนุฏุฉ ุงู„ุจูŠุงู†ุงุชุŒ ู„ู…ุฑูˆู†ุฉ ุฃูƒุจุฑ.


๐Ÿ”น ู…ุฌู…ูˆุนุฉ 5: ุงู„ุทู„ุจุงุช

applications

sql

CREATE TABLE applications (
    id                    BIGSERIAL PRIMARY KEY,
    reference_number      VARCHAR(20) NOT NULL UNIQUE,   -- YEM-2026-000123
    applicant_id          BIGINT NOT NULL REFERENCES applicants(id),
    status                VARCHAR(20) NOT NULL DEFAULT 'draft' CHECK (status IN (
                            'draft','submitted','under_review',
                            'approved','rejected','printed','cancelled'
                          )),
    current_step          SMALLINT NOT NULL DEFAULT 1,
    assigned_reviewer_id  BIGINT REFERENCES users(id),
    priority              SMALLINT NOT NULL DEFAULT 0,   -- 0=normal, 1=high
    submitted_at          TIMESTAMPTZ,
    review_started_at     TIMESTAMPTZ,
    decided_at            TIMESTAMPTZ,
    rejection_reason      TEXT,
    internal_notes        TEXT,
    -- ุงู„ุทุจุงุนุฉ
    printed_count         SMALLINT NOT NULL DEFAULT 0,
    last_printed_at       TIMESTAMPTZ,
    last_printed_by       BIGINT REFERENCES users(id),
    -- timestamps
    created_at            TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at            TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    deleted_at            TIMESTAMPTZ
);

CREATE INDEX idx_applications_status ON applications(status) WHERE deleted_at IS NULL;
CREATE INDEX idx_applications_reviewer ON applications(assigned_reviewer_id, status);
CREATE INDEX idx_applications_submitted ON applications(submitted_at DESC);
CREATE INDEX idx_applications_applicant ON applications(applicant_id);

application_sequences (ู„ุชูˆู„ูŠุฏ ุฑู‚ู… ู…ุฑุฌุนูŠ)

sql

CREATE TABLE application_sequences (
    year        SMALLINT PRIMARY KEY,
    last_number BIGINT NOT NULL DEFAULT 0
);

-- Function ู„ุชูˆู„ูŠุฏ ุฑู‚ู… ู…ุฑุฌุนูŠ
CREATE OR REPLACE FUNCTION next_reference_number()
RETURNS VARCHAR AS $$
DECLARE
    v_year SMALLINT := EXTRACT(YEAR FROM NOW());
    v_num  BIGINT;
BEGIN
    INSERT INTO application_sequences (year, last_number)
    VALUES (v_year, 1)
    ON CONFLICT (year) DO UPDATE
        SET last_number = application_sequences.last_number + 1
    RETURNING last_number INTO v_num;
    
    RETURN 'YEM-' || v_year || '-' || LPAD(v_num::TEXT, 6, '0');
END;
$$ LANGUAGE plpgsql;

๐Ÿ”น ู…ุฌู…ูˆุนุฉ 6: ุณูŠุฑ ุงู„ุนู…ู„ (Workflow)

workflow_history

sql

CREATE TABLE workflow_history (
    id              BIGSERIAL PRIMARY KEY,
    application_id  BIGINT NOT NULL REFERENCES applications(id) ON DELETE CASCADE,
    actor_id        BIGINT REFERENCES users(id),
    actor_type      VARCHAR(20) NOT NULL CHECK (actor_type IN ('user','applicant','system')),
    action          VARCHAR(50) NOT NULL,   -- submit, assign, approve, reject, print, etc.
    from_status     VARCHAR(20),
    to_status       VARCHAR(20),
    comment         TEXT,
    metadata        JSONB,                   -- ุจูŠุงู†ุงุช ุฅุถุงููŠุฉ
    created_at      TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE INDEX idx_workflow_app ON workflow_history(application_id, created_at DESC);
CREATE INDEX idx_workflow_actor ON workflow_history(actor_id, created_at DESC);

review_assignments (ุชูˆุฒูŠุน ุงู„ู…ู‡ุงู…)

sql

CREATE TABLE review_assignments (
    id              BIGSERIAL PRIMARY KEY,
    application_id  BIGINT NOT NULL REFERENCES applications(id),
    reviewer_id     BIGINT NOT NULL REFERENCES users(id),
    assigned_by     BIGINT NOT NULL REFERENCES users(id),
    assigned_at     TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    accepted_at     TIMESTAMPTZ,
    completed_at    TIMESTAMPTZ,
    is_active       BOOLEAN DEFAULT TRUE
);

CREATE UNIQUE INDEX uk_active_assignment ON review_assignments(application_id) 
    WHERE is_active = TRUE;

๐Ÿ”น ู…ุฌู…ูˆุนุฉ 7: ุงู„ูˆุซุงุฆู‚ ุงู„ุตุงุฏุฑุฉ

issued_documents

sql

CREATE TABLE issued_documents (
    id                  BIGSERIAL PRIMARY KEY,
    application_id      BIGINT NOT NULL REFERENCES applications(id),
    document_number     VARCHAR(30) NOT NULL UNIQUE,   -- DOC-2026-000123
    document_type       VARCHAR(30) NOT NULL CHECK (document_type IN (
                            'temporary_id_card','full_id_card'
                        )),
    verification_token  UUID NOT NULL UNIQUE DEFAULT gen_random_uuid(),
    qr_payload_hash     VARCHAR(64) NOT NULL,
    pdf_s3_key          VARCHAR(500) NOT NULL,
    issued_at           TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    expires_at          TIMESTAMPTZ,
    issued_by           BIGINT REFERENCES users(id),
    revoked_at          TIMESTAMPTZ,
    revocation_reason   TEXT,
    is_valid            BOOLEAN GENERATED ALWAYS AS (revoked_at IS NULL) STORED
);

CREATE INDEX idx_issued_app ON issued_documents(application_id);
CREATE INDEX idx_issued_token ON issued_documents(verification_token);

print_jobs

sql

CREATE TABLE print_jobs (
    id                  BIGSERIAL PRIMARY KEY,
    application_id      BIGINT NOT NULL REFERENCES applications(id),
    document_id         BIGINT REFERENCES issued_documents(id),
    printed_by          BIGINT NOT NULL REFERENCES users(id),
    printer_ip          INET,
    copies              SMALLINT NOT NULL DEFAULT 1,
    status              VARCHAR(20) NOT NULL CHECK (status IN ('queued','processing','completed','failed')),
    error_message       TEXT,
    started_at          TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    completed_at        TIMESTAMPTZ
);

CREATE INDEX idx_print_jobs_app ON print_jobs(application_id);

๐Ÿ”น ู…ุฌู…ูˆุนุฉ 8: ุงู„ุชุฏู‚ูŠู‚ ูˆุงู„ู…ุฑุฌุนูŠุงุช

audit_logs (Partitioned by month)

sql

CREATE TABLE audit_logs (
    id              BIGSERIAL,
    actor_type      VARCHAR(20) NOT NULL,
    actor_id        BIGINT,
    actor_username  VARCHAR(50),
    action          VARCHAR(100) NOT NULL,
    entity_type     VARCHAR(50) NOT NULL,
    entity_id       BIGINT,
    old_values      JSONB,
    new_values      JSONB,
    ip_address      INET,
    user_agent      TEXT,
    request_id      UUID,
    created_at      TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    PRIMARY KEY (id, created_at)
) PARTITION BY RANGE (created_at);

-- Partitions ุดู‡ุฑูŠุฉ
CREATE TABLE audit_logs_2026_01 PARTITION OF audit_logs
    FOR VALUES FROM ('2026-01-01') TO ('2026-02-01');
-- ... ุฅู„ุฎ

CREATE INDEX idx_audit_entity ON audit_logs(entity_type, entity_id);
CREATE INDEX idx_audit_actor ON audit_logs(actor_id, created_at DESC);
CREATE INDEX idx_audit_action ON audit_logs(action, created_at DESC);

settings

sql

CREATE TABLE settings (
    key         VARCHAR(100) PRIMARY KEY,
    value       JSONB NOT NULL,
    description TEXT,
    updated_by  BIGINT REFERENCES users(id),
    updated_at  TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

-- Seed
INSERT INTO settings (key, value, description) VALUES
('application.max_file_size',    '5242880',      'ุญุฏ ุฃู‚ุตู‰ 5MB'),
('application.allowed_mimes',    '["image/jpeg","image/png","application/pdf"]', ''),
('application.otp_ttl_seconds',  '300',          'ู…ุฏุฉ ุตู„ุงุญูŠุฉ OTP'),
('application.max_otp_attempts', '5',            'ุนุฏุฏ ู…ุญุงูˆู„ุงุช OTP'),
('workflow.auto_assign',         'true',         'ุชูˆุฒูŠุน ุชู„ู‚ุงุฆูŠ ุนู„ู‰ ุงู„ู…ุฑุงุฌุนูŠู†');

notifications

sql

CREATE TABLE notifications (
    id              BIGSERIAL PRIMARY KEY,
    recipient_type  VARCHAR(20) NOT NULL CHECK (recipient_type IN ('user','applicant')),
    recipient_id    BIGINT NOT NULL,
    channel         VARCHAR(20) NOT NULL CHECK (channel IN ('sms','email','push','in_app')),
    template_code   VARCHAR(50) NOT NULL,
    subject         VARCHAR(200),
    body            TEXT NOT NULL,
    metadata        JSONB,
    status          VARCHAR(20) NOT NULL DEFAULT 'pending' 
                    CHECK (status IN ('pending','sent','failed','delivered','read')),
    sent_at         TIMESTAMPTZ,
    delivered_at    TIMESTAMPTZ,
    read_at         TIMESTAMPTZ,
    error_message   TEXT,
    retry_count     SMALLINT DEFAULT 0,
    created_at      TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE INDEX idx_notif_recipient ON notifications(recipient_type, recipient_id, created_at DESC);
CREATE INDEX idx_notif_status ON notifications(status) WHERE status IN ('pending','failed');

4๏ธโƒฃ ุงู„ุนู„ุงู‚ุงุช (Relationships Summary)

ุงู„ุนู„ุงู‚ุฉุงู„ู†ูˆุนุงู„ูˆุตู
users โ†’ rolesM:1ูƒู„ ู…ุณุชุฎุฏู… ู„ู‡ ุฏูˆุฑ ูˆุงุญุฏ
users โ†’ applications1:Nุงู„ู…ุฑุงุฌุน ู…ุณุคูˆู„ ุนู† ุนุฏุฉ ุทู„ุจุงุช
applicants โ†’ applications1:1ูƒู„ ู…ุณุชููŠุฏ ู„ู‡ ุทู„ุจ ูˆุงุญุฏ ู†ุดุท
applicants โ†’ addresses1:Nุนู†ูˆุงู† ุงู„ุฅู‚ุงู…ุฉ + ุนู†ูˆุงู† ุงู„ูŠู…ู†
applicants โ†’ attachments1:N1 ุดุฎุตูŠุฉ + 6 ุงุฎุชูŠุงุฑูŠุฉ
applications โ†’ workflow_history1:Nุณุฌู„ ูƒุงู…ู„ ู„ู„ุชุบูŠูŠุฑุงุช
applications โ†’ issued_documents1:Nุจุทุงู‚ุงุช ุตุงุฏุฑุฉ
applications โ†’ print_jobs1:Nุนู…ู„ูŠุงุช ุงู„ุทุจุงุนุฉ
regions/governorates โ†’ districts1:Nู‡ุฑู…ูŠุฉ ุฌุบุฑุงููŠุฉ
issued_documents โ†’ verification1:1ุฑู…ุฒ QR ู„ู„ุชุญู‚ู‚

5๏ธโƒฃ ุงู„ูู‡ุงุฑุณ ุงู„ุญุฑุฌุฉ (Critical Indexes)

sql

-- ุจุญุซ ุณุฑูŠุน ุนู† ุทู„ุจ ุจุฑู‚ู… ู…ุฑุฌุนูŠ (ุดุงุฆุน)
CREATE UNIQUE INDEX CONCURRENTLY uk_app_reference ON applications(reference_number);

-- ู‚ุงุฆู…ุฉ ุงู†ุชุธุงุฑ ุงู„ู…ุฑุงุฌุนุฉ (dashboard)
CREATE INDEX CONCURRENTLY idx_review_queue ON applications(status, priority DESC, submitted_at ASC)
    WHERE status IN ('submitted','under_review');

-- ุจุญุซ ุงู„ู…ุณุชููŠุฏ (ุดุงุฆุน ุฌุฏุงู‹)
CREATE INDEX CONCURRENTLY idx_applicant_search ON applicants 
    USING GIN (to_tsvector('arabic', full_name_ar));
CREATE INDEX CONCURRENTLY idx_applicant_mobile ON applicants(mobile_hash);

-- ุชุญู‚ู‚ ู…ู† QR
CREATE INDEX CONCURRENTLY idx_verify_token ON issued_documents(verification_token) 
    WHERE revoked_at IS NULL;

6๏ธโƒฃ ุงู„ุนุฑุถ ุงู„ู…ุงุฏูŠ (Materialized Views) ู„ู„ุชู‚ุงุฑูŠุฑ

sql

-- ู„ูˆุญุฉ KPI ุงู„ูŠูˆู…ูŠุฉ
CREATE MATERIALIZED VIEW mv_daily_stats AS
SELECT
    DATE(created_at) AS day,
    COUNT(*) FILTER (WHERE status = 'submitted')     AS submitted,
    COUNT(*) FILTER (WHERE status = 'approved')      AS approved,
    COUNT(*) FILTER (WHERE status = 'rejected')      AS rejected,
    COUNT(*) FILTER (WHERE status = 'printed')       AS printed,
    AVG(EXTRACT(EPOCH FROM (decided_at - submitted_at))/3600)::NUMERIC(10,2) AS avg_review_hours
FROM applications
WHERE deleted_at IS NULL
GROUP BY DATE(created_at);

CREATE UNIQUE INDEX ON mv_daily_stats(day);

-- ุชุญุฏูŠุซ ูƒู„ ุณุงุนุฉ
-- REFRESH MATERIALIZED VIEW CONCURRENTLY mv_daily_stats;

7๏ธโƒฃ ุณูŠุงุณุงุช ุงู„ุงุญุชูุงุธ ุจุงู„ุจูŠุงู†ุงุช (Retention)

ุงู„ุฌุฏูˆู„ุงู„ู…ุฏุฉุงู„ุฅุฌุฑุงุก
applications10 ุณู†ูˆุงุชุฃุฑุดูุฉ (cold storage)
audit_logs7 ุณู†ูˆุงุชุชู‚ุณูŠู… + S3 Glacier
attachments5 ุณู†ูˆุงุช ุจุนุฏ ุงู„ุฑูุถุญุฐู ุชู„ู‚ุงุฆูŠ
issued_documents10 ุณู†ูˆุงุชุฃุฑุดูุฉ
notifications2 ุณู†ูˆุงุชุญุฐู
sessions90 ูŠูˆู…ุญุฐู
otp_verifications30 ูŠูˆู…ุญุฐู

sql

-- ู…ุซุงู„: ุฏุงู„ุฉ ุญุฐู OTP ู…ู†ุชู‡ูŠุฉ
CREATE OR REPLACE FUNCTION cleanup_expired_otps()
RETURNS void AS $$
BEGIN
    DELETE FROM otp_verifications
    WHERE created_at < NOW() - INTERVAL '30 days';
END;
$$ LANGUAGE plpgsql;

8๏ธโƒฃ ุงู„ุฃู…ุงู† ุนู„ู‰ ู…ุณุชูˆู‰ ุงู„ุตููˆู (RLS)

sql

-- ุงู„ู…ุฑุงุฌุนูˆู† ูŠุฑูˆู† ูู‚ุท ุงู„ุทู„ุจุงุช ุงู„ู…ุณู†ุฏุฉ ุฅู„ูŠู‡ู…
ALTER TABLE applications ENABLE ROW LEVEL SECURITY;

CREATE POLICY reviewer_sees_assigned ON applications
    FOR SELECT
    TO reviewer_role
    USING (assigned_reviewer_id = current_setting('app.current_user_id')::BIGINT
           OR status = 'submitted');

-- ุงู„ู…ุดุฑููˆู† ูŠุฑูˆู† ุงู„ูƒู„
CREATE POLICY supervisor_sees_all ON applications
    FOR ALL
    TO supervisor_role
    USING (TRUE);

9๏ธโƒฃ ุงุนุชุจุงุฑุงุช ุงู„ุฃุฏุงุก ูˆุงู„ุชูˆุณุน

ุงู„ุชู‚ุณูŠู… (Partitioning)

  • audit_logs โ†’ ุดู‡ุฑูŠ (PostgreSQL native).
  • notifications โ†’ ุดู‡ุฑูŠ.
  • applications โ†’ ุณู†ูˆูŠ (ุนู†ุฏ ุชุฌุงูˆุฒ 5M ุตู).

ุงู„ู†ุณุฎ ุงู„ู…ุชู…ุงุซู„ (Replication)

  • Primary (ูƒุชุงุจุฉ) + 2 Read Replicas ู„ู„ุชู‚ุงุฑูŠุฑ.
  • Streaming Replication ู…ุน synchronous_commit = remote_apply ู„ู„ุจูŠุงู†ุงุช ุงู„ุญุณุงุณุฉ.

ุงู„ู†ุณุฎ ุงู„ุงุญุชูŠุงุทูŠ

  • WAL Archiving ู…ุณุชู…ุฑ โ†’ S3.
  • Full Backup ูŠูˆู…ูŠ (pg_basebackup).
  • PITR ู„ู…ุฏุฉ 30 ูŠูˆู….
  • ุงุฎุชุจุงุฑ ุงุณุชุนุงุฏุฉ ุฑุจุน ุณู†ูˆูŠ.

ุงู„ุงุชุตุงู„ุงุช

  • PgBouncer ููŠ ูˆุถุน transaction pooling.
  • max_connections = 500 (ู…ุน PgBouncer).

๐Ÿ”Ÿ ู…ุฎุทุท ERD ุจุตูŠุบุฉ Mermaid (ู„ู„ุงุณุชุฎุฏุงู… ููŠ ุงู„ู…ุณุชู†ุฏุงุช)


1๏ธโƒฃ1๏ธโƒฃ ุงู„ุฎุทูˆุงุช ุงู„ุชุงู„ูŠุฉ

ู‡ู„ ุชุฑูŠุฏ ุฃู† ุฃุชุนู…ู‚ ููŠ:

  • ๐Ÿ”Œ ู…ูˆุงุตูุงุช OpenAPI 3.1 ูƒุงู…ู„ุฉ ู„ูƒู„ endpoint ู…ุฑุชุจุท ุจู‡ุฐุง ุงู„ู…ุฎุทุทุŸ
  • ๐Ÿ” ุฃู…ุซู„ุฉ ุนู…ู„ูŠุฉ ู„ุชุดููŠุฑ pgcrypto (ุชุดููŠุฑ/ููƒ ุชุดููŠุฑ/ุจุญุซ)ุŸ
  • ๐Ÿ“Š ุงุณุชุนู„ุงู…ุงุช SQL ุฌุงู‡ุฒุฉ ู„ู„ุชู‚ุงุฑูŠุฑ ูˆู„ูˆุญุงุช ุงู„ุชุญูƒู…ุŸ
  • ๐Ÿš€ ุณูƒุฑูŠุจุชุงุช Migration (Flyway/Prisma)ุŸ
  • ๐Ÿงช ุจูŠุงู†ุงุช Seed ูˆุงู‚ุนูŠุฉ (ู…ุญุงูุธุงุชุŒ ู…ุฏูŠุฑูŠุงุชุŒ ุฃุฏูˆุงุฑ)ุŸ
  • ๐Ÿ“„ ู‚ุงู„ุจ PDF ุญุฒู…ุฉ ุงู„ุทุจุงุนุฉ (HTML/CSS โ†’ PDF)ุŸ
  • ๐Ÿ”„ ุขู„ูŠุฉ Workflow Engine (State Machine)ุŸ

ุงุฎุชุฑ ุงู„ุฃูˆู„ูˆูŠุฉ ูˆุณุฃุนู…ู‘ู‚ ููŠู‡ุง. ๐Ÿš€

https://registeryem.somee.com/registerYemen/Register.aspx

ุงุชุฑูƒ ุชุนู„ูŠู‚ุงู‹

Scroll to Top

๐Ÿค– ู…ูˆุตู‰ ุจู‡ ู„ูƒ ุจู†ุงุกู‹ ุนู„ู‰ ุงู‡ุชู…ุงู…ุงุชูƒ