→ الرئيسية

دليل المطوّر

كيف يعمل الـ API وكيف تبنون عليه تطبيق Flutter. التفاصيل الكاملة لكل رابط في التوثيق التفاعلي.

1. فكرة التطبيق

تطبيق لعائلة واحدة (عائلة الخضري): شجرتها كاملة، وملف لكل فرد حيّاً أو متوفى، وأخبار العائلة.

الدورماذا يستطيع
زائر (بدون حساب)يتصفح كل شيء
user عضويعلّق ويعجب بالأخبار (إذا كان الخبر يسمح)
staff موظفيضيف ويعدّل الأفراد والأخبار. بدون صلاحية persons.direct / posts.direct تذهب تعديلاته للمدير للموافقة (رد 202)
admin مدير العائلةكل شيء + الموافقة على طلبات الموظفين + المستخدمين والإعدادات. يصله إشعار بكل عملية موظف

2. الأساسيات

Base URL:  https://api-production-34bd.up.railway.app/api/v1
Headerمتى
Accept: application/jsonدائماً
Accept-Language: ar أو enدائماً — يحدد لغة البيانات والتسميات ورسائل الأخطاء
Authorization: Bearer <token>بعد تسجيل الدخول (اختياري للتصفح، لكن أرسلوه إن وُجد: يغيّر can وliked_by_me)

شكل الرد الناجح

{ "message": "تم الحفظ",        // في عمليات الكتابة فقط
  "data": { ... } أو [ ... ],
  "meta": { ... } }              // أحياناً: عدادات أو معلومات إضافية

القوائم (pagination)

GET /persons و/posts وأمثالها ترجع صفحات. أرسلوا page وper_page، واقرؤوا:

"links": { "next": "https://…/persons?page=2", "prev": null, … },
"meta":  { "current_page": 1, "last_page": 48, "per_page": 25, "total": 1188 }

للتحميل اللانهائي: اطلبوا الصفحة التالية ما دام links.next ليس null.

شكل الخطأ (دائماً)

{ "message": "البيانات المدخلة غير صحيحة.",   // مترجمة: اعرضوها للمستخدم
  "code": "VALIDATION_FAILED",                 // ثابتة: اعتمدوا عليها في الكود
  "errors": { "email": ["…"] } }               // في 422 فقط: خطأ لكل حقل

أشياء يجب معرفتها

3. الدخول والحساب

الرابطالجسم
تسجيل جديدPOST /auth/registername, email, password, password_confirmation, phone?, device_name?
دخولPOST /auth/loginemail, password, device_name?
نسيت كلمة السرPOST /auth/forgot-passwordemail
حسابيGET /auth/me—
تعديل حسابيPATCH /auth/mename, phone, locale…
تغيير كلمة السرPUT /auth/me/passwordcurrent_password, password, password_confirmation
خروجPOST /auth/logout—
حذف الحسابDELETE /auth/me—

الدخول والتسجيل يرجعان:

{ "data": { "token": "12|AbC…", "token_type": "Bearer",
            "user": { "id", "name", "email", "roles": ["staff"],
                      "permissions": ["persons.create", "persons.update", …] } } }

4. الشجرة (الرسم)

GET /graph يرجع جزءاً من الشجرة حول شخص، جاهزاً للرسم:

Parameterالمعنى
rootالشخص في المنتصف (افتراضياً جذر العائلة)
depth_up / depth_downكم جيلاً للأعلى (الآباء) وللأسفل (الأبناء)
max_nodesحد أقصى للعقد (حتى 2000 = الشجرة كاملة)
{ "data": {
    "root_id": "01m45…",
    "nodes": [ { "id", "ref_no", "label": "سالم الخضري", "gender": "male",
                 "life_status": "deceased", "birth_year", "death_year", "thumb_url",
                 "generation": 1, "father_id": "01m45…", "children_count": 3,
                 "has_more_up": false, "has_more_down": true } ],
    "families": [ { "father_id": "01m45…", "children": ["id1", "id2"] } ]   // مرتبة من الأكبر
  },
  "meta": { "graph_version": 2424, "nodes_count": 3, "truncated": false } }

