المصدر: specs/ar/02-api.mdتعديل هذه الصفحة

المواصفة 02 — عقود واجهة API (‏NestJS، ‏REST /api/v1)

الاصطلاحات: الصيغة JSON؛ التحقق من صحة كائنات النقل DTO عبر class-validator تحققاً صارماً — فالخصائص غير المعروفة تُرفض ولا تُحذف؛ وأحجام متون الطلبات محدودة؛ شكل الأخطاء { statusCode, code, message, messageAr }؛ التقسيم إلى صفحات ?page=1&limit=20{ items, total, page, limit }؛ المصادقة عبر كوكيز من نوع httpOnly هي nutqi_at / nutqi_rt؛ مُزخرِفات الحراسة @Roles(...)؛ وكل ذلك موصوف بتعليقات Swagger ولا يُقدَّم في الإنتاج إلا إذا فُعِّل صراحةً.

ومسارات القراءة تردّ صفوفاً مسطَّحة (‏D-26): الأسماء محلولةً في نص واحد، والتواريخ التقويمية بصيغة YYYY-MM-DD، والصور الرمزية روابط جاهزة للاستعمال، والعدّادات محسوبةً في الخادم، والعلاقات محذوفة — فلا يتسرّب تخطيط قاعدة البيانات إلى أي عميل.

وتحديد المعدّل يجري لكل عنوان شبكة، بميزانية أضيق بكثير على /auth/login ومسارات رمز التأكيد؛ وهو مكمِّل لقفل الحساب في G15 لا بديل عنه. والمتغيّران JWT_ACCESS_SECRET وCORS_ORIGIN مطلوبان في الإنتاج — فالتطبيق يرفض الإقلاع بدل الارتداد إلى قيمة افتراضية غير آمنة.

التوثيق التفاعلي (‏Swagger / OpenAPI)#

كل ما يلي هذا القسم مكتوب باليد، وقائمة مسارات مكتوبة باليد تنحرف عن الحقيقة فور أن يضيف أحدهم مساراً جديداً. أما التوثيق التفاعلي فلا ينحرف: تبنيه حزمة @nestjs/swagger عند الإقلاع من المُزخرِفات نفسها التي تُعرَّف بها المسارات — مُزخرِف المتحكّم ومُزخرِفات أفعال HTTP، ومعها @ApiTags التي تمنحه تجميع الوحدات نفسه المستعمل عناوينَ هنا — فهو يصف دائماً البناء الجاري فعلاً، وهو صفحة تُتصفَّح وفيها زر تجربة على كل عملية، لا وثيقة تُقرأ. والمسارات والأفعال والوسوم فيه مكتملة؛ أما مخططات الوسائط والمتون فليست أدقّ من المُزخرِفات نفسها، ولا وجود بعدُ لأي تعليق @ApiProperty، ولذلك تبقى أشكال الحمولات المرسومة تحت كل وحدة أدناه هي المرجع لها.

مساران، وكلاهما مركَّب خارج البادئة /api/v1 (‏SWAGGER_PATH في apps/api/src/app.setup.ts). والرابطان أدناه يُحَلّان مقابل مضيف الواجهة الذي يُهيَّأ به موقع التوثيق هذا:

  • /api/docs — واجهة Swagger نفسها.
  • /api/docs-json — وثيقة OpenAPI الخام، وتشتقّها @nestjs/swagger من مسار الواجهة فلا يمكن أن يصف الاثنان مخططين مختلفين. وهذه هي التي تُستورَد في Postman أو Insomnia، أو تُسلَّم لمولّد عملاء.

وهو في بيئة التطوير مُشغَّل دون أن يُطلَب. فالدالة apiDocsEnabled() تحترم ENABLE_API_DOCS حين تكون مضبوطة، وإلا ارتدّت إلى الشرط NODE_ENV !== "production"؛ فتُقدِّم واجهة التطوير المسارين دون أي تهيئة. والقيمة الصريحة تغلب في الاتجاهين: فالضبط ENABLE_API_DOCS=false يُطفئ التوثيق على جهاز التطوير أيضاً.

وفي أي بيئة منشورة لا يُقدَّم إلا حيث تُضبط ENABLE_API_DOCS=true، ولا يُقدَّم قط بلا بيانات دخول. والبوابة مقصودة (‏D-30)، ويذكر الملف apps/api/src/main.ts سببها في سطر واحد: المخطط خريطةٌ لسطح الهجوم. فجردٌ كاملٌ مقروء آلياً بكل مسار ووسيط وفحص دور يخدم المهاجم خدمةً لا يخدمها المتكامِل، إذ يكفي أن يُرسَل إليه الملف.

