البنية¶
تتّبع المنصّة البنية النظيفة: handler ← usecase ← repository ← domain. ولا
تستورد نواة الأعمال إطارَ الويب إطلاقًا.
المكوّنات¶
الإنترنت ──► nginx (TLS)
│
├─ /oauth2/*, /.well-known/*, /userinfo ─► واجهة Go — مُصدِر OIDC مدمج
├─ /rp/sign/* ─► مرحِّل توقيع eID (الواجهة الخلفية)
├─ /rp/eid/* ─► وكيل خدمة eID — شخصي (الواجهة الخلفية)
├─ /rp/eid-org/* ─► وكيل خدمة eID — المنظمات (الواجهة الخلفية)
└─ كلّ ما عدا ذلك ─► BFF بـ Next.js (web) ──► واجهة الخلفية (:8080)
│
الشبكة الداخلية: db (PostgreSQL) · redis
الطبقات¶
| الطبقة | التقنية | ملاحظات |
|---|---|---|
| الواجهة الخلفية | Go · chi (net/http) · pgx (دون ORM) | بنية نظيفة، RLS، SQL مكتوب يدويًّا |
| الواجهة الأمامية | Next.js 16 (BFF) | يخاطب المتصفّح مسارات المصدر نفسه فقط؛ ولا تصل الرموز إلى شيفرة العميل |
| مزوّد OIDC | مدمج (Go، usecases/oidc) | تدير المنصّة بنفسها الدخول والموافقة والخروج |
| الهوية | طرف معتمِد لدى eID Mongolia | التحقّق بالهوية الإلكترونية |
| الذاكرة/الطابور | Redis | قائمة رفض الجلسات، الحالة المؤقّتة |
| الذكاء الاصطناعي | Gemini (REST دون SDK) | محادثة، صوت، ترجمة |
الأمان¶
- الأمان على مستوى الصفّ (RLS) — لا يرى المستخدم سوى صفوفه؛ مع حارس عند الإقلاع يتحقّق من قابلية التطبيق (يستلزم في الإنتاج دورًا غير خارق الصلاحيات).
- نمط BFF — تعيش الرموز في كعكات httpOnly، ولا تظهر أبدًا في شيفرة المتصفّح.
- حماية CSRF مزدوجة — ترويسة مخصّصة + تحقّق من المصدر.
- ترويسات الأمان — CSP، وHSTS، وCOOP/COEP/CORP؛ وتحديد المعدّل حسب IP.
- التدقيق — سجلّ مترابط بالبصمات، لا يقبل إلّا الإضافة.
الشيفرة المشتركة — ما ليس في هذا المستودع¶
لم تُكتب أيّ من القدرات أعلاه هنا. فهي تصل من طبقتين مشتركتين مُصدَّرتين بإصدارات، حتى لا تضطرّ أيّ منصّة إلى تكرار الإصلاح يدويًّا:
| الطبقة | المصدر | الآلية |
|---|---|---|
| نواة الواجهة الخلفية — المصادقة، وRBAC، والبوّابة، والتدقيق، ومزوّد OIDC، وeID/SSO، والذكاء الاصطناعي | open-gerege-core (وحدة Go) |
تبعية في go.mod |
طبقة الواجهة الأمامية — lib/**، وcomponents/**، ومنطق 158 مسار BFF |
@gerege/ui-core (حزمة npm) |
تبعية في package.json |
ولذلك لا يحتوي مجلّد backend/ في هذا المستودع سوى على ملفّ Go واحد:
backend/
├── cmd/api/main.go # نحو 30 سطرًا: تشغيل النواة وإضافة مساراتك
├── deploy/ # Dockerfile، تهيئة قاعدة البيانات
└── .env.example # قالب الإعدادات
func main() {
server.ServiceName = "gerege-template"
app, err := server.NewApp() // ← كلّ القدرات من النواة
// أضِف هنا مسارات هذا التطبيق الخاصّة:
// app.Router().Route("/api/xxx", xxx.Routes(app.Pool()))
app.Run()
}
والواجهة الأمامية على النمط ذاته — إذ إنّ app/**/page.tsx أغلفة رفيعة حول
عروض الحزمة، وapp/api/** إعادة تصدير من سطر واحد لكلّ مسار:
// src/app/api/org/[id]/route.ts
export { GET, PUT, DELETE } from '@gerege/ui-core/api/org/[id]';
export const dynamic = 'force-dynamic';
لماذا تبقى أغلفة المسارات
يمكن اختزال الملفّات الـ158 في مسار شامل واحد [...path]. لكنّ قائمة
المسارات قائمة سماح أمنية — فهي تحدّد أيّ مسارات الواجهة الخلفية يمكن
للمتصفّح بلوغها. والمسار الشامل يُلغيها ويفتح وكيلًا نحو كلّ المسارات.
فالغلاف ثمن مقصود.
أمّا ما تملكه المنصّة فهو: brand.config.ts (الاسم · النطاق · الألوان · عنوان
التوثيق)، وcomponents/landing/** (النصّ التسويقي)، وapp/globals.css (رموز
ألوان العلامة)، وdeploy/** و.github/**.
وتمنع بوّابتا تكامل مستمر عودةَ التكرار — check-brand (يفشل البناء إذا ظهر اسم
منصّة خارج brand.config.ts) وcheck-routes (يحتاج كلّ مسار في الحزمة إلى
غلاف؛ وبدونه تختفي أيّ نقطة نهاية جديدة بصمت).