Skip to Content

مزامنة قوائم نظام إدارة التعلم

يستطيع أمل استيراد قوائم الطلاب والمعلمين من نظام إدارة التعلم في مدرستك والإبقاء عليها متزامنة. يدعم الإصدار الأول موفِّرَين: Google Classroom (مستند إلى OAuth بتوكن حي) وOneRoster (رفع مجموعة ملفات CSV، دون بيانات اعتماد حية). يتشارك الموفِّران سير عمل المراجعة نفسه القائم على مبدأ المعاينة ثم التأكيد.

جميع عمليات الكتابة على هذه الواجهة (إنشاء اتصال، أو رفع مجموعة ملفات، أو حفظ التعيينات، أو تشغيل معاينة، أو التأكيد، أو بدء مزامنة) محظورة على المستأجرين التجريبيين. أما عمليات القراءة فحسب (سرد الاتصالات، أو تصفّح المقررات، أو عرض سجل المزامنة) فتعمل بصورة طبيعية على المستأجرين التجريبيين.


نوعا الاتصال

Google Classroom (OAuth)

يُنشَأ اتصال Google Classroom عبر تدفق موافقة OAuth 2.0 القياسي:

  1. استدعِ POST /api/lms/connections/google_classroom/oauth-start. يحمل الرد حقل authUrl يشير إلى شاشة موافقة Google.
  2. أعِد توجيه مستخدم المسؤول إلى ذلك العنوان. بعد منحه التفويض، يعيد Google التوجيه إلى GET /api/lms/connections/google_classroom/oauth-callback?code=...&state=....
  3. يتبادل مسار الاستدعاء العكسي رمز التفويض، ويخزِّن رمزَي الوصول والتحديث، ويُعيد جسم JSON من نوع 200 LmsConnection. لا يوجد إعادة توجيه HTTP من مسار الاستدعاء العكسي؛ يتولّى العميل التنقل بعد قراءة جسم الرد.

يُطبَّق الاتصال بأسلوب “أدرج أو حدِّث” على المفتاح الطبيعي (organizationId, provider, scope, createdByUserId)، وبذلك تُحدِّث إعادة التفويض لبيانات الاعتماد المنتهية صلاحيتها الرموزَ في موضعها دون إنشاء اتصال مكرَّر.

OneRoster (مجموعة ملفات CSV)

لا يتطلب اتصال OneRoster أي OAuth. ارفع مجموعة ملفات CSV الخاصة بـ OneRoster 1.1 إلى POST /api/lms/connections/oneroster/upload بجسم JSON يحتوي على خريطة files:

{ "files": { "classes.csv": "<csv text>", "users.csv": "<csv text>", "enrollments.csv": "<csv text>" } }

يخزِّن الخادم محتوى CSV في سجل الاتصال. تُعيد المعاينة والتأكيد تحليله محليًا دون الحاجة إلى رفع جديد. يُعيد رفع ناجح 201 LmsConnection. يؤدي رفع مجموعة ملفات جديدة إلى استبدال المحتوى المخزَّن ومسح ذاكرة التخزين المؤقت للمقررات في ذلك الاتصال.


توزيع الصلاحيات

تتحكّم صلاحيتان في واجهة نظام إدارة التعلم. تتباين درجة ثقتهما لأن إدارة الاتصالات وتكوين المقررات لا تكتب أي صفوف في جدول “الأشخاص”، بينما تفعل ذلك المعاينة والتأكيد.

الصلاحيةتتحكّم في
MANAGE_LMS_CONNECTIONSسرد الاتصالات؛ بدء OAuth؛ استدعاء OAuth العكسي؛ رفع OneRoster؛ حذف اتصال؛ سرد المقررات؛ حفظ التعيينات؛ سرد تشغيلات المزامنة
MANAGE_ROSTERإنشاء معاينة؛ تعديل صفوف المعاينة؛ التأكيد؛ بدء مزامنة فورية

المسؤول الذي يملك MANAGE_LMS_CONNECTIONS دون MANAGE_ROSTER يستطيع تكوين الاتصال وتعيينه، غير أنه لا يستطيع بدء أي عملية تنشئ حسابات مستخدمين أو تحدِّثها. امنح MANAGE_ROSTER للمسؤولين المخوَّلين فحسب بإنشاء الحسابات.


اكتشاف المقررات وتعيينها

بعد تفعيل الاتصال، استرجع المقررات المتاحة عبر GET /api/lms/{provider}/courses. تُقدَّم النتائج من ذاكرة تخزين مؤقت داخل العملية لمدة 5 دقائق لكل اتصال. مرِّر ?cache=bypass لإجبار الخادم على جلب بيانات جديدة من الموفِّر (مفيد مباشرةً بعد تغيير القائمة). يتضمّن الرد حقل cached: boolean حتى يعرف المستدعي أيّ المسارين جرى اتخاذه.