ولذلك فتشغيله يُشغِّل بيانات الدخول معه في الوقت نفسه. فالوسيط apiDocsAuth في الملف apps/api/src/app.setup.ts يطالب بمصادقة أساسية على البادئة /api/docs كلها — بما فيها المخطط الخام، إذ إن حماية الشطر المقروء وحده تمثيلٌ لا حماية — كما تجعل الدالة apiDocsCredentialProblem في الملف apps/api/src/common/env.ts ضبطَ ENABLE_API_DOCS=true دون API_DOCS_USER وAPI_DOCS_PASSWORD خطأً قاتلاً عند الإقلاع. فالتهيئة الناقصة تفشل بوصفها واجهةً ترفض الإقلاع، لا بوصفها مخططاً يقرؤه أي أحد. ووُضع الفحص في التطبيق لا في الوسيط العكسي عن قصد: فملف بيانات الدخول تحت /etc/nginx خفيٌّ عن المراجعة، وغائبٌ عن كل بيئة أخرى، ويضيع عند إعادة توليد إعداد المضيف.

وهو مُفعَّل في الإنتاج، فيردّ المساران 401 على الطلب المجهول و200 على الطلب المصحوب ببيانات الدخول. وتُسلَّم بيانات الدخول لكل جهة على حدة، لا تُنشَر هنا.

والمصادقة تجري بالكوكيز، فلا رمز يُلصَق في خانة. تصادق الواجهة بكوكيَّتين من نوع httpOnly هما nutqi_at (للوصول) وnutqi_rt (للتجديد)، ولهذا تُعلن تهيئة Swagger السطر addCookieAuth("nutqi_at") بدل مخطط الرمز الحامل. وهذا الإعلان يسمّي الكوكية في المخطط ويؤشّر العمليات التي تحتاجها؛ وليس موضعاً تُكتب فيه بيانات اعتماد، لأن الصفحة لا تستطيع ضبط ترويسة Cookie، ولأن كوكية httpOnly لا تُقرأ بالبرمجة أصلاً. والذي يجعل زر التجربة يعمل هو الجلسة التي يحملها المتصفح سلفاً: فالواجهة تُقدَّم من أصل الخادم نفسه، فتكون طلباتها على الأصل ذاته وترافقها الكوكية تلقائياً. أما متصفح بلا جلسة فيسجّل الدخول من الصفحة نفسها — يُنفَّذ POST /auth/login منها فتُضبط الكوكيز على ذلك الأصل، وتصير كل عملية بعده مصادَقاً عليها.

وحدة health#

  • GET /health (عام، بلا مصادقة) ← فحص حياة مع فحص قاعدة بيانات حقيقي. وهو خالٍ عمداً من أرقام الإصدارات وسلاسل الاتصال ونصوص الأخطاء؛ ويستهلكه الوسيط العكسي وفحص صحة الحاوية.

وحدة auth#

  • POST /auth/register بحمولة { role: PERSONAL_PATIENT|GUARDIAN|SPECIALIST|CENTER, firstName, lastName, email, phone, password, gender?, birthDate?, center? {nameAr, licenseNumber?, governorate?, city?, branchName?} } ← تُنشئ مستخدماً (بالحالة PENDING_VERIFICATION)، ومعه مركز وأول فرع حين يكون الدور CENTER طبقاً لـ G03؛ وتُرسل رمز تحقق للبريد والهاتف (المزوّد في بيئة التطوير هو console). تردّ 409 عند تكرار البريد أو رقم الهاتف.
  • POST /auth/verify-otp بحمولة { target, code, purpose: "verify" | "reset" } ← عند التحقق: تُعلَّم الوسيلة كمُوثَّقة، وتتحول الحالة إلى ACTIVE (للمريض وولي الأمر) أو PENDING_ACTIVATION (للأخصائي والمركز)؛ وتُضبط الكوكيز. وعند إعادة التعيين: تُعيد رمز resetToken صالحاً لمرة واحدة.
  • POST /auth/resend-otp بحمولة { target, channel? } — محكومة بمهلة 60 ثانية (مطابقة للعدّاد التنازلي في الواجهة).
  • POST /auth/login بحمولة { email, password } ← تضبط الكوكيز؛ وكلمة السر الخطأ تزيد عدّاد المحاولات؛ وعند القفل تردّ 423 مع lockedUntil طبقاً لـ G15.
  • POST /auth/forgot بحمولة { email, phone } ← رمز تحقق بغرض reset.
  • POST /auth/reset-password بحمولة { resetToken, password }.
  • POST /auth/refresh و POST /auth/logout.
  • GET /auth/me ← بيانات المستخدم + ملخص الملف الخاص بدوره + نسبة اكتمال التفعيل (وهي ما يحرّك حلقة البوابة).
  • PATCH /auth/password بحمولة { current, next } (من الإعدادات).

