مزامنة قوائم نظام إدارة التعلم
يستطيع أمل استيراد قوائم الطلاب والمعلمين من نظام إدارة التعلم في مدرستك والإبقاء عليها متزامنة. يدعم الإصدار الأول موفِّرَين: Google Classroom (مستند إلى OAuth بتوكن حي) وOneRoster (رفع مجموعة ملفات CSV، دون بيانات اعتماد حية). يتشارك الموفِّران سير عمل المراجعة نفسه القائم على مبدأ المعاينة ثم التأكيد.
جميع عمليات الكتابة على هذه الواجهة (إنشاء اتصال، أو رفع مجموعة ملفات، أو حفظ التعيينات، أو تشغيل معاينة، أو التأكيد، أو بدء مزامنة) محظورة على المستأجرين التجريبيين. أما عمليات القراءة فحسب (سرد الاتصالات، أو تصفّح المقررات، أو عرض سجل المزامنة) فتعمل بصورة طبيعية على المستأجرين التجريبيين.
نوعا الاتصال
Google Classroom (OAuth)
يُنشَأ اتصال Google Classroom عبر تدفق موافقة OAuth 2.0 القياسي:
- استدعِ
POST /api/lms/connections/google_classroom/oauth-start. يحمل الرد حقلauthUrlيشير إلى شاشة موافقة Google. - أعِد توجيه مستخدم المسؤول إلى ذلك العنوان. بعد منحه التفويض، يعيد Google التوجيه إلى
GET /api/lms/connections/google_classroom/oauth-callback?code=...&state=.... - يتبادل مسار الاستدعاء العكسي رمز التفويض، ويخزِّن رمزَي الوصول والتحديث، ويُعيد
جسم 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 أثناء أي استدعاء لمهيّئ البيانات (جلب المقررات أو جلب القائمة)،
تتعامل خدمة المزامنة مع ذلك باعتباره إلغاءً للرمز. يحدث الآتي ذريًا:
- يُعيَّن
LmsConnection.connectionStatusإلىrevoked. - يُعيَّن
LmsConnection.syncCronEnabledإلىfalse(يُعطَّل cron). - يُرسَل إشعار
lms_connection_revokedإلى كل مسؤول نشط في المؤسسة عبر نظام الإشعارات. - يُضاف صف
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/connections | MANAGE_LMS_CONNECTIONS | مسموح | 200 LmsConnectionListResponse |
POST | /api/lms/connections/{provider}/oauth-start | MANAGE_LMS_CONNECTIONS | محظور | 200 LmsOAuthStartResponse |
GET | /api/lms/connections/{provider}/oauth-callback | MANAGE_LMS_CONNECTIONS | محظور | 200 LmsConnection |
POST | /api/lms/connections/oneroster/upload | MANAGE_LMS_CONNECTIONS | محظور | 201 LmsConnection |
DELETE | /api/lms/connections/{id} | MANAGE_LMS_CONNECTIONS | محظور | 200 { ok: true } |
GET | /api/lms/{provider}/courses | MANAGE_LMS_CONNECTIONS | مسموح | 200 LmsCoursesResponse |
POST | /api/lms/{provider}/mappings | MANAGE_LMS_CONNECTIONS | محظور | 200 LmsMappingResponse |
POST | /api/lms/preview | MANAGE_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-now | MANAGE_ROSTER | محظور | 202 LmsSyncNowResponse |
GET | /api/lms/sync-runs | MANAGE_LMS_CONNECTIONS | مسموح | 200 LmsSyncRunsResponse |
للاطلاع على تعريفات المخططات الكاملة راجع مرجع واجهة برمجة التطبيقات.
أدلة ذات صلة
- إنشاء المستخدمين: إنشاء المستخدمين يدويًا وتسجيلهم في الفصول
- الفصول والقوائم: كيف تُحدِّد عضوية الفصل تفويض المعلم
- مركز الإشعارات: المكان الذي تظهر فيه تنبيهات
lms_connection_revoked