عيِّن مقررًا أو أكثر من مقررات الموفِّر على فصول أمل عبر POST /api/lms/{provider}/mappings. أرسِل connectionId ومصفوفة من أزواج { externalCourseId, amalClassId }. يجب أن ينتمي كل amalClassId إلى مؤسسة المستدعي؛ يُنتج معرّف فصل من مؤسسة مختلفة خطأ 422. تُطبَّق التعيينات بأسلوب “أدرج أو حدِّث” على المفتاح الفريد (organizationId, providerCourseId)، وبذلك يستبدل تعيين مقرر لفصل مختلف التعيينَ السابق.


المعاينة والتأكيد

إنشاء الحسابات عملية من خطوتين. لا يُكتَب شيء في حسابات المستخدمين حتى تُؤكِّد صراحةً.

الخطوة الأولى: إنشاء معاينة

يقرأ POST /api/lms/preview القائمة الخارجية للمقررات المُعيَّنة في الاتصال ويُنتج مقارنة تفاضلية للقراءة فحسب. لا يكتب شيئًا في User، أو StudentProfile، أو TeacherProfile، أو ClassStudent، أو ClassTeacher، أو ParentStudent. الرد هو 200 LmsPreviewResponse:

{ "previewId": "<cuid>", "expiresAt": "<ISO-8601>", "summary": { "create": 4, "update": 12, "conflict": 2, "skip": 0 }, "rows": [ ... ] }

للمعاينة صلاحية 24 ساعة. بعد انتهاء الصلاحية، تُعيد استدعاءات التأكيد وتعديل الصفوف 410 Preview expired. أنشئ معاينة جديدة للبدء من جديد.

يحمل كل صف حقل action بأحد أربعة قيم:

الإجراءالمعنى
createلم يُعثَر على مستخدم مطابق في أمل؛ سينشئ التأكيد حسابًا جديدًا
updateطابق مستخدمًا موجودًا في أمل دون حقول متعارضة؛ سيحدِّث التأكيد عضوية الفصل
conflictطابق مستخدمًا موجودًا لكن بيانات نظام إدارة التعلم تختلف عن بيانات أمل (الاسم أو المرحلة الدراسية)؛ يجب حل التعارض قبل التأكيد
skipالصف مستثنى عمدًا من المقارنة التفاضلية (مثال: هوية فرعية مكرَّرة)

يتبع مطابقة الهوية سلسلة أولويات: أولًا عبر رابط الهوية الخارجي للموفِّر (provider, externalSub)، ثم عبر مطابقة البريد الإلكتروني التامة داخل المؤسسة ذاتها. لا توجد مطابقة تلقائية تقريبية.

الخطوة الثانية: حل التعارضات

يجب تحديد قرار لصفوف ذات action = conflict قبل التأكيد. استخدم PATCH /api/lms/preview/{previewId}/rows/{rowId} بجسم { "resolution": "<value>" }.

يُقبَل قيمتان للقرار:

القيمةالأثر عند التأكيد
accept_lmsيستبدل حقل أمل بقيمة نظام إدارة التعلم
keep_amalيحتفظ بحقل أمل؛ يحدِّث عضوية الفصل فحسب

أي قيمة أخرى تُنتج خطأ تحقق من Zod. اترك الصفوف التي لا تريد استيرادها دون قرار؛ سيرفض التأكيد بـ 422 Unresolved conflict rows حتى يُحدَّد قرار لكل صف تعارض.

الخطوة الثالثة: التأكيد

يُنشئ POST /api/lms/commit/{previewId} القائمة المُراجَعة بأكملها في معاملة Postgres واحدة: تُطبَّق جميع عمليات الإنشاء والتحديث وصفوف التعارض المحلولة ذريًا أو لا تُطبَّق أيٌّ منها. يُعيد تأكيد ناجح 200 LmsCommitResponse مع أعداد الصفوف التي جرى إنشاؤها وتحديثها وتخطّيها.

التأكيد متكافئ: إذا سبق تأكيد المعاينة ذاتها، يُعيد الرد { alreadyCommitted: true } دون أي كتابة إضافية.


المزامنة التلقائية (autoApplySimpleChanges)