وحدة users#

  • PATCH /users/me (الأسماء بالعربية والإنجليزية، nationalId، تاريخ الميلاد، النوع، الصورة الشخصية، اللغة، وحقول العنوان).
  • PUT /users/me/languages بحمولة [{language, level}] (اللغات المحكية، G13-b).
  • GET/PUT /users/me/notification-channels بحمولة [{channel, target, enabled}]؛ و POST /users/me/notification-channels/:channel/verify (مسار رمز التحقق).

وحدة patients (بوابة ولي الأمر)#

  • GET /patients (الخاصة بي) / POST /patients (إضافة طفل، G09) / GET|PATCH /patients/:id.
  • POST /patients/:id/documents (رفع متعدد الأجزاء، النوع DOC|VIDEO) / GET /patients/:id/documents / DELETE …/:docId.
  • التشخيص: GET /forms/diagnosis-template ← القالب النظامي؛ و POST /patients/:id/diagnosis-response (تُنشئ الاستجابة أو تحدّثها عبر واجهة الإجابات الموصوفة أدناه).

وحدة specialists#

  • GET /specialists — الدليل العام (G12): المرشِّحات q و governorate و specialty و sessionType و priceMin/Max و rating؛ ولا يظهر إلا من كان بالحالة ACTIVE.
  • GET /specialists/:id/profile ← حمولة النظرة العامة (الإحصاءات: الخط الزمني للحجوزات، وتوزيع الأنواع، وأعلى البرامج — وكلها محسوبة)، والبيانات (الشهادات والتدريبات والفيديوهات)، والعيادات، وملخص التقييمات.
  • GET /specialists/:id/schedule?weekStart= ← شبكة الجلسات مع الأسعار؛ و GET /specialists/:id/slots?date=&clinicId?= ← المواعيد المتاحة بفترات نصف ساعة (ساعات العمل − الحجوزات − أيام الراحة) طبقاً لـ D-09.
  • مقصورة على الحساب نفسه: GET/PUT /specialists/me/profile، و POST/PATCH/DELETE /specialists/me/work-info، و …/certificates، و …/videos، و …/clinics، و PUT /specialists/me/schedule (كتل أيام الأسبوع)، و POST /specialists/me/days-off.
  • PUT /specialists/me/booking-settings بحمولة { availableForWork, acceptsOnline, acceptsOffline, acceptsConsultation }.
  • GET/POST/DELETE /specialists/me/blocklist.
  • GET /specialists/me/stats?period=all|year|month|week|day{ dailyAvgCases, totalCases, earningsCents, pending: bool }.

وحدة bookings#

  • POST /bookings بحمولة { patientId, specialistId, clinicId?|type ONLINE, sessionType, date, startTime } ← تُنشئ الحجز بالحالة PENDING؛ وتتحقق من أن الميعاد شاغر، وأن الأخصائي مفعَّل ومتاح، وأن الحاجز غير محظور؛ ويُحتسب السعر في الخادم. وتردّ 409 إذا كان الميعاد محجوزاً.
  • GET /bookings قائمة محدودة بنطاق الدور مع مرشِّحات (status و type و q و dateRange) بالإضافة إلى ?export=csv.
  • PATCH /bookings/:id/status بحمولة { status, meetingUrl?, newDate?/newTime? for POSTPONED } — الانتقالات المسموح بها محكومة بآلة الحالات (المواصفة docs-content/02 §5)؛ ويجوز لولي الأمر إلغاء حجزه هو وهو بالحالة PENDING/WAITING.
  • GET /bookings/upcoming ← الحجز التالي لعرضه في الشريط طبقاً لـ E10.

وحدة sessions#

  • POST /sessions (من حجز أو من حالة خاصة) / PATCH /sessions/:id بحمولة { progressPercent, evaluation, notes }.
  • GET /patients/:id/sessions، و GET /special-cases/:id/sessions.

وحدة special-cases (للأخصائي)#

  • عمليات إنشاء وقراءة وتعديل وحذف على /special-cases؛ والمرفقات على /special-cases/:id/attachments (المرحلة BEFORE|AFTER)؛ والملاحظات لها العمليات نفسها (حذف ناعم مع نافذة تراجع، G18).

وحدة forms (محرك الخطط والمقاييس)#

  • القوالب: GET/POST /forms/templates (الخاصة بي)، و GET/PATCH/DELETE /forms/templates/:id (مع حمولة متداخلة للصفحات والأسئلة، ومُدارة بالإصدارات).
  • الإسناد: POST /forms/assignments بحمولة { templateId, patientId|specialCaseId, dueDate? } ← إشعار إلى ولي الأمر.
  • GET /forms/assignments?role=guardian|specialist&status= ← بيانات البطاقات (لم يتم / تم).
  • الإجابة: GET /forms/assignments/:id/response (أو إنشاؤها)، و PUT /forms/responses/:id/answers/:questionId بحمولة { value }حفظ تلقائي بالإدراج أو التحديث طبقاً لـ E01، و POST /forms/responses/:id/submit ← يتحقق من الحقول المطلوبة ← يصبح الإسناد ANSWERED.
  • النتائج: GET /forms/assignments/:id/result (عرض للقراءة فقط للسؤال والإجابة).