5. الأفراد

الرابطملاحظات
القائمةGET /personsفلاتر: filter[search]، filter[gender]، filter[life_status]، filter[generation]، filter[father_id]. ترتيب: sort=birth_date أو -birth_date، generation…
بحث سريعGET /persons/search?q=20 نتيجة مختصرة. الاسم بأي لغة، والعربي موحّد ("احمد" تجد "أحمد"). رقم = ref_no
الملف الكاملGET /persons/{id}كل الحقول: الألقاب، الكنية، الميلاد والوفاة ومكانها، الدفن، المهنة، النبذة، الأب
بطاقة الملفGET /persons/{id}/cardالأنسب لشاشة الملف: الاسم بالنسب (lineage_name: فلان بن فلان)، العمر، الأب، الأبناء، العدادات، وcan
الأبناءGET /persons/{id}/childrenمرتّبين من الأكبر، وmeta فيها عدد الأبناء والبنات
الأقاربGET /persons/{id}/relativesالأب، الجد، الإخوة، الأعمام والعمات، الأبناء
الأجدادGET /persons/{id}/ancestors?depth=6سلسلة الآباء للأعلى
الذريةGET /persons/{id}/descendants?depth=3مقسمة حسب الجيل
صلة القرابةGET /persons/{id}/relationship/{otherId}label جاهز ("عم"، "بنت عم"، "أخت")، والجد المشترك
أخبار الشخصGET /persons/{id}/postsالأخبار المرتبطة به (نعي، زفاف…)

كائن can في البطاقة يقول ماذا يستطيع المستخدم الحالي:

"can": { "update": true, "delete": false, "add_relative": true, "add_child": true,
         "needs_review": true }   // true = تعديلاته ستذهب للمدير: اعرضوا تنبيهاً بذلك

6. الأخبار

الرابطملاحظات
القائمةGET /poststype (news, obituary, wedding, birth, event, announcement)، q، person_id. المثبّت أولاً
الخبرGET /posts/{id}العنوان، النص، الصور، الأشخاص المرتبطون
التعليقاتGET /posts/{id}/commentsصفحات
إضافة تعليقPOST /posts/{id}/comments{"body": "…"} — يحتاج حساب
حذف تعليقDELETE /comments/{id}صاحبه أو المدير (يرجع 204 بدون جسم)
إعجاب / إلغاءPOST / DELETE /posts/{id}/likeيحتاج حساب

7. الإضافة والتعديل والموافقة

العمليات الأساسية

الرابطالجسم
إضافة قريبPOST /persons/{id}/relatives{"relation": "son", "person": {"first_name": {"ar": "أحمد"}, "birth_date": "1990-03-12"}}
relation: son، daughter، brother، sister، father
تعديل شخصPATCH /persons/{id}الحقول المتغيرة فقط، مثلاً {"life_status": "deceased", "death_date": "2010-12-01"}
تغيير الأبPUT /persons/{id}/father{"father_id": "…"} أو null
صورة الشخصPOST /persons/{id}/photomultipart، حقل photo (jpg/png/webp، حتى 5MB)
ترتيب الأبناءPUT /persons/{id}/children/order{"order": ["id1", "id2", …]} (للتوائم أو التواريخ المجهولة)
حذف / استرجاعDELETE /persons/{id} ثم POST /persons/{id}/restoreالحذف ناعم: الأبناء يبقون
خبر جديدPOST /postsmultipart: title[ar]، body[ar]، type، status (published/draft)، published_at (للجدولة)، allow_comments، allow_likes، is_pinned، person_ids[]، images[] (حتى 10)
تعديل / حذف خبرPATCH / DELETE /posts/{id}الصور: POST /posts/{id}/images وDELETE /posts/{id}/images/{imageId}

