۱۹. پروژهٔ سوم؛ قرارداد API تیکت و مالکیت داده
در دو پروژهٔ قبلی، بیشتر تصمیمها داخل مرورگر بودند. اکنون داده بین چند هویت آزمایشی مشترک است و مرورگر نمیتواند مرجع مالکیت باشد. هدف درس، فهم مسیر «درخواست، بررسی، تغییر و پاسخ» در API محلی Node.js است. این پروژه یک مرکز تیکت آموزشی با فایل JSON است؛ انتخاب آلیس، باب یا کارشناس آزمایشی، ورود واقعی و امن برای استفادهٔ عمومی نیست. سرور فقط روی 127.0.0.1 اجرا میشود و نباید همین نسخه را به اینترنت باز کنید.
قرارداد را پیش از رابط بخوانید
در ریشهٔ بسته npm start را اجرا و صفحهٔ /support/ را در نشانی چاپشدهٔ ترمینال باز کنید. مسیر GET /api/tickets فهرست تیکتهای مجاز را برمیگرداند. POST روی همان مسیر برای ایجاد تیکت است و فقط title و message و category را میپذیرد. عنوان ۶ تا ۱۲۰ و پیام نخست ۲۰ تا ۳۰۰۰ نویسه پس از trim لازم دارد. دسته باید یکی از مقادیر مدل باشد؛ برای تمرین از course استفاده میکنیم.
این کد را فقط در کنسول همان صفحهٔ محلی اجرا کنید. نشست آزمایشی در همان مرورگر ساخته میشود و درخواست بعدی از آن استفاده میکند. از شناسه یا اطلاعات واقعی دانشجویان استفاده نکنید.
const sessionResponse = await fetch('/api/demo-session', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ profile: 'alice' })
});
console.log(sessionResponse.status); // 200
const createdResponse = await fetch('/api/tickets', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
title: 'پرسش دربارهٔ تمرین سبد خرید',
message: 'با افزودن سومین کالا خطای موجودی دریافت میکنم؛ انتظار چیست؟',
category: 'course'
})
});
const ticket = await createdResponse.json();
console.log(createdResponse.status, ticket.owner, ticket.version);
// 201 alice 1در مرورگر، Origin درخواست هممبدأ به شکل معمول ارسال میشود. در آزمون Node باید آن را صریح بسازیم. نمونه برای اجرای کنسولی است و موفقیت را صرفاً از وجود JSON نتیجه نمیگیرد؛ کد وضعیت نیز مهم است. در برنامهٔ اصلی، تابع api خطای پاسخ ناموفق را به Error تبدیل میکند تا فرم در مسیر موفقیت پاک نشود.
مالک از نشست میآید
کلاینت اجازه ندارد owner را انتخاب کند. اگر به بدنهٔ ساخت تیکت owner:'bob' اضافه کنید، سرور به علت فیلد اضافی 400 میدهد. سرور user را از نشست فعلی پیدا و owner را از user.id میسازد. برای خواندن نیز canRead هر رکورد را بررسی میکند: مالک یا کارشناس آزمایشی مجاز است. ناشناختهبودن UUID بهتنهایی کنترل دسترسی نیست.
نمونهٔ دوم مستقل است و منطق خالص همان مدل را در Node بررسی میکند. آن را در ریشهٔ بسته با نام ownership-demo.mjs ذخیره و node ownership-demo.mjs اجرا کنید.
import { canRead, nextStatus } from './projects/support/model.mjs';
const ticket = { owner: 'alice', status: 'open' };
const alice = { id: 'alice', role: 'student' };
const bob = { id: 'bob', role: 'student' };
const staff = { id: 'staff', role: 'staff' };
console.log(canRead(ticket, alice), canRead(ticket, bob)); // true false
console.log(nextStatus(ticket, staff, 'reply')); // waiting
console.log(nextStatus(ticket, alice, 'reply')); // openاین مدل میگوید پاسخ کارشناس وضعیت را waiting، یعنی انتظار پاسخ دانشجو، میکند. پاسخ مالک وضعیت را open میکند. تیکت بسته تا بازگشایی پاسخ تازه نمیگیرد. این نامگذاری قرارداد همین پروژه است؛ آن را با معنای وضعیت در یک سامانهٔ دیگر اشتباه نگیرید. نمایش فارسی وضعیت در فایل رابط جداست تا منطق مدل به متن صفحه وابسته نباشد.
فایل داده و محدودهٔ تضمین
server.mjs داده را ابتدا در فایل موقت مینویسد و سپس جایگزین میکند. این روش برای آزمایشگاه تکفرایندی ما روشن و قابل بررسی است؛ پایگاه دادهٔ تراکنشی چندسروری نیست. نشستها در حافظهاند و با راهاندازی دوباره از بین میروند؛ تیکتها در فایل داده میمانند. بنابراین باقیماندن تیکت بعد از restart و خارجشدن از نشست دو رفتار متناقض نیستند.
فهرست، متن پیامهای همهٔ تیکتها را نمیفرستد؛ برای مشاهدهٔ رشته باید مسیر GET /api/tickets/{id} را بخوانید. این تفکیک به خوانایی قرارداد و کاهش دادهٔ غیرضروری کمک میکند. خطای 404 برای تیکت ناموجود و تیکت غیرمجاز یکسان است. از آن نتیجه نگیرید که رکورد حتماً وجود ندارد؛ فقط برای این درخواست قابل دسترس نیست.
تمرین، پاسخ راهنما و معیار تحویل
یک تیکت با آلیس بسازید، شناسهاش را یادداشت کنید و با رابط به باب تغییر هویت بدهید. باب نباید آن را در فهرست ببیند. درخواست مستقیم خواندن همان شناسه هم باید 404 بگیرد. سپس به کارشناس آزمایشی بروید و پاسخ بدهید؛ وضعیت باید waiting شود. پاسخ راهنما این است که مخفیکردن دکمه بهتنهایی کافی نیست؛ موفقیت تمرین به رد درخواست مستقیم روی سرور بستگی دارد.
گزارش تحویل باید بدنهٔ مجاز ساخت، سه هویت آزمایشی، مسیر خواندن و رفتار مالکیت را توضیح دهد. اگر owner را از localStorage میخوانید یا به دادهٔ بدنه اعتماد میکنید، مدل را اصلاح کنید. هدف یادگیری مرز اعتماد است و ادعای ساخت یک سامانهٔ احراز هویت تولیدی در این درس نداریم.
منابع رسمی برای مطالعهٔ بیشتر
https://nodejs.org/docs/latest-v24.x/api/http.html
https://cheatsheetseries.owasp.org/cheatsheets/Authorization_Cheat_Sheet.html
تألیف و مثالهای اختصاصی: تحریریه درسآوند.