وحدة reviews#

  • POST /reviews بحمولة { specialistId, bookingId?, stars, text, kind } (يشترط أن يكون لصاحب التقييم حجز بالحالة DONE مع الأخصائي حين يكون النوع SESSION).
  • GET /specialists/:id/reviews?kind=&period=؛ و PATCH /reviews/:id/like، و PATCH /reviews/:id/reply (للأخصائي)، و POST /reviews/:id/report طبقاً لـ G17.

وحدة payments#

  • GET /payments/mine (سجل مدفوعات ولي الأمر، G01) ← الصفوف مع الإجماليات.
  • POST /bookings/:id/payment بحمولة { method, status } (تسجّله السكرتارية أو الأخصائي؛ ويُنشئ معاملة محفظة من نوع EARNING عند الحالة PAID).
  • GET /wallet (للأخصائي) ← { balanceCents, withdrawnCents }؛ و GET /wallet/transactions.
  • POST /wallet/withdrawals بحمولة { amountCents, method, target } طبقاً لـ G19؛ و GET /wallet/withdrawals.

وحدة centers#

  • GET/PATCH /centers/me (للمالك والمدير)؛ والفروع لها العمليات الكاملة على /centers/me/branches.
  • الموظفون: GET/POST /centers/me/staff (إنشاء مستخدم موظف مع الدور والفرع والراتب)، و PATCH/DELETE /centers/me/staff/:id.
  • مرضى المركز: GET /centers/me/patients (مع تمرير طلبات الملفات الشخصية).

وحدة hr (للمركز)#

  • POST /hr/attendance/clock-in|clock-out (للموظف نفسه، بدور CENTER_SPECIALIST/SECRETARY) — و branchId اختياري طبقاً لـ G20.
  • GET /hr/attendance?staffId?&range (المالك والمدير يريان الجميع؛ والموظف يرى سجلّه هو).
  • عمليات شبه كاملة على /hr/absences، و /hr/overtime، و /hr/penalties (مع deductionCents)، وهي للمالك والمدير فقط.
  • الطلبات: POST /hr/requests (للموظف)، و GET /hr/requests (محدودة بالنطاق)، و PATCH /hr/requests/:id/decision بحمولة { status: APPROVED|REJECTED } (للمالك والمدير) ← إشعار.

وحدة jobs#

  • للمركز: GET/POST /jobs/postings، و PATCH /jobs/postings/:id (مسودة ← منشورة ← مغلقة)، والطلبات: GET /jobs/postings/:id/applications، و PATCH /jobs/applications/:id/decision ← عند الحالة ACCEPTED تُعرض حمولة إنشاء سجل CenterStaff.
  • للأخصائي: GET /jobs/market (المنشورة، مع مرشِّحات، G08)، و POST /jobs/postings/:id/apply، و GET /jobs/applications/mine.

وحدة notifications#

  • GET /notifications?unread=، و PATCH /notifications/:id/read، و PATCH /notifications/read-all.
  • GET /notifications/stream — بتقنية SSE طبقاً لـ E13.
  • خدمة إطلاق الأحداث تستعملها بقية الوحدات؛ وهي تُرسل دائماً عبر القناة IN_APP إضافةً إلى القنوات المفعَّلة والمُوثَّقة (البريد عبر console أو SMTP تطويري؛ وواتساب وتليجرام واجهتان صوريتان تسجّلان الحمولات فقط، D-17).

وحدة files#

  • POST /files رفع متعدد الأجزاء (يتطلب المصادقة) ← StoredFile؛ و GET /files/:id (الصلاحية بحسب الملكية أو الارتباط)؛ مع حدود للحجم والنوع (الصور 5 ميجابايت، والمستندات 10 ميجابايت، والفيديو 100 ميجابايت).

وحدة admin (عبر واجهة API فقط في الإصدار الأول)#

  • GET /admin/activations (الأخصائيون والمراكز المعلَّقون)، و PATCH /admin/activations/:userId بحمولة { approve|reject }.
  • GET /admin/withdrawals، و PATCH /admin/withdrawals/:id بحمولة { TRANSFERRED|REJECTED }.
  • GET /admin/reported-reviews.

الأحداث ← الإشعارات (الحد الأدنى)#

booking.created (← الأخصائي/السكرتارية)، و booking.status_changed (← ولي الأمر)، و assignment.created (← ولي الأمر)، و assignment.answered (← الأخصائي)، و application.decided (← الأخصائي)، و hr.request.decided (← الموظف)، و withdrawal.processed (← الأخصائي)، و activation.decided (← المستخدم).