قواعد تتحقق منها الشجرة تلقائياً (ترجع 422 برمز واضح): الأب ذكر وأكبر بـ 12 سنة على الأقل، لا يكون الشخص جداً لنفسه، الميلاد قبل الوفاة… انظر رموز الأخطاء.

تحذيرات بدون رفض: إذا كانت التواريخ تقريبية، أو وُلد الشخص بعد وفاة أبيه، يُحفظ الطلب ويرجع معه "meta": {"warnings": ["INVALID_PARENT_AGE"]} أو BIRTH_AFTER_PARENT_DEATH — اعرضوا تنبيهاً للمستخدم ليتأكد.

الموافقة: انتبهوا لـ 201 مقابل 202

نفس الطلب قد يرجع ردين مختلفين حسب صلاحية المستخدم:

الردالمعنىماذا تعرضون
200 / 201حُفظ مباشرة (مدير، أو موظف معه *.direct)"تم الحفظ" وحدّثوا الشاشة
202لم يُحفظ بعد: أُرسل للمدير كطلب تعديل"أُرسل للمدير للموافقة" — لا تضيفوا الشخص للشجرة محلياً
// 202
{ "message": "أُرسل طلبك للمدير للموافقة",
  "data": { "change_request": { "id", "status": {"value": "pending"}, "action": {"label": "إضافة شخص"},
                                "subject": "أحمد الخضري", "preview": { … } } } }

الطلبات: GET /change-requests (الموظف يرى طلباته، المدير يرى الكل)، والمدير يوافق POST /change-requests/{id}/approve أو يرفض …/reject مع {"note": "…"}. الموظف يلغي طلبه بـ DELETE.

8. الإشعارات

القائمةGET /notificationsكل إشعار فيه title وbody جاهزان للعرض، وdata (مثل change_request_id للانتقال)
عدد غير المقروءGET /notifications/unread-countللشارة على الأيقونة
تعليم كمقروءPOST /notifications/{id}/read أو /notifications/read-all

لا يوجد Push حالياً (FCM مخطط لاحقاً): اطلبوا unread-count عند فتح التطبيق وكل دقيقة أو دقيقتين أثناء استخدامه.

9. شاشات المدير

كلها تحت /admin وتحتاج صلاحيات المدير:

10. رموز الأخطاء

HTTPcodeالمعنى
401UNAUTHENTICATEDبدون توكن أو توكن منتهي
401INVALID_CREDENTIALSالإيميل أو كلمة السر خطأ
403ACCOUNT_SUSPENDEDالحساب موقوف
403FORBIDDENلا توجد صلاحية
404NOT_FOUNDغير موجود
422VALIDATION_FAILEDحقول خاطئة: التفاصيل في errors
429TOO_MANY_REQUESTSطلبات كثيرة، أعيدوا بعد قليل
422PARENT_GENDER_MISMATCHالأب يجب أن يكون ذكراً
422INVALID_PARENT_AGEالأب أكبر من الابن بأقل من 12 سنة (عندما يكون التاريخان دقيقين)
422CYCLE_DETECTEDالشخص لا يكون جداً لنفسه
422INVALID_DATE_ORDERالميلاد بعد الوفاة أو تاريخ في المستقبل
422INVALID_DEATH_DATAبيانات وفاة لشخص غير متوفى
409PARENT_ALREADY_SETله أب مسبقاً
422PARENTS_REQUIRED_FOR_SIBLINGأضيفوا الأب أولاً لإضافة أخ أو أخت
422GENDER_CHANGE_CONFLICTلا يمكن تغيير جنس شخص له أبناء
422REF_NO_TAKENالرقم المرجعي مستخدم
422TRANSLATION_REQUIREDالقيمة بالعربية (اللغة الافتراضية) مطلوبة
403COMMENTS_DISABLED / LIKES_DISABLEDمغلقة على هذا الخبر
422TOO_MANY_IMAGESأكثر من 10 صور للخبر
409CHANGE_REQUEST_CLOSEDالطلب عولج مسبقاً
409CHANGE_REQUEST_FAILEDتعذرت الموافقة لأن الشجرة تغيرت

