أقسام الصفحة الرئيسية
تخطيط الصفحة الرئيسية هو القائمة المرتّبة للأقسام التي يعرضها المتجر في صفحته الرئيسية. لكل قسم نوع وإعدادات. يمكن إضافة عشرة أنواع في كل قالب: category-products وfeatured وcategories وbanner وimage-with-text وrich-text وtrust-badges وtestimonials وfaq وvideo. ويقدّم قالب مبني على الأقسام مثل atlas أيضاً hero وproduct-grid. يعطي types في استجابة GET إعدادات كل نوع. كل كتابة في هذه الصفحة تظهر في المتجر بمجرد أن تُرجع استجابتها.
هذه النقاط ليست GET /v1/store/home-sections التي تقرأ مفاتيح الصفحة الرئيسية في قوالب Digital وAriana وPrestige.
قبل أن تبدأ
- تحتاج
GETإلىstore:read، وتحتاج الكتابات إلىstore:write. مفاتيح التاجر تحمل الاثنتين. - تحتاج
POSTوPATCHوDELETEإلىIdempotency-Key. وهو اختياري معPUT. انظر Idempotency. - رقم الفئة يأتي من
GET /v1/categoriesالتي تحتاج إلىproducts:read.
كائن القسم
| الحقل | النوع | ملاحظات |
|---|---|---|
id | int | ثابت ما دام القسم موجوداً. القسم الذي يعود بالتراجع يأخذ رقماً جديداً. |
type | string | إحدى قيم type المذكورة في types ضمن GET /v1/store/home-layout. |
settings | object | كل إعدادات النوع، والقيم الافتراضية مملوءة. |
is_active | bool | false تُخفي القسم عن الزبائن وتُبقيه في القائمة. |
available | bool | false عندما لا يعود قالب المتجر يملك هذا النوع. يبقى القسم كما خُزّن، ويمكن لأي كتابة أن تُبقيه. |
إعدادات category-products
| الإعداد | النوع | الافتراضي | القواعد |
|---|---|---|---|
category | رقم فئة | 0 | فئة من هذا المتجر. 0 تعني بلا فئة، وعندها لا يعرض القسم شيئاً للزبائن. رقم فئة من متجر آخر يُرجع 422 invalid_settings. |
title | text | "" | حتى 80 حرفاً، ويُزال منه HTML. الفارغ يُظهر اسم الفئة. |
count | range | 8 | من 4 إلى 12. الرقم خارج هذا المجال يُنقل إلى أقرب حدّ. |
layout | select | grid | grid أو slider. |
show_view_all | checkbox | true | رابط إلى صفحة الفئة. |
الفئة التي لا منتجات فيها لا تعرض شيئاً للزبائن كذلك. اقرأ قواعد أي نوع من settings_schema الخاص به بدل كتابتها في برنامجك: قائمة الأنواع تتبع قالب المتجر.
صيغ الإعدادات
| نوع الإعداد | القيمة المقبولة |
|---|---|
category | رقم من GET /v1/categories، أو 0 لعدم الاختيار. |
link | #anchor، أو مسار /path داخل المتجر، أو عنوان http:// أو https://، أو رابط tel: أو mailto:، حتى 500 حرف، أو "". العنوان المرسل بلا بادئته، مثل wa.me/213...، يُحفظ مع https:// في أوله. |
youtube | رابط فيديو YouTube أو معرّفه ذو 11 حرفاً. يُحفظ المعرّف. |
image | مسار صورة رُفعت من لوحة التحكم لهذا المتجر، /uploads/banners/{store_id}/...، أو "". لا يمكن رفع الصور عبر الواجهة البرمجية حالياً: يرفع التاجر الصورة أولاً من إعدادات القسم في لوحة التحكم، ثم تُرجع GET مسارها. |
متى يرى الزبائن القسم
القسم الذي لم يُملأ محتواه بعد يُخزَّن وتُرجع الكتابة 2xx، لكن الزبائن لا يرونه حتى يُملأ:
| النوع | يراه الزبائن عندما |
|---|---|
category-products | تكون category فئة من المتجر فيها منتجات. |
featured | تكون في source المختار منتجات. |
categories | تكون في المتجر فئة فيها منتجات، أو أي فئة عندما تكون show_empty بقيمة true. |
banner | تُملأ image. |
image-with-text | تُملأ image مع title أو text. |
rich-text | يُملأ title أو text. |
trust-badges | دائماً. الشارتان 1 و2 الفارغتان تُظهران سطري التوصيل والدفع عند الاستلام الافتراضيين. |
testimonials | يُملأ tN_text واحد على الأقل. |
faq | يُملأ qN واحد على الأقل مع aN الخاص به. |
video | تحمل video معرّف فيديو YouTube. |
لا تتغيّر rendered لهذا السبب: فهي تقول إن كان القالب يعرض الأقسام المخزّنة، لا إن كان قسم بعينه ظاهراً. في قالب مبني على الأقسام (atlas) لا تحلّ الأقسام المحفوظة محل الصفحة الرئيسية للقالب إلا ما دامت تتضمن قسم product-grid ظاهراً، وتبقى rendered بقيمة false حتى ذلك الحين؛ وقبل ذلك يرى الزبائن الصفحة الأصلية للقالب، ولا تعرض GET إلا الأقسام المحفوظة، لا أقسام القالب نفسه.
GET /v1/store/home-layout
الأقسام بترتيب عرضها، وأنواع الأقسام التي يمكن إضافتها في قالب المتجر، وحدّ الخطة.
المصادقة: مفتاح منصة بصلاحية store:read.
الطلب
curl 'https://api.dzbuild.app/v1/store/home-layout' \
-H "Authorization: Bearer $DZ_KEY"
الاستجابة 200
{
"data": {
"theme": "starter",
"rendered": true,
"max_sections": 25,
"cap": 25,
"version": "9c1e04b7a2d35f68",
"sections": [
{
"id": 412,
"type": "category-products",
"settings": {
"category": 57,
"title": "",
"count": 8,
"layout": "grid",
"show_view_all": true
},
"is_active": true,
"available": true
},
{
"id": 415,
"type": "category-products",
"settings": {
"category": 61,
"title": "Nos parfums",
"count": 10,
"layout": "slider",
"show_view_all": false
},
"is_active": false,
"available": true
}
],
"types": [
{
"type": "category-products",
"name": {"ar": "منتجات فئة", "fr": "Produits d'une catégorie"},
"description": {"ar": "اعرض منتجات فئة واحدة في شبكة أو شريط تمرير.", "fr": "Affichez les produits d'une catégorie en grille ou en carrousel."},
"icon": "bi-grid-3x3-gap",
"limit": 12,
"settings_schema": [
{"id": "category", "type": "category", "default": 0, "label": {"ar": "الفئة", "fr": "Catégorie"}},
{"id": "title", "type": "text", "max": 80, "default": "", "label": {"ar": "العنوان (إذا تركته فارغاً يظهر اسم الفئة)", "fr": "Titre (si vide, le nom de la catégorie s'affiche)"}},
{"id": "count", "type": "range", "min": 4, "max": 12, "default": 8, "label": {"ar": "عدد المنتجات", "fr": "Nombre de produits"}},
{"id": "layout", "type": "select", "options": ["grid", "slider"], "default": "grid", "label": {"ar": "طريقة العرض", "fr": "Affichage"}, "option_labels": {"ar": ["شبكة", "شريط تمرير"], "fr": ["Grille", "Carrousel"]}},
{"id": "show_view_all", "type": "checkbox", "default": true, "label": {"ar": "زر عرض الكل", "fr": "Lien « Voir tout »"}}
]
}
]
},
"meta": {"request_id": "8f2c1a9d4b7e6035", "api_version": "v1"}
}
| الحقل | المعنى |
|---|---|
theme | مفتاح قالب المتجر. |
rendered | false عندما لا يعرض قالب المتجر الحالي أقسام الصفحة الرئيسية. تبقى الأقسام محفوظة وتظهر من جديد مع قالب يعرضها. وفي قالب مبني على الأقسام لا تكون true إلا ما دام قسم product-grid ظاهر محفوظاً. |
max_sections | 25، أقصى عدد من الأقسام تحمله صفحة رئيسية واحدة. |
cap | عدد الأقسام الذي تسمح به خطة المتجر: 3 في الخطة المجانية أو خطة منتهية، و25 ابتداءً من Pro. |
version | بصمة التخطيط المخزَّن. أرسلها في version مع PUT لرفض تخطيط تغيّر منذ هذه القراءة. |
sections | الأقسام بترتيب عرضها، ومعها الأقسام المخفية. |
types | الأنواع التي يمكن إضافتها في هذا القالب، مع name وdescription وicon وlimit (أقصى عدد من أقسام هذا النوع في الصفحة) وsettings_schema. يعرض المثال أعلاه نوعاً واحداً منها. |
القراءة بعد الكتابة
عبر api.dzbuild.app تُخزَّن استجابة GET الناجحة 30 ثانية لكل مفتاح وسلسلة استعلام (ترويسة X-Cache: HIT|MISS تخبرك بالحالة). لذلك قد تُرجع GET المرسلة مباشرة بعد كتابة التخطيطَ السابق لها. استعمل التخطيط الموجود في استجابة الكتابة: كل كتابة تُرجع القائمة كاملة بترتيب العرض مع version الجديدة. وإن احتجت إلى قراءة جديدة، أضف سلسلة استعلام من عندك، مثل ?fresh=1727520000.
POST /v1/store/home-layout/sections
يضيف قسماً واحداً.
المصادقة: مفتاح منصة بصلاحية store:write. يتطلب Idempotency-Key.
الجسم
| الحقل | النوع | إلزامي | ملاحظات |
|---|---|---|---|
type | string | نعم | قيمة type من types. |
settings | object | لا | الإعدادات التي لا ترسلها تأخذ القيم الافتراضية للنوع. |
position | int | لا | 0 يضع القسم في الأعلى، و24 آخر مكان. بدونه يُضاف القسم في النهاية. |
الطلب
curl -X POST 'https://api.dzbuild.app/v1/store/home-layout/sections' \
-H "Authorization: Bearer $DZ_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: hs-add-sacs-1" \
-d '{"type": "category-products", "settings": {"category": 64, "layout": "slider"}, "position": 0}'
الاستجابة 201
{
"data": {
"section": {
"id": 418,
"type": "category-products",
"settings": {"category": 64, "title": "", "count": 8, "layout": "slider", "show_view_all": true},
"is_active": true,
"available": true
},
"sections": [
{"id": 418, "type": "category-products", "settings": {"category": 64, "title": "", "count": 8, "layout": "slider", "show_view_all": true}, "is_active": true, "available": true},
{"id": 412, "type": "category-products", "settings": {"category": 57, "title": "", "count": 8, "layout": "grid", "show_view_all": true}, "is_active": true, "available": true},
{"id": 415, "type": "category-products", "settings": {"category": 61, "title": "Nos parfums", "count": 10, "layout": "slider", "show_view_all": false}, "is_active": false, "available": true}
],
"version": "e27a90c4b1f36d05",
"change_id": 90231,
"rendered": true
},
"meta": {"request_id": "3b7d0e5a9c14f862", "api_version": "v1"}
}
PATCH /v1/store/home-layout/sections/{id}
يغيّر قسماً واحداً. الإعدادات التي ترسلها تُدمج فوق الإعدادات المخزّنة. أرسل settings أو is_active أو كليهما.
المصادقة: مفتاح منصة بصلاحية store:write. يتطلب Idempotency-Key.
الجسم
| الحقل | النوع | إلزامي | ملاحظات |
|---|---|---|---|
settings | object | لا | تُدمج فوق الإعدادات المخزّنة. |
is_active | bool | لا | false تُخفي القسم، وtrue تُظهره. |
replace | bool | لا | مع settings، القيمة true تُرجع كل إعداد لا ترسله إلى قيمته الافتراضية. |
الطلب
curl -X PATCH 'https://api.dzbuild.app/v1/store/home-layout/sections/415' \
-H "Authorization: Bearer $DZ_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: hs-415-show-1" \
-d '{"settings": {"count": 6}, "is_active": true}'
الاستجابة 200
للاستجابة الحقول نفسها التي لاستجابة POST: section (القسم بعد التغيير)، وsections، وversion، وchange_id، وrendered.
{
"data": {
"section": {
"id": 415,
"type": "category-products",
"settings": {"category": 61, "title": "Nos parfums", "count": 6, "layout": "slider", "show_view_all": false},
"is_active": true,
"available": true
},
"sections": [
{"id": 418, "type": "category-products", "settings": {"category": 64, "title": "", "count": 8, "layout": "slider", "show_view_all": true}, "is_active": true, "available": true},
{"id": 412, "type": "category-products", "settings": {"category": 57, "title": "", "count": 8, "layout": "grid", "show_view_all": true}, "is_active": true, "available": true},
{"id": 415, "type": "category-products", "settings": {"category": 61, "title": "Nos parfums", "count": 6, "layout": "slider", "show_view_all": false}, "is_active": true, "available": true}
],
"version": "51d8c3e06fa2b974",
"change_id": 90232,
"rendered": true
},
"meta": {"request_id": "c90a6e1f2d7b4538", "api_version": "v1"}
}
DELETE /v1/store/home-layout/sections/{id}
يحذف قسماً واحداً.
المصادقة: مفتاح منصة بصلاحية store:write. يتطلب Idempotency-Key.
الطلب
curl -X DELETE 'https://api.dzbuild.app/v1/store/home-layout/sections/412' \
-H "Authorization: Bearer $DZ_KEY" \
-H "Idempotency-Key: hs-del-412-1"
الاستجابة 200
{
"data": {
"deleted": true,
"id": 412,
"sections": [
{"id": 418, "type": "category-products", "settings": {"category": 64, "title": "", "count": 8, "layout": "slider", "show_view_all": true}, "is_active": true, "available": true},
{"id": 415, "type": "category-products", "settings": {"category": 61, "title": "Nos parfums", "count": 6, "layout": "slider", "show_view_all": false}, "is_active": true, "available": true}
],
"version": "0f6b2d9e84a1c357",
"change_id": 90233,
"rendered": true
},
"meta": {"request_id": "71e4b08c3a5d9f26", "api_version": "v1"}
}
POST /v1/store/home-layout/reorder
يحدّد ترتيب العرض. تذكر ids كل أقسام الصفحة مرة واحدة بالضبط، ومعها الأقسام المخفية.
المصادقة: مفتاح منصة بصلاحية store:write. يتطلب Idempotency-Key.
الطلب
curl -X POST 'https://api.dzbuild.app/v1/store/home-layout/reorder' \
-H "Authorization: Bearer $DZ_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: hs-order-2" \
-d '{"ids": [415, 418]}'
الاستجابة 200
تحمل الاستجابة sections بالترتيب الجديد، وversion، وchange_id، وrendered. الرقم الذي ليس في الصفحة يُرجع 404 section_not_found. والرقم الناقص أو المكرر يُرجع 422 invalid_order.
PUT /v1/store/home-layout
يستبدل التخطيط كله بالقائمة التي ترسلها، بترتيبها.
المصادقة: مفتاح منصة بصلاحية store:write. Idempotency-Key اختياري.
الجسم
| الحقل | النوع | إلزامي | ملاحظات |
|---|---|---|---|
sections | array | نعم | 25 عنصراً على الأكثر، كل عنصر {id?, type, settings?, is_active?}. القائمة الفارغة تحذف كل الأقسام. |
version | string | لا | قيمة version من آخر قراءة لك. إذا تغيّر التخطيط منذها، يُرجع النداء 409 write_conflict مع sections وversion الحاليتين ولا يكتب شيئاً. |
كيف يُقرأ كل عنصر:
- العنصر الذي يحمل
idيُبقي ذلك القسم. ويجب أن يكونtypeهو النوع الحالي للقسم. - العنصر الذي بلا
idينشئ قسماً. - كل قسم في الصفحة غير موجود في القائمة يُحذف.
settingsهو الكائن كاملاً: الإعداد الذي تتركه يعود إلى قيمته الافتراضية. أرسل الإعدادات كاملة لكل قسم تُبقيه.- قيمة
is_activeالافتراضيةtrue.
الطلب
curl -X PUT 'https://api.dzbuild.app/v1/store/home-layout' \
-H "Authorization: Bearer $DZ_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: hs-replace-7" \
-d '{
"version": "0f6b2d9e84a1c357",
"sections": [
{"id": 415, "type": "category-products", "settings": {"category": 61, "title": "Nos parfums", "count": 6, "layout": "slider", "show_view_all": false}},
{"type": "category-products", "settings": {"category": 57}}
]
}'
الاستجابة 200
تحمل الاستجابة sections وversion وchange_id وrendered. القسم 418 لم يُرسل، لذلك يُحذف. وإعادة إرسال التخطيط الذي قرأته كما هو تُرجع change_id: null.
مع Idempotency-Key، إعادة المحاولة بالمفتاح نفسه والجسم نفسه تُرجع الاستجابة المخزّنة لمدة 24 ساعة مع Idempotency-Replay: 1، والمفتاح نفسه مع جسم آخر يُرجع 422 idempotency_key_reuse. وبدون مفتاح يُنفَّذ النداء في كل مرة، وهذا آمن: إرسال القائمة نفسها مرتين يترك التخطيط نفسه.
التراجع
كل كتابة تُرجع change_id، أو null عندما لا تغيّر شيئاً. يُرجع POST /v1/changes/{change_id}/undo الصفحة الرئيسية كلها كما كانت قبل ذلك التغيير.
- يمكن التراجع عن إضافة قسم أيضاً: التراجع يزيله. في الموارد الأخرى يرفض التراجع أي تغيير أنشأ شيئاً.
- إذا تغيّرت الصفحة الرئيسية بعد ذلك التغيير، عبر الواجهة البرمجية أو من لوحة التحكم، يُرجع التراجع
409 layout_changedولا يكتب شيئاً. اقرأ التخطيط واكتب ما تريده مباشرة. - القسم الذي يعود بعد حذفه يأخذ رقماً جديداً.
- التراجع نفسه يُسجَّل تغييراً مستقلاً،
undo_change_id، ويمكنك التراجع عنه بدوره. التراجع عن تراجعٍ عن حذف يُرجع409 layout_changed، لأن القسم عاد برقم جديد. - التغييرات التي يحفظها التاجر من لوحة التحكم لا تُسجَّل، فلا يمكن التراجع عنها عبر الواجهة البرمجية.
- يعرض
GET /v1/changes?entity=store.home_layoutتغييرات تخطيط الصفحة الرئيسية، الأحدث أولاً، بصلاحيةstore:read. - لا تحمل استجابة التراجع التخطيط. اقرأه مع سلسلة استعلام من عندك، مثل
?fresh=<unix time>، حتى لا يُرجع التخزين المؤقت لمدة 30 ثانية التخطيطَ السابق للتراجع.
يحتاج التراجع إلى store:write وإلى Idempotency-Key.
curl -X POST 'https://api.dzbuild.app/v1/changes/90231/undo' \
-H "Authorization: Bearer $DZ_KEY" \
-H "Idempotency-Key: undo-90231"
{
"data": {
"undone": true,
"change_id": 90231,
"entity": "store.home_layout",
"undo_change_id": 90240
},
"meta": {"request_id": "5ad2f7c01e9b8634", "api_version": "v1"}
}
التغيير الذي تتراجع عنه مرة ثانية يُرجع 409 already_undone.
الأخطاء
| HTTP | الرمز | السبب |
|---|---|---|
| 400 | bad_request | الجسم ليس كائن JSON، أو حقل من نوع خاطئ (type غائب، أو settings ليس كائناً، أو is_active ليس قيمة منطقية، أو position خارج 0 إلى 24، أو ids ليس مصفوفة من أرقام الأقسام، أو version ليس نصاً)، أو رقم القسم في المسار ليس رقماً موجباً، أو Idempotency-Key غائب أو غير صالح مع POST أو PATCH أو DELETE. |
| 401 | unauthorized | مفتاح خاطئ أو غائب. |
| 402 | quota_exceeded | انتهت حصة الطلبات الشهرية للمتجر. انظر حدود المعدل. |
| 403 | forbidden | Missing scope: store:read أو Missing scope: store:write. |
| 403 | plan_required | الكتابة تترك أقساماً أكثر مما تسمح به الخطة. يحمل الخطأ plan وcap. |
| 404 | section_not_found | لا يوجد قسم بهذا الرقم في الصفحة. |
| 409 | write_conflict | وصلت كتابة أخرى قبلك، أو version المرسلة مع PUT ليست الحالية. عندما يحمل الخطأ sections وversion فهما التخطيط الحالي: أعد المحاولة انطلاقاً منهما. وإلا فاقرأ التخطيط وأعد المحاولة. |
| 409 | layout_changed | للتراجع فقط: تغيّرت الصفحة الرئيسية بعد هذا التغيير. |
| 409 | already_undone | للتراجع فقط: سبق التراجع عن هذا التغيير. |
| 413 | payload_too_large | الجسم أكبر من 1 ميغابايت. |
| 422 | invalid_settings | رُفضت قيمة. تذكر fields كل إعداد مرفوض في القسم، مثل settings.category. ومع PUT تذكر إعدادات أول قسم مرفوض فقط، مثل sections.2.settings.layout، وتشمل أيضاً id ليس في الصفحة (sections.N.id) وقسماً مُبقى أُرسل بنوع آخر (sections.N.type). وتذكر message المسارات أيضاً. |
| 422 | invalid_section_type | النوع غير موجود أو لا يمكن إضافته في هذا القالب. تعطي fields المسار. |
| 422 | limit_reached | أكثر من 25 قسماً، أو أكثر من limit لنوع واحد. يحمل الخطأ limit، إلا عندما ترسل PUT أكثر من 25 عنصراً. |
| 422 | invalid_order | ids في إعادة الترتيب ينقصها قسم أو تكرر قسماً. |
| 422 | no_changes | PATCH بلا settings ولا is_active. |
| 422 | idempotency_key_reuse | استُعمل Idempotency-Key نفسه مع جسم آخر. |
| 429 | rate_limited | طلبات كثيرة، ومنها أكثر من 30 كتابة على تخطيط الصفحة الرئيسية في الدقيقة للمتجر. انتظر المدة في Retry-After. |
| 429 | too_many_concurrent | أكثر من 5 كتابات على تخطيط الصفحة الرئيسية تعمل في الوقت نفسه للمتجر. أعد المحاولة بعد ثوانٍ. |
| 500 | server_error | فشل الطلب. أعد المحاولة بالـ Idempotency-Key نفسه. |
هكذا تبدو استجابة invalid_settings.
{
"error": {
"code": "invalid_settings",
"message": "Invalid value at settings.category: category not found in this store; accepted values are in settings_schema of GET /v1/store/home-layout",
"fields": [{"path": "settings.category", "code": "invalid"}]
},
"meta": {"request_id": "e4c19a0b7d2f5836", "api_version": "v1"}
}
الحدود
- 25 قسماً في الصفحة الرئيسية (
max_sections). - حدّ الخطة (
cap): 3 أقسام في الخطة المجانية أو خطة منتهية، و25 ابتداءً من Pro. الأقسام المخفية تُحسب. المتجر الذي تجاوز حدّه بعد نزول خطته يُبقي أقسامه ويستطيع تعديلها وإخفاءها وترتيبها وحذفها. الكتابة التي تترك أقساماً أكثر من الحدّ وأكثر مما كانت تُرجع403 plan_required. - لكل نوع: لكل نوع
limitفيtypes، وهو 12 لـcategory-products. - الإعدادات: 8 كيلوبايت لكل قسم بعد الترميز. النص الأطول يُقصّ عند
maxالخاص بالإعداد. - الجسم: 1 ميغابايت.
- الكتابات: 30 في الدقيقة و5 في الوقت نفسه لكل متجر، فوق حدّ المتجر في الدقيقة. انظر حدود المعدل.