منابع داده
اتصال منبع دادهٔ API
پیادهسازی API سازگار با موچت؛ قالب پاسخ، صفحهبندی، مرتبسازی، فیلتر تاریخ و احراز هویت، همراه با مثالهای Laravel، Express و FastAPI.
با منبع دادهٔ API میتوانید اطلاعات سامانه، کاتالوگ محصولات یا پایگاه دانش خود را به موچت متصل کنید. این راهنما قرارداد آدرسی را که باید ارائه کنید، روش بررسی آن توسط موچت و مثالهای پیادهسازی با PHP Laravel، Node.js Express و Python FastAPI را توضیح میدهد.
موچت این API را برای دریافت و همگامسازی دانش میخواند. برای افزودن اتصال در داشبورد، بخش اتصال از طریق API را ببینید.
الزامهای API
آدرس API
یک آدرس کامل HTTP یا HTTPS ارائه کنید که از سرورهای موچت قابل دسترسی باشد. برای ارسال اطلاعات احراز هویت، از HTTPS استفاده کنید.
| نمونهٔ آدرس قابل دسترسی | آدرس محلی که موچت به آن دسترسی ندارد |
|---|---|
https://api.example.com/items | http://localhost:3000/api/items |
https://example.com/api/products | http://127.0.0.1/api/items |
https://data.example.org/endpoint | http://192.168.1.100/api/items |
اگر API در شبکهٔ داخلی قرار دارد، پیش از اتصال باید دسترسی سرورهای موچت به آن را فراهم کنید. آدرس نهایی را بدهید که بدون تغییر مسیر، مستقیماً JSON برگرداند. بررسی اولیهٔ اتصال، تغییر مسیر را دنبال میکند، اما دریافت داده هنگام همگامسازی این کار را نمیکند. صفحهٔ HTML ورود یا بررسی مرورگر، پاسخ سازگار محسوب نمیشود.
روش درخواست و کش
API باید درخواست GET را با این ویژگیها بپذیرد:
- پارامترهای صفحهبندی، مرتبسازی و فیلتر تاریخ را از query string دریافت کند.
- پارامتر
_nonceرا که موچت برای جلوگیری از دریافت پاسخ کششده اضافه میکند، بپذیرد. - تا وقتی داده تغییر نکرده است، برای فیلترها و ترتیب یکسان، نتیجهٔ یکسان برگرداند.
- در مهلت اتصال، پاسخ موفق JSON بدهد؛ مهلت فعلی اعتبارسنجی، ۳۰ ثانیه برای هر درخواست است.
موچت علاوه بر _nonce، هدرهای جلوگیری از کش را هم میفرستد. API شما میتواند مقدار _nonce را نادیده بگیرد، اما باید آن را بپذیرد و وارد فیلتر دادهها نکند.
قالب پاسخ
یک شیء JSON شامل total_count و items برگردانید:
{
"total_count": 100,
"items": [
{
"id": "123",
"title": "Product Name",
"date_modified": "2026-10-20T15:30:45.000Z",
"description": "Detailed product description...",
"url": "https://example.com/products/123",
"price": 29.99,
"regular_price": 39.99,
"sale_price": 29.99,
"stock_status": "instock",
"currency": "USD",
"variations": [
{ "id": "456", "title": "Size L", "price": 32.99 }
]
}
]
}آرایهٔ items شامل صفحهٔ درخواستشده است. مقدار total_count باید عدد صحیح و برابر با تعداد همهٔ آیتمهای منطبق با فیلتر، پیش از اعمال skip و take باشد. مثلاً صفحهای با یک آیتم میتواند total_count: 100 داشته باشد. اگر فیلتر نتیجهای نداشت، پاسخ موفق با {"total_count": 0, "items": []} برگردانید.
الزامهای فیلدها
فیلدهای اجباری
هر آیتم باید این فیلدها را داشته باشد:
| فیلد | نوع | شرط | نمونه |
|---|---|---|---|
id | رشته یا عدد صحیح | شناسهٔ یکتا و ثابت آیتم | "123" یا 456 |
title | رشته | عنوان غیرخالی | "Product Name" |
date_modified | رشته | زمان دقیق UTC با میلیثانیه | "2026-10-20T15:30:45.000Z" |
شناسهٔ id هر آیتم را بین همگامسازیها ثابت نگه دارید. با هر تغییر محتوا، از جمله تغییر قیمت، موجودی یا تنوع محصول، date_modified را بهروز کنید. این مقدار باید زمان واقعی تغییر باشد؛ با هر درخواست GET، زمان تازهای برای همان محتوای قبلی نسازید.
قالب دقیق تاریخ
قالب موردنیاز YYYY-MM-DDTHH:mm:ss.SSSZ است. حرف پایانی Z به معنی UTC است و بخش اعشاری دقیقاً سه رقم دارد.
| مقدار | نتیجه |
|---|---|
2026-10-20T15:30:45.000Z | معتبر |
2026-10-20T15:30:45Z | نامعتبر؛ میلیثانیه ندارد |
2026-10-20 15:30:45 | نامعتبر؛ جداکننده و منطقهٔ زمانی ندارد |
2026-10-20T15:30:45+00:00 | نامعتبر؛ از میلیثانیه و پسوند Z استفاده کنید |
2026-10-20T15:30:45.000000Z | نامعتبر؛ بهجای سه رقم اعشار، شش رقم دارد |
پیش از قالببندی، زمان را به UTC تبدیل کنید؛ اضافهکردن Z به ساعت محلی، آن را به UTC تبدیل نمیکند. زمان تغییر در دیتابیس را با دقت میلیثانیه نگه دارید تا تاریخ خروجی و فیلتر دقیق تاریخ با هم سازگار باشند.
فیلدهای اختیاری
محتوایی را که موچت باید یاد بگیرد، در فیلدهای همان آیتم قرار دهید. فیلدهای زیر برای اطلاعات محصول و پایگاه دانش کاربرد دارند؛ موچت اطلاعاتی مانند قیمت، موجودی، واحد پول و تنوعها را در متادیتای آیتم هم نگه میدارد.
| فیلد | نوع | توضیح |
|---|---|---|
description | رشته | محتوای کامل آیتم |
url | رشته | لینک عمومی مرتبط با آیتم |
price | عدد | قیمت فعلی |
regular_price | عدد | قیمت پیش از تخفیف |
sale_price | عدد | قیمت تخفیفخورده، در صورت وجود |
stock_status | رشته | مثلاً instock، outofstock، onbackorder یا مقدار اختصاصی شما |
currency | رشته | مثلاً USD، EUR، IRR، ریال یا تومان |
variations | آرایه | تنوعهای محصول با جزئیات جداگانه |
| فیلدهای اختصاصی | هر مقدار معتبر JSON | اطلاعات تکمیلی محصول، مقاله یا پرسشوپاسخ |
مقدار فیلدهای اختصاصی میتواند رشته، عدد، مقدار بولی، شیء، آرایه یا null باشد. این فیلدها در محتوای منبع آیتم قرار میگیرند؛ افزودن آنها بهتنهایی کنترل تازهای برای فیلتر جستجو در داشبورد نمیسازد. دریافت و همگامسازی به فعالبودن قابلیت منبع API و ظرفیت کافی پایگاه دانش در حساب شما نیاز دارد.
تنوعهای محصول
هر تنوع میتواند فیلدهای اختصاصی خودش را داشته باشد. چند نمونهٔ رایج:
| فیلد | نوع | توضیح |
|---|---|---|
id | رشته یا عدد صحیح | شناسهٔ یکتای تنوع |
title | رشته | نام نمایشی تنوع |
price | عدد | قیمت فعلی تنوع |
regular_price | عدد | قیمت عادی |
sale_price | عدد | قیمت تخفیفخورده |
stock_status | رشته | وضعیت موجودی |
stock | عدد | تعداد موجود |
| فیلدهای اختصاصی | هر مقدار معتبر JSON | رنگ، اندازه، کد کالا، وزن یا ویژگیهای دیگر |
مثال اول: یک محصول فروشگاهی
{
"id": "123",
"title": "Wireless Headphones",
"date_modified": "2024-03-20T15:30:45.000Z",
"description": "High-quality wireless headphones...",
"price": 99.99,
"regular_price": 129.99,
"sale_price": 99.99,
"stock_status": "instock",
"currency": "USD",
"category": "electronics",
"brand": "TechBrand",
"sku": "WH-123",
"weight": 0.3,
"variations": [
{
"id": "123-black-large",
"title": "Wireless Headphones - Black Large",
"price": 99.99,
"regular_price": 129.99,
"sale_price": 99.99,
"stock_status": "instock",
"stock": 25,
"color": "black",
"size": "large",
"sku": "WH-123-BL",
"weight": 0.35
},
{
"id": "123-white-medium",
"title": "Wireless Headphones - White Medium",
"price": 104.99,
"regular_price": 129.99,
"sale_price": 104.99,
"stock_status": "outofstock",
"stock": 0,
"color": "white",
"size": "medium",
"sku": "WH-123-WM",
"expected_restock": "2024-04-15"
},
{
"id": "123-red-small",
"title": "Wireless Headphones - Red Small",
"price": 94.99,
"regular_price": 129.99,
"sale_price": 94.99,
"stock_status": "onbackorder",
"stock": 5,
"color": "red",
"size": "small",
"sku": "WH-123-RS",
"pre_order": true
}
]
}مثال دوم: یک آیتم پرسشوپاسخ
{
"id": 1,
"title": "What is Python?",
"date_modified": "2024-03-15T12:00:00.000Z",
"question": "What is Python?",
"answer": "Python is a high-level, interpreted programming language known for its simplicity and readability."
}هر مثال، یک آیتم است. آنها را داخل آرایهٔ items پاسخ برگردانید و total_count را برای کل مجموعهٔ منطبق با فیلتر حساب کنید.
پارامترهای درخواست
API باید پارامترهای زیر را پیادهسازی کند. ممکن است بعضی از آنها در درخواست نباشند؛ مانند مثالهای پیادهسازی، مقدار پیشفرض در نظر بگیرید.
صفحهبندی
| پارامتر | نوع | رفتار موردنیاز | نمونه |
|---|---|---|---|
take | عدد صحیح مثبت | حداکثر تعداد آیتمهای پاسخ | ?take=30 |
skip | عدد صحیح نامنفی | تعداد آیتمهایی که از ابتدای نتیجهٔ مرتبشده و فیلترشده رد میشوند | ?skip=60 |
موچت در اعتبارسنجی و همگامسازی فعلی، در هر درخواست تا ۳۱ آیتم میخواهد. محدودکردن اندازهٔ صفحه به ۳۰ کافی نیست. مثالهای این صفحه تا ۱۰۰ آیتم را میپذیرند.
مرتبسازی
| پارامتر | نوع | رفتار موردنیاز | نمونه |
|---|---|---|---|
order | رشته | اعمال ترتیب ASC یا DESC روی فیلدهای انتخابشده | ?order=ASC |
order_by | رشته | مرتبسازی با نام فیلدها، جداشده با ویرگول | ?order_by=date_modified,id |
هر دو فیلد date_modified و id را پشتیبانی کنید. اگر زمان تغییر چند آیتم یکسان باشد، id ترتیب بین آنها را ثابت نگه میدارد. مرتبسازی باید پیش از اعمال skip و take انجام شود.
نام عمومی فیلدها را به ستون متناظر در دیتابیس نگاشت کنید؛ مثلاً date_modified از updated_at در SQL و id از _id در MongoDB گرفته شود. فقط فیلدهای مجاز مرتبسازی را بپذیرید.
فیلتر تاریخ
| پارامتر | نوع | رفتار موردنیاز | نمونه |
|---|---|---|---|
modified_after | رشتهٔ زمان UTC | فقط آیتمهایی با زمان تغییر بزرگتر از ورودی را برگرداند | ?modified_after=2026-10-20T15:30:45.000Z |
شرط باید date_modified > modified_after باشد؛ یعنی بزرگتر، نه بزرگتر یا مساوی. بعد از این فیلتر، total_count را دوباره حساب کنید. اگر آیتم تازهتری وجود ندارد، آرایهٔ items خالی و total_count: 0 برگردانید.
هر دو شکل ورودی UTC، با میلیثانیه و بدون آن، را بپذیرید: 2026-10-20T15:30:45.000Z و 2026-10-20T15:30:45Z. بررسی اولیهٔ اتصال، میلیثانیه میفرستد؛ همگامسازی ممکن است زمان را با دقت ثانیه بفرستد. تاریخ آیتمها در پاسخ همچنان باید قالب دقیق میلیثانیه داشته باشد.
همگامسازی بر اساس id، آیتم تازه میسازد و آیتم موجود را بهروز میکند. اگر date_modified آیتم موجود عوض نشده باشد، آن را رد میکند؛ حتی اگر فیلد دیگری تغییر کرده باشد. حذف آیتم از پاسخ API، منبع واردشدهٔ قبلی را خودکار حذف نمیکند؛ در صورت نیاز، آن منبع را از داشبورد حذف کنید.
جلوگیری از کش
| پارامتر | نوع | رفتار | نمونه |
|---|---|---|---|
_nonce | رشته | موچت خودکار اضافه میکند؛ آن را بپذیرید، بدون اینکه آیتمها را با مقدارش فیلتر کنید | ?_nonce=1792497600000 |
روشهای احراز هویت
هنگام افزودن منبع API در پایگاه دانش، روش احراز هویت را تنظیم کنید. این اطلاعات مربوط به API خود شما هستند و موچت هنگام دریافت داده آنها را میفرستد.
بدون احراز هویت
اگر API عمومی است، بدون احراز هویت را انتخاب کنید. موچت اطلاعات API Key یا Bearer به درخواست اضافه نمیکند.
کلید API
مقدار کلید و نام هدر آن، مانند X-API-Key، را وارد کنید. اگر API شما از Authorization بهعنوان هدر اختصاصی کلید استفاده میکند، مقدار کامل مورد انتظارش را وارد کنید.
GET /api/items?take=10&skip=0&_nonce=1792497600000 HTTP/1.1
Host: api.example.com
X-API-Key: your-api-key-hereتوکن Bearer
Bearer Token را انتخاب کنید و فقط مقدار توکن را وارد کنید. موچت پیشوند Bearer را در هدر Authorization اضافه میکند.
GET /api/items?take=10&skip=0&_nonce=1792497600000 HTTP/1.1
Host: api.example.com
Authorization: Bearer your-token-hereاگر احراز هویت فعال است، API باید درخواست بدون اطلاعات معتبر یا با اطلاعات اشتباه را رد کند. کوکی ورود مرورگر شما از طریق این اتصال به API فرستاده نمیشود.
بررسی سازگاری API
هنگام ثبت منبع، موچت پیش از ساخت آن، قالب پاسخ، صفحهبندی، مرتبسازی و فیلتر تاریخ را بررسی میکند.
ساختار اولیه
- پاسخ باید یک شیء JSON با
total_countعدد صحیح و آرایهٔitemsباشد. - هر آیتم باید یک شیء JSON باشد و فیلدهای
id،titleوdate_modifiedشرایط گفتهشده را داشته باشند. - درخواست
take=1باید آیتمی برگرداند تا ساختار آن بررسی شود.
دقت صفحهبندی
GET /api/items?take=2&order=ASC&order_by=date_modified,id
GET /api/items?take=1&skip=0&order=ASC&order_by=date_modified,id
GET /api/items?take=1&skip=1&order=ASC&order_by=date_modified,idدرخواست دوم باید اولین آیتم پاسخ اول را برگرداند و درخواست سوم، دومین آیتم همان پاسخ را. برای همهٔ مقدارهای skip، صفحهٔ اول را تکرار نکنید.
برای مجموعههایی با حداقل ۳۱ آیتم، موچت میتواند همپوشانی صفحهها را هم بررسی کند: با ترتیب یکسان، آخرین آیتم take=31&skip=0 باید با اولین آیتم take=31&skip=30 برابر باشد.
عملکرد مرتبسازی
GET /api/items?take=2&order=ASC&order_by=date_modified,id
GET /api/items?take=2&order=DESC&order_by=date_modified,id
GET /api/items?take=1&order=ASC&order_by=date_modified,idمرتبسازی صعودی و نزولی و استفاده از چند فیلد باید کار کند. ترتیب درخواست صفحهها را یکسان نگه دارید تا وقتی داده تغییر نکرده است، آیتمها بین صفحهها جابهجا نشوند.
دقت فیلتر تاریخ
موچت نتیجهٔ درخواستها را با این مرزهای زمانی مقایسه میکند:
| مقدار فیلتر | نتیجهٔ مورد انتظار |
|---|---|
| یک دقیقه پیش از زمان قدیمیترین آیتم | قدیمیترین آیتم همچنان در ترتیب صعودی، اولین نتیجه باشد |
| دقیقاً برابر با زمان قدیمیترین آیتم | آیتمی با همان زمان یا زمان قدیمیتر برنگردد |
| زمان فعلی | اگر همهٔ زمانهای تغییر در گذشتهاند، آیتمی برنگردد |
زمان تغییر را مطابق زمان واقعی هر ویرایش ثبت کنید. تاریخ آینده میتواند باعث ردشدن بررسی آخر شود.
مثالهای پیادهسازی
این مثالها قرارداد یکسان قالب پاسخ، فیلتر، مرتبسازی و صفحهبندی را پیاده میکنند. نمونهٔ Laravel از مدل SQL موجودِ Item استفاده میکند، نمونهٔ Express از مدل موجودِ Item در Mongoose و نمونهٔ FastAPI از فایل JSON همین صفحه.
PHP Laravel
این کنترلر را در app/Http/Controllers/DatasourceController.php قرار دهید. مدل Item باید فیلدهای title، description، price و updated_at داشته باشد. فیلد updated_at را با date cast در Eloquent و با زمان UTC و دقت میلیثانیه نگه دارید.
<?php
namespace App\Http\Controllers;
use App\Models\Item;
use Carbon\CarbonImmutable;
use Illuminate\Http\Request;
use Illuminate\Validation\ValidationException;
class DatasourceController extends Controller
{
public function index(Request $request)
{
$params = $request->validate([
'take' => ['sometimes', 'integer', 'min:1', 'max:100'],
'skip' => ['sometimes', 'integer', 'min:0'],
'order' => ['sometimes', 'in:ASC,DESC'],
'order_by' => ['sometimes', 'string'],
'modified_after' => [
'sometimes', 'date',
'regex:/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/',
],
]);
$columns = ['date_modified' => 'updated_at', 'id' => 'id'];
$fields = array_map('trim', explode(',', $params['order_by'] ?? 'date_modified,id'));
foreach ($fields as $field) {
if (!isset($columns[$field])) {
throw ValidationException::withMessages(['order_by' => 'Unsupported field']);
}
}
if (!in_array('id', $fields, true)) {
$fields[] = 'id';
}
$query = Item::query();
if (isset($params['modified_after'])) {
$since = CarbonImmutable::parse($params['modified_after'])->utc();
$query->where('updated_at', '>', $since->format('Y-m-d H:i:s.v'));
}
$total = (clone $query)->count();
foreach (array_unique($fields) as $field) {
$query->orderBy($columns[$field], $params['order'] ?? 'ASC');
}
$items = $query->offset((int) ($params['skip'] ?? 0))
->limit((int) ($params['take'] ?? 30))->get();
return response()->json([
'total_count' => $total,
'items' => $items->map(fn ($item) => [
'id' => (string) $item->id,
'title' => $item->title,
'date_modified' => $item->updated_at->copy()->utc()
->format('Y-m-d\TH:i:s.v\Z'),
'description' => $item->description,
'price' => $item->price === null ? null : (float) $item->price,
])->values(),
]);
}
}مسیر را در routes/api.php ثبت کنید:
use App\Http\Controllers\DatasourceController;
use Illuminate\Support\Facades\Route;
Route::get('/items', [DatasourceController::class, 'index']);با پیشوند پیشفرض API در Laravel، آدرس /api/items در دسترس قرار میگیرد. اگر API خصوصی است، middleware احراز هویت API Key یا Bearer خود را روی همین مسیر اعمال کنید. فیلدهای دیگر محصول را هم در صورت نیاز به خروجی هر آیتم اضافه کنید. جزئیات متدهای مرتبسازی و صفحهبندی در مستندات Query Builder لاراول آمده است.
Node.js Express
این router از Mongoose استفاده میکند. مدل موجودِ Item باید updated_at از نوع Date داشته باشد که هنگام تغییر محتوا بهروز شود؛ همچنین فیلدهای title، description و price. شناسهٔ عمومی id از تبدیل _id در MongoDB به رشته به دست میآید.
import express from "express";
import Item from "./models/item.js";
const router = express.Router();
const columns = { date_modified: "updated_at", id: "_id" };
router.get("/api/items", async (req, res, next) => {
const {
take = "30", skip = "0", order = "ASC",
order_by = "date_modified,id", modified_after
} = req.query;
const integers = [take, skip].every((value) =>
typeof value === "string" && /^\d+$/.test(value)
);
const limit = Number(take);
const offset = Number(skip);
if (!integers || !Number.isSafeInteger(limit) || limit < 1 || limit > 100
|| !Number.isSafeInteger(offset) || offset < 0) {
return res.status(400).json({ error: "Invalid take or skip" });
}
if (!["ASC", "DESC"].includes(order) || typeof order_by !== "string") {
return res.status(400).json({ error: "Invalid ordering" });
}
const fields = [...new Set(order_by.split(",").map((field) => field.trim()))];
if (fields.some((field) => !Object.hasOwn(columns, field))) {
return res.status(400).json({ error: "Unsupported order_by field" });
}
if (!fields.includes("id")) fields.push("id");
const filter = {};
if (modified_after !== undefined) {
const since = typeof modified_after === "string" ? new Date(modified_after) : null;
if (!since || Number.isNaN(since.getTime())) {
return res.status(400).json({ error: "Invalid modified_after timestamp" });
}
const canonical = since.toISOString();
if (![canonical, canonical.replace(".000Z", "Z")].includes(modified_after)) {
return res.status(400).json({ error: "Use UTC ISO 8601 with seconds or milliseconds" });
}
filter.updated_at = { $gt: since };
}
const sort = Object.fromEntries(fields.map((field) => [
columns[field], order === "DESC" ? -1 : 1
]));
try {
const [total_count, items] = await Promise.all([
Item.countDocuments(filter),
Item.find(filter).sort(sort).skip(offset).limit(limit).lean()
]);
return res.json({
total_count,
items: items.map((item) => ({
id: String(item._id),
title: item.title,
date_modified: item.updated_at.toISOString(),
description: item.description,
price: item.price
}))
});
} catch (error) {
next(error);
}
});
export default router;router را به برنامهٔ Express متصل کنید:
import datasourceRouter from "./datasource-router.js";
app.use(datasourceRouter);برای API خصوصی، middleware کلید API یا Bearer را پیش از این router اجرا کنید. فیلدهای دیگر محصول را در نگاشت خروجی اضافه کنید. متدهای sort، skip، limit و countDocuments در مرجع Query در Mongoose توضیح داده شدهاند.
Python FastAPI
این کد را در main.py ذخیره کنید. مسیر API، فایل items.json را از همان پوشه میخواند، فیلدهای اختصاصی آیتم را نگه میدارد و شناسهها را بهصورت رشته برمیگرداند.
import json
import os
from datetime import datetime, timezone
from pathlib import Path
from secrets import compare_digest
from typing import Literal
from fastapi import Depends, FastAPI, HTTPException, Query, Security
from fastapi.security import APIKeyHeader, HTTPAuthorizationCredentials, HTTPBearer
app = FastAPI()
api_key_header = APIKeyHeader(name="X-API-Key", auto_error=False)
bearer_auth = HTTPBearer(auto_error=False)
def verify_api_key(api_key: str | None = Security(api_key_header)):
expected = os.environ.get("DATASOURCE_API_KEY")
if not expected or not api_key or not compare_digest(api_key, expected):
raise HTTPException(status_code=401, detail="Invalid API key")
def verify_bearer_token(
credentials: HTTPAuthorizationCredentials | None = Security(bearer_auth),
):
expected = os.environ.get("DATASOURCE_BEARER_TOKEN")
if not expected or not credentials or not compare_digest(credentials.credentials, expected):
raise HTTPException(status_code=401, detail="Invalid bearer token")
def parse_timestamp(value: str) -> datetime:
try:
parsed = datetime.fromisoformat(value.replace("Z", "+00:00"))
canonical = parsed.astimezone(timezone.utc).isoformat(timespec="milliseconds")
canonical = canonical.replace("+00:00", "Z")
if parsed.tzinfo is None or value not in {canonical, canonical.replace(".000Z", "Z")}:
raise ValueError
return parsed
except (TypeError, ValueError):
raise HTTPException(status_code=422, detail="Use UTC ISO 8601 with seconds or milliseconds")
def load_json_file() -> list[dict]:
path = Path(__file__).with_name("items.json")
items = json.loads(path.read_text(encoding="utf-8"))
return [{**item, "id": str(item["id"])} for item in items]
@app.get("/api/items")
def get_items(
take: int = Query(default=30, ge=1, le=100),
skip: int = Query(default=0, ge=0),
order_by: str = "date_modified,id",
order: Literal["ASC", "DESC"] = "ASC",
modified_after: str | None = None,
# API key authentication: uncomment the next line.
# api_key: None = Depends(verify_api_key),
# Bearer authentication: uncomment the next line instead.
# bearer: None = Depends(verify_bearer_token),
):
fields = list(dict.fromkeys(field.strip() for field in order_by.split(",")))
if any(field not in {"date_modified", "id"} for field in fields):
raise HTTPException(status_code=422, detail="Unsupported order_by field")
if "id" not in fields:
fields.append("id")
items = load_json_file()
if modified_after is not None:
since = parse_timestamp(modified_after)
items = [item for item in items if parse_timestamp(item["date_modified"]) > since]
for field in reversed(fields):
items.sort(
key=lambda item: parse_timestamp(item[field]) if field == "date_modified" else item[field],
reverse=order == "DESC",
)
total_count = len(items)
return {"total_count": total_count, "items": items[skip:skip + take]}روش احراز هویت را مطابق تنظیم منبع داده انتخاب کنید:
- بدون احراز هویت: هر دو خط dependency را بهصورت کامنت نگه دارید.
- API Key: متغیر محیطی
DATASOURCE_API_KEYرا تنظیم کنید و خط dependency مربوط بهapi_keyرا از حالت کامنت خارج کنید. نام هدر را در موچتX-API-Keyقرار دهید. - Bearer Token: متغیر
DATASOURCE_BEARER_TOKENرا تنظیم کنید و بهجای آن، dependency مربوط بهbearerرا فعال کنید.
dependency فعال، درخواست بدون توکن، توکن اشتباه و تنظیمنبودن کلید روی سرور را رد میکند. ابزارهای دریافت هدر در مرجع امنیت FastAPI توضیح داده شدهاند.
دادهٔ نمونهٔ زیر را در items.json قرار دهید. دو آیتم زمان یکسان دارند تا بتوانید مرتبسازی دوم با id را هم بررسی کنید:
[
{
"id": "1",
"title": "Wireless Headphones",
"date_modified": "2026-10-20T09:30:00.000Z",
"description": "Wireless headphones with a microphone.",
"price": 99.99,
"currency": "USD"
},
{
"id": "2",
"title": "USB-C Cable",
"date_modified": "2026-10-20T09:30:00.000Z",
"description": "A one-meter USB-C charging cable.",
"price": 9.99,
"currency": "USD"
},
{
"id": "3",
"title": "What is Python?",
"date_modified": "2026-10-21T12:00:00.000Z",
"question": "What is Python?",
"answer": "Python is a high-level programming language."
}
]FastAPI را نصب و نمونه را اجرا کنید:
pip install "fastapi[standard]"
fastapi dev main.pyبرای آزمایش محلی، آدرس http://127.0.0.1:8000/api/items را باز کنید. پیش از اتصال موچت، API را روی آدرس قابل دسترسی از بیرون منتشر کنید. نمونههای بیشتر در مخزن مثالهای موچت قرار دارند.
خطاهای رایج و راهحلها
خطای قالب تاریخ
مشکل: بررسی اتصال، مقدار date_modified را رد میکند.
راهحل: مقدار date_modified را با قالب دقیق YYYY-MM-DDTHH:mm:ss.SSSZ برگردانید. زمان را به UTC تبدیل کنید، سه رقم اعشار نگه دارید و با تغییر آیتم، تاریخ را بهروز کنید. ورودی modified_after را هم با میلیثانیه و هم با دقت ثانیه بپذیرید. دقت مقایسهٔ فیلتر و زمان خروجی باید یکسان باشد.
خطای صفحهبندی
مشکل: یک آیتم در همهٔ صفحهها تکرار میشود، بعضی آیتمها جا میافتند یا بررسی صفحهٔ بزرگتر شکست میخورد.
راهحل: ابتدا فیلتر تاریخ، سپس مرتبسازی date_modified,id و در پایان skip و take را اعمال کنید. اندازهٔ صفحه باید حداقل تا ۳۱ آیتم را بپذیرد. total_count را پیش از صفحهبندی حساب کنید و برای زمانهای برابر، ترتیب ثابت داشته باشید.
خطای احراز هویت
مشکل: API پاسخ 401 یا 403 میدهد.
راهحل: نام و مقدار هدر را با API خود تطبیق دهید. برای Bearer، فقط توکن را در موچت وارد کنید. سرور باید اطلاعات اشتباه را رد کند و توکن معتبر تنظیمشده را بدون نیاز به نشست مرورگر بپذیرد.
مجموعهٔ خالی یا تعداد نادرست
مشکل: در بررسی اولیه آیتمی برای اعتبارسنجی وجود ندارد یا با تغییر اندازهٔ صفحه، total_count عوض میشود.
راهحل: هنگام اتصال حداقل دو آیتم در دسترس داشته باشید. تعداد همهٔ رکوردهای منطبق با فیلتر تاریخ را پیش از صفحهبندی حساب کنید. برای فیلتر بدون نتیجه، پاسخ موفق با items: [] و total_count: 0 برگردانید.
خطای اتصال یا پاسخ
مشکل: موچت به آدرس دسترسی ندارد، درخواست طول میکشد یا پاسخ HTML است.
راهحل: آدرس عمومی، DNS، دسترسی شبکه و احراز هویت را بررسی کنید. آدرس نهایی را بدون تغییر مسیر وارد کنید. مسیر GET باید مستقیماً JSON بدهد، همهٔ پارامترها از جمله _nonce را بپذیرد و در مهلت اتصال پاسخ بدهد. نتیجه را بیرون از نشست مرورگری که وارد حساب شده است هم آزمایش کنید.
آزمایش API
پیش از اتصال منبع، با curl یا Postman دریافت اولیه، صفحهبندی، هر دو جهت مرتبسازی، فیلتر تاریخ و ترکیب پارامترها را بررسی کنید. آدرس و تاریخ نمونه را با مقدارهای خودتان جایگزین کنید.
curl --get 'https://api.example.com/items' --data-urlencode 'take=5'
curl --get 'https://api.example.com/items' --data-urlencode 'take=5' \
--data-urlencode 'skip=5' --data-urlencode 'order_by=date_modified,id'curl --get 'https://api.example.com/items' --data-urlencode 'order=DESC' \
--data-urlencode 'order_by=date_modified,id'
curl --get 'https://api.example.com/items' \
--data-urlencode 'modified_after=2026-10-20T09:30:00.000Z'
curl --get 'https://api.example.com/items' \
--data-urlencode 'modified_after=2026-10-20T09:30:00Z'curl --get 'https://api.example.com/items' \
--header 'X-API-Key: your-api-key-here' \
--data-urlencode 'take=31' --data-urlencode 'skip=0' \
--data-urlencode 'order=ASC' --data-urlencode 'order_by=date_modified,id' \
--data-urlencode 'modified_after=2026-10-20T09:30:00.000Z' \
--data-urlencode '_nonce=1792497600000'برای Bearer، هدر کلید API را با Authorization: Bearer your-token-here جایگزین کنید. آزمون مرزهای زمانی را هم انجام دهید و در حالت احراز هویت فعال، ردشدن درخواست بدون اطلاعات معتبر یا با اطلاعات اشتباه را بررسی کنید.
پس از موفقیت آزمونها، آدرس و اطلاعات احراز هویت را در منابع داده وارد کنید. بعد از تغییر محتوای اصلی، date_modified را بهروز کنید و با همگامسازی مجدد، تغییرات را دریافت کنید.