المحطّة ٤: التحقّق من المدخلات ومنع تعارض الحجز
المتطلّب الحرج ينفّذ: تحقّقٌ صارمٌ ومنعٌ للحجز المزدوج. ⏱ نحو 30 دقيقة.
- تتحقّق من المدخلات وترفض الناقص برمز 422.
- تمنع تعارض الحجز برمز 409.
- ترتّب بوّابات التحقّق قبل تعديل البيانات.
- تبني الإلغاء الذي يعيد الموعد متاحًا.
١ثلاث بوّاباتٍ قبل أيّ حجز
الحجز ليس مجرّد «اكتب الاسم في الموعد». قبل أن نلمس البيانات نمرّ على ثلاث بوّابات، وترتيبها مهمّ:
- موجود؟ إن لم يوجد الموعد ← 404.
- مدخلٌ صالح؟ إن لم يُرسَل اسم المريض ← 422 (تحقّق المدخلات).
- متاح؟ إن كان محجوزًا أصلًا ← 409 (منع التعارض).
فقط بعد اجتياز الثلاث نحجز. هذا هو منع التعارض الذي حدّدناه متطلّبًا حرجًا في المحطّة الأولى — ونراه هنا سطرًا واحدًا: if (a.bookedBy !== null) return 409.
٢الحجز يعمل
جرّب السيناريو كاملًا: نحجز الموعد المتاح 1، ثم نحاول حجزه ثانيةً (تعارض)، ثم نحجز بلا اسمٍ (مدخلٌ ناقص)، ثم نحجز موعدًا غير موجود:
const express = require('express');const app = express();app.use(express.json()); let appts = [ { id: 1, doctor: 'د. منى', time: '09:00', bookedBy: null }, { id: 2, doctor: 'د. منى', time: '09:30', bookedBy: 'سارة' }, { id: 3, doctor: 'د. أحمد', time: '10:00', bookedBy: null },];const find = (id) => appts.find(a => a.id === Number(id));const notFound = (res) => res.status(404).json({ error: { code: 'NOT_FOUND', message: 'لا يوجد موعد' } }); app.post('/api/appointments/:id/book', (req, res) => { const a = find(req.params.id); if (!a) return notFound(res); if (!req.body.patient || !req.body.patient.trim()) return res.status(422).json({ error: { code: 'VALIDATION', message: 'اسم المريض مطلوب' } }); if (a.bookedBy !== null) return res.status(409).json({ error: { code: 'CONFLICT', message: 'الموعد محجوز' } }); a.bookedBy = req.body.patient.trim(); res.json({ data: a });}); app.listen(3000);
٣الإلغاء يفتح الموعد من جديد
الإلغاء بسيط: نعيد bookedBy إلى null، فيعود الموعد متاحًا للجميع:
app.post('/api/appointments/:id/cancel', (req, res) => { const a = find(req.params.id); if (!a) return notFound(res); a.bookedBy = null; res.json({ data: a });});
توقّع وتحقّق
ترتيب البوّابات الصحيح قبل الحجز:
ما رمز حجز موعدٍ محجوزٍ أصلًا؟
ما رمز الحجز بلا اسم مريض؟
كيف يعود الموعد متاحًا عند الإلغاء؟
الخلاصة
- قبل الحجز: موجود؟
(404)← مدخلٌ صالح؟(422)← متاح؟(409)← ثم احجز. - منع التعارض سطرٌ واحد:
if (a.bookedBy !== null) return 409. - 422 لمدخلٍ غير صالح، و409 لحالةٍ تمنع — تمييزٌ يفيد الواجهة.
- الإلغاء يعيد
bookedByإلىnullفيعود الموعد للجميع. - لا تعدّل البيانات قبل اجتياز كل البوّابات.
تحدٍّ برمجي: الحجز والإلغاء ومنع التعارض
برمجيأكمِل قلب النظام: مسار حجزٍ يمرّ بالبوّابات الثلاث (وجود، تحقّق، تعارض)، ومسار إلغاءٍ يفتح الموعد. هذا تنفيذ متطلّب «لا يُحجَز موعدٌ مرّتين».
POST /:id/bookبجسم{"patient": …}: 404 إن لم يوجد، 422 إن لا اسم، 409 إن محجوز، وإلّا 200 مع{"data": …}وbookedBy= الاسم.POST /:id/cancel: يعيدbookedByإلىnullويرد 200، أو 404 إن لم يوجد.
👀 اعرض الاستجابات المطلوبة
POST /api/appointments/1/book{"patient": "سعود"}POST /api/appointments/1/book{"patient": "خالد"}POST /api/appointments/3/book{}POST /api/appointments/99/book{"patient": "نورة"}POST /api/appointments/1/cancel{}POST /api/appointments/1/book{"patient": "خالد"}
- الخادم يعمل ويرد على كل الطلبات بلا أخطاء غير معالَجة
- الحجز المتاح ← 200
- … ويُسجّل اسم الحاجز
- حجز المحجوز ← 409
- … برمز CONFLICT
- الحجز بلا اسم ← 422
- … برمز VALIDATION
- حجز غير الموجود ← 404
- الإلغاء يفتح الموعد
(bookedBy=null) - بعد الإلغاء يُحجَز من جديد
💡 تلميح 1
(404) ثم المدخل (422) ثم التعارض (409) — قبل أيّ تعديل.💡 تلميح 2
.trim() وتحقّق a.bookedBy !== null للتعارض.حاول بنفسك أولًا؛ للمسألة أكثر من حلّ صحيح.
const express = require('express');const app = express();app.use(express.json()); let appts = [ { id: 1, doctor: 'د. منى', time: '09:00', bookedBy: null }, { id: 2, doctor: 'د. منى', time: '09:30', bookedBy: 'سارة' }, { id: 3, doctor: 'د. أحمد', time: '10:00', bookedBy: null },];const find = (id) => appts.find(a => a.id === Number(id));const notFound = (res) => res.status(404).json({ error: { code: 'NOT_FOUND', message: 'لا يوجد موعد' } }); app.post('/api/appointments/:id/book', (req, res) => { const a = find(req.params.id); if (!a) return notFound(res); if (!req.body.patient || !req.body.patient.trim()) return res.status(422).json({ error: { code: 'VALIDATION', message: 'اسم المريض مطلوب' } }); if (a.bookedBy !== null) return res.status(409).json({ error: { code: 'CONFLICT', message: 'الموعد محجوز' } }); a.bookedBy = req.body.patient.trim(); res.json({ data: a });}); app.post('/api/appointments/:id/cancel', (req, res) => { const a = find(req.params.id); if (!a) return notFound(res); a.bookedBy = null; res.json({ data: a });}); app.listen(3000);