يحمل كل اتصال علامة autoApplySimpleChanges. عند تشغيل جدولة cron أو مزامنة يدوية فورية، تتحكّم العلامة فيما يجري بعد إنشاء المقارنة التفاضلية:

  • العلامة مُغلَقة (الافتراضي): تُقيَّد المعاينة دائمًا لمراجعة المسؤول، بصرف النظر عن محتوى المقارنة. يُعيَّن LmsSyncRun.status إلى pending_admin_review. لا تُكتَب أي صفوف أشخاص تلقائيًا.
  • العلامة مفتوحة، دون صفوف تعارض: تُؤكَّد المقارنة التفاضلية تلقائيًا في معاملة واحدة. يُعيَّن LmsSyncRun.status إلى success.
  • العلامة مفتوحة، مع وجود صفوف تعارض: لا يستطيع النظام حل التعارض تلقائيًا، فتُقيَّد المعاينة لمراجعة المسؤول تمامًا كما لو كانت العلامة مُغلَقة. يُعيَّن LmsSyncRun.status إلى pending_admin_review.

في جميع الحالات يُحدَّث lastSyncAt على الاتصال بعد تشغيل المزامنة. يُحدَّث lastSuccessfulSyncAt فقط عند نجاح التأكيد التلقائي.


المزامنة الفورية اليدوية

لبدء مزامنة فورية خارج جدول cron، استدعِ POST /api/lms/connections/{id}/sync-now. يُعيد المسار 202 بجسم { syncRunId, status } حيث status إحدى القيم: completed، أو pending_admin_review، أو error. يجب أن يكون الاتصال في حالة active؛ يُعيد اتصال في أي حالة أخرى 404.


إلغاء الرمز

عند إعادة الموفِّر HTTP 401 أو 403 أثناء أي استدعاء لمهيّئ البيانات (جلب المقررات أو جلب القائمة)، تتعامل خدمة المزامنة مع ذلك باعتباره إلغاءً للرمز. يحدث الآتي ذريًا:

  1. يُعيَّن LmsConnection.connectionStatus إلى revoked.
  2. يُعيَّن LmsConnection.syncCronEnabled إلى false (يُعطَّل cron).
  3. يُرسَل إشعار lms_connection_revoked إلى كل مسؤول نشط في المؤسسة عبر نظام الإشعارات.
  4. يُضاف صف LmsSyncRun بـ status = error وerrorMessage = token_revoked.

تُلزِم إعادة الاتصال المسؤولَ بتشغيل تدفق OAuth من جديد (أو إعادة رفع مجموعة ملفات CSV لـ OneRoster) لاستعادة الاتصال إلى حالة active وإعادة تفعيل cron.


سجل المزامنة

يُعيد GET /api/lms/sync-runs سجلات LmsSyncRun مُصفَّحة لمؤسسة المستدعي، الأحدث أولًا. استخدم ?page= و?limit= (الحد الأقصى 100) للتصفح. يسجِّل كل تشغيل ما إذا كان مُشغَّلًا بجدولة cron أو يدويًا، وما إذا جرى تطبيقه تلقائيًا، وأوقات البدء والانتهاء، وأعداد الصفوف، ورسالة الخطأ عند الاقتضاء.


مرجع واجهة برمجة التطبيقات

تتطلب جميع المسارات جلسة موثَّقة. يُستَخلَص organizationId دائمًا من سياق الجلسة ولا يُقبَل في أجسام الطلبات أو معاملات المسار.

الطريقةالمسارالصلاحيةالتجريبيتُعيد
GET/api/lms/connectionsMANAGE_LMS_CONNECTIONSمسموح200 LmsConnectionListResponse
POST/api/lms/connections/{provider}/oauth-startMANAGE_LMS_CONNECTIONSمحظور200 LmsOAuthStartResponse
GET/api/lms/connections/{provider}/oauth-callbackMANAGE_LMS_CONNECTIONSمحظور200 LmsConnection
POST/api/lms/connections/oneroster/uploadMANAGE_LMS_CONNECTIONSمحظور201 LmsConnection
DELETE/api/lms/connections/{id}MANAGE_LMS_CONNECTIONSمحظور200 { ok: true }
GET/api/lms/{provider}/coursesMANAGE_LMS_CONNECTIONSمسموح200 LmsCoursesResponse
POST/api/lms/{provider}/mappingsMANAGE_LMS_CONNECTIONSمحظور200 LmsMappingResponse
POST/api/lms/previewMANAGE_ROSTERمحظور200 LmsPreviewResponse
PATCH/api/lms/preview/{previewId}/rows/{rowId}MANAGE_ROSTERمحظور200 LmsPreviewResponse
POST/api/lms/commit/{previewId}MANAGE_ROSTERمحظور200 LmsCommitResponse
POST/api/lms/connections/{id}/sync-nowMANAGE_ROSTERمحظور202 LmsSyncNowResponse
GET/api/lms/sync-runsMANAGE_LMS_CONNECTIONSمسموح200 LmsSyncRunsResponse

للاطلاع على تعريفات المخططات الكاملة راجع مرجع واجهة برمجة التطبيقات.


أدلة ذات صلة