القاعدة البسيطة: اعرضوا message للمستخدم كما هي (مترجمة)، واستخدموا code فقط عندما تحتاجون تصرفاً خاصاً (مثل 401 → شاشة الدخول).

11. Flutter: البداية

عميل dio واحد للتطبيق

final dio = Dio(BaseOptions(
  baseUrl: 'https://api-production-34bd.up.railway.app/api/v1',
  headers: {'Accept': 'application/json'},
  connectTimeout: const Duration(seconds: 60),   // السيرفر قد يأخذ وقتاً في أول طلب
));

dio.interceptors.add(InterceptorsWrapper(
  onRequest: (options, handler) async {
    options.headers['Accept-Language'] = currentLocale;          // 'ar' أو 'en'
    final token = await storage.read(key: 'token');
    if (token != null) options.headers['Authorization'] = 'Bearer $token';
    handler.next(options);
  },
  onError: (e, handler) async {
    final body = e.response?.data;
    if (e.response?.statusCode == 401) await auth.logoutLocally();
    handler.reject(e.copyWith(error: ApiError(
      code: body is Map ? body['code'] : 'NETWORK',
      message: body is Map ? body['message'] : 'تحقق من الاتصال',
      fields: body is Map ? body['errors'] : null,
    )));
  },
));

الحفظ مع الموافقة

final res = await dio.post('/persons/$id/relatives', data: {
  'relation': 'son',
  'person': {'first_name': {'ar': name}, 'birth_date': date},
});
if (res.statusCode == 202) {
  showSnack('أُرسل للمدير للموافقة');
} else {
  showSnack(res.data['message']);
  refreshTree();
}

رفع صورة

await dio.post('/persons/$id/photo', data: FormData.fromMap({
  'photo': await MultipartFile.fromFile(file.path),
}));

الشجرة مع التخزين

final res = await dio.get('/graph',
  queryParameters: {'depth_down': 12, 'max_nodes': 2000},
  options: Options(
    headers: {if (savedEtag != null) 'If-None-Match': savedEtag},
    validateStatus: (s) => s == 200 || s == 304,
  ));
if (res.statusCode == 304) return cachedGraph;
save(res.data, res.headers.value('etag'));

12. كل شاشة وروابطها

الشاشةالروابط
البداية (Splash)/meta/languages، /meta/settings، /family، و/auth/me إذا يوجد توكن
الشجرة/graph، /graph/expand/{id}، وعند الضغط على شخص: /persons/{id}/card في Bottom Sheet
الأفراد/persons بالفلاتر، و/persons/search لمربع البحث
ملف شخص/persons/{id}/card، تبويبات: /persons/{id} (التفاصيل)، /relatives، /children، /posts
صلة القرابة/persons/search لاختيار الشخصين ثم /persons/{a}/relationship/{b}
الأخبار/posts (تبويبات حسب type)
خبر/posts/{id}، /comments، /like
إضافة / تعديل شخص/persons/search (لاختيار الأب)، POST /relatives، PATCH /persons/{id}، /photo
الإشعارات/notifications، /unread-count، /read
طلبات التعديل/change-requests، /approve، /reject
حسابي/auth/me، /auth/me/password، /auth/logout
المدير/admin/stats، /admin/users، /admin/settings

⚠️ هذا هو السيرفر الحقيقي ببيانات العائلة. للتجربة استخدموا حساب موظف بدون صلاحية مباشرة: كل إضافاتكم تذهب للمدير بدل أن تُكتب في الشجرة.

أي سؤال أو رابط ناقص؟ تواصلوا مع مسؤول المشروع.