flwo / API_REFERENCE.md
moustafa7's picture
Initial commit
24727a3
|
Raw History Blame Contribute Delete
31.7 kB
# DesignFlow — API Reference الكامل
> Base URL: `https://api.designflow.com/api`
> كل الـ responses بتكون `Content-Type: application/json`
> كل الـ timestamps بتكون ISO 8601: `"2025-06-01T10:30:00.000Z"`
> الأخطاء دايماً بتكون: `{ "error": "...", "code": "..." }`
---
## Authentication
الـ API بيستخدم **JWT Bearer Tokens** للـ authentication.
```
Authorization: Bearer <access_token>
```
- **Access Token:** عمره 15 دقيقة — يتبعت في الـ response body
- **Refresh Token:** عمره 7 أيام — يتخزن في `HttpOnly Cookie` تلقائياً (مش بتشوفه في الـ JavaScript)
- الـ cookie اسمه `refresh_token` وبيتبعت تلقائياً مع كل request لـ `/api/auth/refresh`
---
## Response Format الموحد
### Success Response
```json
{
"data": { ... }, // أو الـ object مباشرة حسب الـ endpoint
"message": "..." // اختياري
}
```
### Error Response
```json
{
"error": "رسالة خطأ واضحة",
"code": "ERROR_CODE_IN_CAPS",
"details": { ... } // موجود فقط في الـ validation errors
}
```
### Validation Error Response
```json
{
"error": "بيانات غير صحيحة",
"code": "VALIDATION_ERROR",
"details": {
"email": ["بريد إلكتروني غير صحيح"],
"password": ["كلمة المرور 8 أحرف على الأقل"]
}
}
```
### Error Codes المحتملة
| Code | HTTP | المعنى |
|------|------|--------|
| `VALIDATION_ERROR` | 400 | بيانات مدخلة غير صحيحة |
| `INVALID_CREDENTIALS` | 401 | بيانات تسجيل الدخول غلط |
| `NO_TOKEN` | 401 | لم يتم إرسال access token |
| `TOKEN_REVOKED` | 401 | الـ token اتعمله logout |
| `INVALID_REFRESH_TOKEN` | 401 | Refresh token منتهي أو غير صحيح |
| `REFRESH_TOKEN_REUSE` | 401 | محاولة إعادة استخدام refresh token قديم |
| `UNAUTHORIZED` | 401 | غير مسجل دخول |
| `FORBIDDEN` | 403 | ليس لديك صلاحية |
| `ACCOUNT_SUSPENDED` | 403 | الحساب موقوف |
| `NOT_FOUND` | 404 | العنصر غير موجود |
| `TEMPLATE_NOT_FOUND` | 404 | القالب غير موجود |
| `ORDER_NOT_FOUND` | 404 | الطلب غير موجود |
| `DUPLICATE_ENTRY` | 409 | البريد أو الهاتف مستخدم بالفعل |
| `RATE_LIMIT_EXCEEDED` | 429 | عدد كبير من الطلبات |
| `TOO_MANY_LOGIN_ATTEMPTS` | 429 | محاولات دخول كثيرة |
| `INTERNAL_ERROR` | 500 | خطأ داخلي في الخادم |
---
## Pagination
الـ endpoints التي تدعم الـ pagination بتقبل:
| Query Param | Default | Description |
|-------------|---------|-------------|
| `page` | 1 | رقم الصفحة |
| `limit` | 10 أو 12 | عدد العناصر في الصفحة |
وبترجع:
```json
{
"data": [...],
"total": 45,
"page": 1,
"totalPages": 5
}
```
---
# AUTH ENDPOINTS
---
## `POST /auth/register`
تسجيل عميل جديد.
**Rate Limit:** 3 requests/hour per IP
**Request:** `multipart/form-data` (بسبب رفع الصورة)
| Field | Type | Required | Validation |
|-------|------|----------|------------|
| `firstName` | string | ✅ | 2-50 حرف، حروف فقط |
| `lastName` | string | ✅ | 2-50 حرف، حروف فقط |
| `phone` | string | ✅ | format: `+201234567890` |
| `email` | string | ✅ | valid email |
| `password` | string | ✅ | 8+ chars، uppercase + رقم + رمز خاص |
| `confirmPassword` | string | ✅ | يطابق `password` |
| `avatar` | file | ❌ | JPEG/PNG/WebP، max 5MB |
**Response `201 Created`:**
```json
{
"user": {
"id": "clx1234abcd",
"firstName": "Ahmed",
"lastName": "Mohamed",
"email": "ahmed@example.com",
"phone": "+201234567890",
"avatarUrl": "https://res.cloudinary.com/.../avatar.jpg",
"role": "CLIENT",
"createdAt": "2025-06-01T10:00:00.000Z"
},
"accessToken": "eyJhbGciOiJIUzI1NiJ9..."
}
```
*+ Sets `refresh_token` HttpOnly Cookie*
**Errors:**
- `400 VALIDATION_ERROR` — بيانات غير صحيحة
- `409 DUPLICATE_ENTRY` — البريد أو الهاتف موجود
- `429 TOO_MANY_REGISTRATIONS` — تجاوز حد التسجيل
---
## `POST /auth/login`
تسجيل الدخول.
**Rate Limit:** 5 requests/15min per IP (فاشلين فقط)
**Request:** `application/json`
```json
{
"email": "ahmed@example.com",
"password": "MyPass@123"
}
```
**Response `200 OK`:**
```json
{
"user": {
"id": "clx1234abcd",
"firstName": "Ahmed",
"lastName": "Mohamed",
"email": "ahmed@example.com",
"role": "CLIENT",
"avatarUrl": "https://..."
},
"accessToken": "eyJhbGciOiJIUzI1NiJ9..."
}
```
*+ Sets `refresh_token` HttpOnly Cookie*
**Errors:**
- `400 VALIDATION_ERROR`
- `401 INVALID_CREDENTIALS` — نفس الـ message للـ user غير الموجود وكلمة المرور الغلط (security)
- `403 ACCOUNT_SUSPENDED`
- `429 TOO_MANY_LOGIN_ATTEMPTS`
---
## `POST /auth/refresh`
تجديد الـ access token باستخدام الـ refresh token cookie.
**Request:** لا يحتاج body — الـ cookie يتبعت تلقائياً
**Response `200 OK`:**
```json
{
"user": { ... },
"accessToken": "eyJhbGciOiJIUzI1NiJ9..."
}
```
*+ Sets new `refresh_token` HttpOnly Cookie (rotation)*
*+ Deletes old refresh token from DB*
**Errors:**
- `401 NO_REFRESH_TOKEN` — مفيش cookie
- `401 INVALID_REFRESH_TOKEN` — منتهي أو غير صحيح
- `401 REFRESH_TOKEN_REUSE` — إعادة استخدام token قديم (security breach — يُعمل revoke لكل tokens)
---
## `POST /auth/logout`
**Requires:** `Authorization: Bearer <token>`
تسجيل الخروج — بيحذف الـ refresh token وبيـblacklist الـ access token.
**Response `200 OK`:**
```json
{
"message": "تم تسجيل الخروج بنجاح"
}
```
*+ Clears `refresh_token` cookie*
*+ Blacklists current access token in Redis*
---
## `GET /auth/me`
**Requires:** Authentication
بيانات المستخدم الحالي.
**Response `200 OK`:**
```json
{
"id": "clx1234abcd",
"firstName": "Ahmed",
"lastName": "Mohamed",
"email": "ahmed@example.com",
"phone": "+201234567890",
"role": "CLIENT",
"avatarUrl": "https://..."
}
```
---
# USER ENDPOINTS
---
## `GET /users/profile`
**Requires:** Authentication
**Response `200 OK`:**
```json
{
"id": "clx1234abcd",
"firstName": "Ahmed",
"lastName": "Mohamed",
"email": "ahmed@example.com",
"phone": "+201234567890",
"avatarUrl": "https://...",
"createdAt": "2025-06-01T10:00:00.000Z",
"_count": {
"orders": 5
}
}
```
---
## `PUT /users/profile`
**Requires:** Authentication
تعديل الاسم والهاتف.
**Request:** `application/json`
```json
{
"firstName": "Ahmed",
"lastName": "Ali",
"phone": "+201234567891"
}
```
**Response `200 OK`:** نفس شكل `GET /users/profile`
**Errors:**
- `400 VALIDATION_ERROR`
- `409 DUPLICATE_ENTRY` — رقم الهاتف موجود عند شخص آخر
---
## `PUT /users/profile/avatar`
**Requires:** Authentication
**Request:** `multipart/form-data`
| Field | Type | Required | Validation |
|-------|------|----------|------------|
| `avatar` | file | ✅ | JPEG/PNG/WebP، max 5MB |
**Response `200 OK`:**
```json
{
"avatarUrl": "https://res.cloudinary.com/.../new-avatar.jpg"
}
```
---
## `PUT /users/password`
**Requires:** Authentication
**Request:** `application/json`
```json
{
"currentPassword": "OldPass@123",
"newPassword": "NewPass@456"
}
```
**Response `200 OK`:**
```json
{
"message": "تم تغيير كلمة المرور بنجاح"
}
```
**Errors:**
- `400 VALIDATION_ERROR`
- `401 INVALID_CREDENTIALS` — كلمة المرور الحالية غلط
---
# TEMPLATES ENDPOINTS
---
## `GET /templates`
قائمة القوالب المنشورة. **لا يحتاج authentication.**
**Query Parameters:**
| Param | Type | Default | Description |
|-------|------|---------|-------------|
| `page` | number | 1 | رقم الصفحة |
| `limit` | number | 12 | عدد القوالب في الصفحة (max: 50) |
| `category` | string | — | فلتر بالفئة |
| `search` | string | — | بحث في العنوان والوصف |
| `sort` | string | `newest` | `newest` \| `price_asc` \| `price_desc` |
**Response `200 OK`:**
```json
{
"templates": [
{
"id": "clx5678efgh",
"title": "E-Commerce Pro",
"description": "قالب احترافي للمتاجر الإلكترونية...",
"category": "تجارة",
"price": "149.00",
"previewUrl": "https://res.cloudinary.com/.../preview.jpg",
"tags": ["متجر", "عربي", "RTL"],
"defaultColors": {
"primary": "#2563EB",
"secondary": "#F8FAFC",
"accent": "#7C3AED",
"text": "#0F172A"
}
}
],
"total": 24,
"page": 1,
"totalPages": 2
}
```
> **Note:** `components` مش موجودة في القائمة — موجودة في الـ detail endpoint فقط.
**Cached:** ✅ Redis — 5 دقائق
---
## `GET /templates/:id`
**لا يحتاج authentication.**
**Response `200 OK`:**
```json
{
"id": "clx5678efgh",
"title": "E-Commerce Pro",
"description": "قالب احترافي...",
"category": "تجارة",
"price": "149.00",
"previewUrl": "https://res.cloudinary.com/.../preview.jpg",
"demoUrl": "https://demo.designflow.com/ecom-pro",
"tags": ["متجر", "عربي", "RTL"],
"defaultColors": {
"primary": "#2563EB",
"secondary": "#F8FAFC",
"accent": "#7C3AED",
"text": "#0F172A"
},
"components": {
"sections": [
{
"id": "hero",
"label": "القسم الرئيسي",
"draggable": true,
"colorBindings": {
"background": "primary",
"heading": "text",
"button": "accent"
}
},
{
"id": "features",
"label": "المميزات",
"draggable": true,
"colorBindings": { "background": "secondary" }
}
]
}
}
```
**Errors:**
- `404 TEMPLATE_NOT_FOUND` — غير موجود أو غير منشور
---
# ORDERS ENDPOINTS
---
## `POST /orders`
**Requires:** Authentication (CLIENT)
إنشاء طلب جديد بعد الشراء والتخصيص.
**Request:** `application/json`
```json
{
"templateId": "clx5678efgh",
"customization": {
"colorTokens": {
"primary": "#E11D48",
"secondary": "#FFF1F2",
"accent": "#9333EA",
"text": "#0F172A"
},
"sections": [
{ "id": "hero", "label": "القسم الرئيسي", "draggable": true },
{ "id": "features", "label": "المميزات", "draggable": true },
{ "id": "footer", "label": "التذييل", "draggable": true }
]
},
"notes": "عايز اللون الأحمر في الـ hero button بالظبط"
}
```
| Field | Required | Validation |
|-------|----------|------------|
| `templateId` | ✅ | يجب أن يكون موجوداً ومنشوراً |
| `customization` | ✅ | object يحتوي على `colorTokens` و `sections` |
| `customization.colorTokens` | ✅ | object بأربع keys: primary, secondary, accent, text |
| `customization.colorTokens.*` | ✅ | valid hex color `#RRGGBB` |
| `customization.sections` | ✅ | array من الأقسام |
| `notes` | ❌ | string, max 500 chars |
**Response `201 Created`:**
```json
{
"id": "clxorder123",
"orderNumber": "ORD-1717200000-A3B2C1",
"userId": "clx1234abcd",
"templateId": "clx5678efgh",
"status": "PENDING",
"customization": { ... },
"totalAmount": "149.00",
"notes": "عايز اللون...",
"createdAt": "2025-06-01T10:30:00.000Z",
"template": {
"title": "E-Commerce Pro",
"previewUrl": "https://..."
}
}
```
**Errors:**
- `400 VALIDATION_ERROR`
- `401 UNAUTHORIZED`
- `404 TEMPLATE_NOT_FOUND`
---
## `GET /orders`
**Requires:** Authentication (CLIENT)
طلبات العميل الحالي.
**Query Parameters:**
| Param | Type | Description |
|-------|------|-------------|
| `page` | number | Default: 1 |
| `limit` | number | Default: 10 |
| `status` | string | `PENDING` \| `ACCEPTED` \| `IN_PROGRESS` \| `COMPLETED` \| `CANCELLED` |
**Response `200 OK`:**
```json
{
"orders": [
{
"id": "clxorder123",
"orderNumber": "ORD-1717200000-A3B2C1",
"status": "PENDING",
"totalAmount": "149.00",
"createdAt": "2025-06-01T10:30:00.000Z",
"template": {
"title": "E-Commerce Pro",
"previewUrl": "https://...",
"category": "تجارة"
},
"conversation": {
"id": "clxconv456",
"isOpen": true
}
}
],
"total": 5,
"page": 1,
"totalPages": 1
}
```
> `conversation` بيكون `null` لو مفيش محادثة مفتوحة.
---
## `GET /orders/:id`
**Requires:** Authentication (CLIENT — يشوف طلباته بس)
**Response `200 OK`:**
```json
{
"id": "clxorder123",
"orderNumber": "ORD-1717200000-A3B2C1",
"status": "ACCEPTED",
"totalAmount": "149.00",
"notes": "...",
"createdAt": "2025-06-01T10:30:00.000Z",
"updatedAt": "2025-06-02T08:00:00.000Z",
"customization": {
"colorTokens": { "primary": "#E11D48", ... },
"sections": [ ... ]
},
"template": {
"id": "clx5678efgh",
"title": "E-Commerce Pro",
"category": "تجارة",
"previewUrl": "https://..."
},
"conversation": {
"id": "clxconv456",
"isOpen": true
}
}
```
**Errors:**
- `403 FORBIDDEN` — لو العميل بيحاول يشوف طلب شخص آخر
- `404 ORDER_NOT_FOUND`
---
# CHAT ENDPOINTS
---
## `GET /chat/conversations`
**Requires:** Authentication (CLIENT)
محادثات العميل الحالي (بس المفتوحة).
**Response `200 OK`:**
```json
{
"conversations": [
{
"id": "clxconv456",
"isOpen": true,
"unreadCount": 2,
"lastMessage": {
"content": "سيتم البدء في التنفيذ خلال 24 ساعة",
"createdAt": "2025-06-02T09:00:00.000Z",
"senderRole": "ADMIN"
},
"order": {
"id": "clxorder123",
"orderNumber": "ORD-1717200000-A3B2C1",
"template": {
"title": "E-Commerce Pro",
"previewUrl": "https://..."
}
}
}
]
}
```
---
## `GET /chat/conversations/:id/messages`
**Requires:** Authentication (CLIENT — يشوف محادثاته بس)
**Query Parameters:**
| Param | Type | Description |
|-------|------|-------------|
| `cursor` | string | ID آخر رسالة شوفتها (للـ pagination) |
| `limit` | number | Default: 20 |
**Response `200 OK`:**
```json
{
"messages": [
{
"id": "clxmsg789",
"content": "مرحباً! تم قبول طلبك وسيتواصل معك فريقنا قريباً 🎉",
"isRead": true,
"createdAt": "2025-06-02T08:00:00.000Z",
"sender": {
"firstName": "Support",
"lastName": "Team",
"avatarUrl": "https://...",
"role": "ADMIN"
}
},
{
"id": "clxmsg790",
"content": "شكراً، متى سيتم البدء؟",
"isRead": true,
"createdAt": "2025-06-02T08:05:00.000Z",
"sender": {
"firstName": "Ahmed",
"lastName": "Mohamed",
"avatarUrl": "https://...",
"role": "CLIENT"
}
}
],
"nextCursor": "clxmsg770"
}
```
> `nextCursor` بيكون `null` لو مفيش رسائل قديمة أكثر.
**Errors:**
- `403 FORBIDDEN` — محاولة الوصول لمحادثة شخص آخر
- `404 CONVERSATION_NOT_FOUND`
---
# LANDING PAGE ENDPOINTS
---
## `GET /landing`
**لا يحتاج authentication.**
محتوى الـ landing page الكامل.
**Response `200 OK`:**
```json
{
"hero": {
"headline": "قوالب جاهزة تُطلقك في دقائق لا أيام",
"subheadline": "اكتشف مئات القوالب الاحترافية...",
"ctaPrimaryText": "استعرض القوالب",
"ctaSecondaryText":"شاهد كيف يعمل",
"trustBadges": ["100+ قالب", "تخصيص كامل", "دعم فوري"]
},
"features": [
{ "icon": "paint", "title": "تخصيص بالسحب", "description": "..." },
{ "icon": "zap", "title": "تسليم سريع", "description": "..." }
],
"howItWorks": [
{ "step": 1, "title": "اختر قالبك", "description": "..." },
{ "step": 2, "title": "خصص ألوانه", "description": "..." },
{ "step": 3, "title": "احصل على موقعك", "description": "..." }
],
"testimonials": [
{ "name": "محمد أحمد", "role": "صاحب متجر", "text": "...", "avatar": "https://..." }
],
"footer": {
"companyDescription": "منصة تصميم احترافية...",
"socialLinks": { "facebook": "https://...", "twitter": "https://..." },
"email": "support@designflow.com"
}
}
```
**Cached:** ✅ Redis — 10 دقائق
---
# ADMIN ENDPOINTS
> كل الـ admin endpoints تحت `/api/admin`
> تحتاج: `Authorization: Bearer <admin_token>` + role: `ADMIN` أو `SUPPORT`
---
## `GET /admin/stats`
**Requires:** ADMIN
نظرة عامة على المنصة.
**Response `200 OK`:**
```json
{
"totalOrders": 45,
"pendingOrders": 8,
"totalClients": 120,
"totalRevenue": "9500.00",
"monthlyRevenue": "2300.00",
"recentOrders": [
{
"id": "clxorder123",
"orderNumber": "ORD-1717200000-A3B2C1",
"status": "PENDING",
"totalAmount": "149.00",
"createdAt": "2025-06-01T10:30:00.000Z",
"user": {
"firstName": "Ahmed",
"lastName": "Mohamed",
"avatarUrl": "https://..."
},
"template": { "title": "E-Commerce Pro" }
}
],
"ordersPerMonth": [
{ "month": "يناير", "count": 5, "revenue": "650.00" },
{ "month": "فبراير","count": 8, "revenue": "1100.00" },
{ "month": "مارس", "count": 12, "revenue": "1600.00" }
]
}
```
---
## `GET /admin/orders`
**Requires:** ADMIN or SUPPORT
**Query Parameters:**
| Param | Type | Description |
|-------|------|-------------|
| `page` | number | Default: 1 |
| `limit` | number | Default: 20 |
| `status` | string | فلتر بالحالة |
| `search` | string | بحث باسم العميل أو رقم الطلب |
| `from` | string | تاريخ البداية ISO date |
| `to` | string | تاريخ النهاية ISO date |
**Response `200 OK`:**
```json
{
"orders": [
{
"id": "clxorder123",
"orderNumber": "ORD-1717200000-A3B2C1",
"status": "PENDING",
"totalAmount": "149.00",
"notes": "...",
"createdAt": "2025-06-01T10:30:00.000Z",
"user": {
"id": "clx1234abcd",
"firstName": "Ahmed",
"lastName": "Mohamed",
"email": "ahmed@example.com",
"phone": "+201234567890",
"avatarUrl": "https://..."
},
"template": {
"title": "E-Commerce Pro",
"previewUrl": "https://...",
"category": "تجارة"
},
"conversation": {
"id": "clxconv456",
"isOpen": true
}
}
],
"total": 45,
"page": 1,
"totalPages": 3
}
```
---
## `GET /admin/orders/:id`
**Requires:** ADMIN or SUPPORT
**Response `200 OK`:**
```json
{
"id": "clxorder123",
"orderNumber": "ORD-1717200000-A3B2C1",
"status": "ACCEPTED",
"totalAmount": "149.00",
"notes": "...",
"createdAt": "2025-06-01T10:30:00.000Z",
"updatedAt": "2025-06-02T08:00:00.000Z",
"customization": {
"colorTokens": { "primary": "#E11D48", "secondary": "#FFF1F2", "accent": "#9333EA", "text": "#0F172A" },
"sections": [ { "id": "hero", "label": "القسم الرئيسي" }, ... ]
},
"user": {
"id": "clx1234abcd",
"firstName": "Ahmed",
"lastName": "Mohamed",
"email": "ahmed@example.com",
"phone": "+201234567890",
"avatarUrl": "https://...",
"_count": { "orders": 3 }
},
"template": {
"id": "clx5678efgh",
"title": "E-Commerce Pro",
"category": "تجارة",
"price": "149.00",
"previewUrl": "https://..."
},
"conversation": {
"id": "clxconv456",
"isOpen": true,
"createdAt": "2025-06-02T08:00:00.000Z"
}
}
```
---
## `PATCH /admin/orders/:id/status`
**Requires:** ADMIN or SUPPORT
**Request:** `application/json`
```json
{
"status": "ACCEPTED"
}
```
| Value | Description |
|-------|-------------|
| `PENDING` | قيد الانتظار |
| `ACCEPTED` | مقبول (بيعمل auto-conversation تلقائياً) |
| `IN_PROGRESS` | جاري التنفيذ |
| `COMPLETED` | مكتمل |
| `CANCELLED` | ملغي |
**Response `200 OK`:**
```json
{
"id": "clxorder123",
"status": "ACCEPTED",
"updatedAt": "2025-06-02T08:00:00.000Z",
"conversation": {
"id": "clxconv456",
"isOpen": true
}
}
```
> **Note:** لما الحالة تبقى `ACCEPTED` → بيتعمل conversation تلقائياً (لو مش موجودة) + رسالة ترحيبية تلقائية للعميل.
---
## `POST /admin/orders/:id/conversation`
**Requires:** ADMIN or SUPPORT
فتح محادثة مع العميل يدوياً (من غير تغيير الحالة).
**Request:** `application/json` — لا يحتاج body
**Response `201 Created` (جديدة) أو `200 OK` (موجودة):**
```json
{
"id": "clxconv456",
"orderId": "clxorder123",
"isOpen": true,
"createdAt": "2025-06-02T08:00:00.000Z",
"messages": [
{
"id": "clxmsg789",
"content": "مرحباً! تم قبول طلبك وسيتواصل معك فريقنا قريباً 🎉",
"createdAt": "2025-06-02T08:00:00.000Z",
"sender": { "firstName": "Admin", "role": "ADMIN" }
}
]
}
```
---
## `GET /admin/templates`
**Requires:** ADMIN
جميع القوالب (منشورة وغير منشورة).
**Response `200 OK`:**
```json
{
"templates": [
{
"id": "clx5678efgh",
"title": "E-Commerce Pro",
"category": "تجارة",
"price": "149.00",
"isPublished": true,
"previewUrl": "https://...",
"tags": ["متجر", "RTL"],
"createdAt": "2025-05-01T10:00:00.000Z",
"_count": { "orders": 12 }
}
],
"total": 15
}
```
---
## `POST /admin/templates`
**Requires:** ADMIN
**Request:** `multipart/form-data`
| Field | Type | Required | Validation |
|-------|------|----------|------------|
| `title` | string | ✅ | 3-100 chars |
| `description` | string | ✅ | 10-2000 chars |
| `category` | string | ✅ | |
| `price` | number | ✅ | > 0 |
| `preview` | file | ✅ | JPEG/PNG/WebP, max 10MB |
| `demoUrl` | string | ❌ | valid URL |
| `tags` | string (JSON array) | ❌ | `'["tag1","tag2"]'` |
| `defaultColors` | string (JSON) | ✅ | `'{"primary":"#...","secondary":"#...","accent":"#...","text":"#..."}'` |
| `components` | string (JSON) | ✅ | `'{"sections":[...]}'` |
| `isPublished` | boolean | ❌ | Default: false |
**Response `201 Created`:**
```json
{
"id": "clxnewtemplate",
"title": "New Template",
"isPublished": false,
"createdAt": "2025-06-01T10:00:00.000Z"
}
```
---
## `PUT /admin/templates/:id`
**Requires:** ADMIN
**Request:** `multipart/form-data` (نفس حقول POST، كلها اختيارية)
**Response `200 OK`:** بيانات القالب المحدثة
> **Note:** بعد التحديث بيتعمل cache invalidation تلقائياً لهذا القالب وقائمة القوالب.
---
## `DELETE /admin/templates/:id`
**Requires:** ADMIN
**Response `200 OK`:**
```json
{
"message": "تم حذف القالب بنجاح"
}
```
**Errors:**
- `400 TEMPLATE_HAS_ORDERS` — لا يمكن حذف قالب عنده طلبات
---
## `PATCH /admin/templates/:id/publish`
**Requires:** ADMIN
نشر/إخفاء قالب.
**Request:** `application/json`
```json
{
"isPublished": true
}
```
**Response `200 OK`:**
```json
{
"id": "clx5678efgh",
"isPublished": true,
"updatedAt": "2025-06-01T10:00:00.000Z"
}
```
---
## `GET /admin/landing`
**Requires:** ADMIN
محتوى الـ landing page كاملاً.
**Response:** نفس شكل `GET /landing` — راجع الـ Landing endpoints.
---
## `PUT /admin/landing/:section`
**Requires:** ADMIN
تعديل section معين في الـ landing page.
**Path Params:**
| Section | Description |
|---------|-------------|
| `hero` | الـ Hero section |
| `features` | المميزات |
| `how_it_works` | كيف يعمل |
| `testimonials` | آراء العملاء |
| `footer` | التذييل |
**Request:** `application/json` — بيانات الـ section (flexible JSON)
```json
{
"headline": "قوالب جاهزة تُطلقك في دقائق",
"subheadline": "وصف جديد...",
"ctaPrimaryText": "ابدأ الآن",
"trustBadges": ["200+ قالب", "تخصيص كامل", "دعم 24/7"]
}
```
**Response `200 OK`:**
```json
{
"section": "hero",
"content": { ... },
"updatedAt": "2025-06-01T10:00:00.000Z"
}
```
> **Note:** بعد التحديث بيتعمل Redis cache invalidation فوراً.
---
## `GET /admin/conversations`
**Requires:** ADMIN or SUPPORT
كل المحادثات.
**Query Parameters:**
| Param | Type | Description |
|-------|------|-------------|
| `page` | number | Default: 1 |
| `limit` | number | Default: 20 |
| `isOpen` | boolean | فلتر بالحالة |
**Response `200 OK`:**
```json
{
"conversations": [
{
"id": "clxconv456",
"isOpen": true,
"createdAt": "2025-06-02T08:00:00.000Z",
"unreadCount": 3,
"lastMessage": {
"content": "متى سيتم البدء؟",
"createdAt": "2025-06-02T09:00:00.000Z",
"senderRole": "CLIENT"
},
"order": {
"orderNumber": "ORD-1717200000-A3B2C1",
"template": { "title": "E-Commerce Pro" },
"user": {
"firstName": "Ahmed",
"lastName": "Mohamed",
"avatarUrl": "https://..."
}
}
}
],
"total": 8
}
```
---
## `GET /admin/users`
**Requires:** ADMIN
**Query Parameters:** `page`, `limit`, `search` (بالاسم أو البريد)
**Response `200 OK`:**
```json
{
"users": [
{
"id": "clx1234abcd",
"firstName": "Ahmed",
"lastName": "Mohamed",
"email": "ahmed@example.com",
"phone": "+201234567890",
"avatarUrl": "https://...",
"isActive": true,
"createdAt": "2025-05-01T10:00:00.000Z",
"_count": { "orders": 3 }
}
],
"total": 120,
"page": 1,
"totalPages": 12
}
```
---
# SOCKET.IO EVENTS
> Connection URL: `wss://api.designflow.com`
> Auth: `{ auth: { token: "<access_token>" } }` في الـ handshake
## Client → Server Events
### `join_conversation`
الانضمام لـ room محادثة معينة.
```javascript
socket.emit('join_conversation', 'clxconv456')
```
### `send_message`
إرسال رسالة.
```javascript
socket.emit('send_message', {
conversationId: 'clxconv456',
content: 'مرحباً، متى سيتم البدء؟'
})
```
| Field | Validation |
|-------|-----------|
| `conversationId` | مطلوب، يجب أن يكون لديك صلاحية |
| `content` | مطلوب، max 2000 chars |
### `mark_read`
تعليم الرسائل كمقروءة.
```javascript
socket.emit('mark_read', { conversationId: 'clxconv456' })
```
---
## Server → Client Events
### `new_message`
رسالة جديدة وصلت في الـ room.
```javascript
socket.on('new_message', (message) => {
// message shape:
// {
// id: "clxmsg789",
// conversationId: "clxconv456",
// content: "...",
// createdAt: "2025-06-02T09:00:00.000Z",
// sender: { firstName, lastName, avatarUrl, role }
// }
})
```
### `message_notification`
إشعار برسالة جديدة (للـ user حتى لو مش في الـ room).
```javascript
socket.on('message_notification', (data) => {
// data: { conversationId, senderName, preview }
})
```
### `messages_read`
الطرف الآخر قرأ الرسائل.
```javascript
socket.on('messages_read', (data) => {
// data: { conversationId, readBy: "user-id" }
})
```
### `joined_conversation`
تأكيد الانضمام للـ room.
```javascript
socket.on('joined_conversation', (data) => {
// data: { conversationId }
})
```
### `error`
خطأ في أي event.
```javascript
socket.on('error', (data) => {
// data: { message: "..." }
})
```
---
## `GET /health`
**لا يحتاج authentication.**
**Response `200 OK`:**
```json
{
"status": "ok",
"timestamp": "2025-06-01T10:00:00.000Z"
}
```
---
## Frontend Integration Examples
### Axios Instance مع Auto Refresh
```javascript
// lib/axios.js
import axios from 'axios'
const api = axios.create({
baseURL: import.meta.env.VITE_API_URL,
withCredentials: true, // مهم للـ refresh token cookie
})
api.interceptors.request.use((config) => {
const token = useAuthStore.getState().accessToken
if (token) config.headers.Authorization = `Bearer ${token}`
return config
})
// Auto-refresh on 401
let isRefreshing = false
let queue = []
api.interceptors.response.use(null, async (error) => {
const original = error.config
if (error.response?.status === 401 && !original._retry) {
if (isRefreshing) {
return new Promise((res, rej) => queue.push({ res, rej }))
.then(token => { original.headers.Authorization = `Bearer ${token}`; return api(original) })
}
original._retry = true
isRefreshing = true
try {
const { data } = await api.post('/auth/refresh')
useAuthStore.getState().setTokens(data.user, data.accessToken)
queue.forEach(p => p.res(data.accessToken))
queue = []
isRefreshing = false
original.headers.Authorization = `Bearer ${data.accessToken}`
return api(original)
} catch {
queue.forEach(p => p.rej(error))
queue = []
isRefreshing = false
useAuthStore.getState().logout()
return Promise.reject(error)
}
}
return Promise.reject(error)
})
```
### Socket.IO Client
```javascript
// features/chat/socketClient.js
import { io } from 'socket.io-client'
import { useAuthStore } from '../../store/authStore'
let socket = null
export function getSocket() {
if (!socket) {
socket = io(import.meta.env.VITE_API_URL, {
auth: { token: useAuthStore.getState().accessToken },
transports: ['websocket'],
autoConnect: false,
})
}
return socket
}
export function connectSocket() {
const s = getSocket()
// Update token before connecting (قد يكون اتجدد)
s.auth.token = useAuthStore.getState().accessToken
if (!s.connected) s.connect()
return s
}
export function disconnectSocket() {
if (socket?.connected) {
socket.disconnect()
socket = null
}
}
```
---
*API Reference v1.0 — DesignFlow Platform*