Add multi-image compositions, docker setup, tests, and docs

- Add CompositionImage model with multi-image upload and main image flag
- Add composition endpoints: add-images, set-main-image, delete image, by-created-at filter
- Make contact-us by_category public
- Add 24 API tests covering all endpoints
- Add Dockerfile, docker-compose, entrypoint and docker docs
- Add Persian API docs and update README and Postman collection

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Shayan Azadi
2026-06-24 13:43:34 +03:30
parent cc2e082d53
commit 7e71d922d3
18 changed files with 2332 additions and 398 deletions

519
API_DOCS_FA.md Normal file
View File

@@ -0,0 +1,519 @@
<div dir="rtl" lang="fa">
# مستندات API (Zoneco ORG)
این سند نحوهٔ کارکرد APIهای بک‌اند پروژه Zoneco ORG را به زبان فارسی توضیح می‌دهد.
---
## وضعیت تست‌ها
```
Ran 24 tests — OK (0 failures)
```
| تعداد تست | گروه |
|-----------|------|
| ۶ تست | Contact Us |
| ۱۱ تست | Compositions |
| ۷ تست | Campaigns |
اجرای تست‌ها:
```bash
cd backend
python manage.py test api -v 2
```
---
## اطلاعات پایه
| مقدار | مورد |
|--------|------|
| `{{base_url}}` | آدرس پایه |
| `/api/` | پیشوند API |
| JSON | فرمت پاسخ |
| ۲۰ آیتم در هر صفحه | صفحه‌بندی |
| `/admin/` | پنل ادمین |
### متغیر Postman
در فایل Postman از متغیر `{{base_url}}` استفاده شده است.
مقدار آن را مطابق محیط اجرا (لوکال، سرور تست یا پروداکشن) تنظیم کنید.
**نمونهٔ آدرس کامل یک endpoint:**
```
{{base_url}}/api/contact-us/
```
---
## سطح دسترسی
| دسترسی | عملیات |
|--------|--------|
| عمومی — بدون نیاز به ورود | لیست و جزئیات (متد `GET`) |
| عمومی — بدون نیاز به ورود | ایجاد (متد `POST`) |
| فقط ادمین — نیاز به احراز هویت | ویرایش و حذف (متدهای `PUT`، `PATCH`، `DELETE`) |
برای درخواست‌های ادمین در Postman می‌توانید از **Basic Auth** با نام کاربری و رمز ادمین استفاده کنید.
---
## ۱. تماس با ما (Contact Us)
**مسیر پایه:**
```
/api/contact-us/
```
### فیلدها
| توضیح | الزامی | نوع | فیلد |
|-------|--------|-----|------|
| نام و نام خانوادگی | بله | string | `name` |
| ایمیل یا شماره تماس | بله | string | `email_or_phone` |
| متن پیام | بله | string | `description` |
| دسته‌بندی | بله | string | `category` |
**مقادیر مجاز برای `category`:**
- همکاری
- فروش
- پشتیبانی
- درخواست مشاور
- سایر
---
### دریافت لیست تماس‌ها
```
GET {{base_url}}/api/contact-us/
```
**پاسخ نمونه:**
```json
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"id": 1,
"name": "احمد محمدی",
"email_or_phone": "ahmad@example.com",
"description": "سلام، سوالی دارم.",
"category": "پشتیبانی",
"created_at": "2025-12-17T12:00:00Z",
"updated_at": "2025-12-17T12:00:00Z"
}
]
}
```
---
### دریافت یک تماس
```
GET {{base_url}}/api/contact-us/{id}/
```
---
### ثبت تماس جدید
```
POST {{base_url}}/api/contact-us/
Content-Type: application/json
```
**بدنه درخواست:**
```json
{
"name": "احمد محمدی",
"email_or_phone": "09121234567",
"description": "می‌خواهم در مورد محصولات اطلاعات بگیرم.",
"category": "فروش"
}
```
**کد پاسخ:** `201 Created`
---
### فیلتر بر اساس دسته‌بندی
```
GET {{base_url}}/api/contact-us/by_category/?category=پشتیبانی
```
| توضیح | الزامی | پارامتر |
|-------|--------|---------|
| یکی از دسته‌های مجاز | بله | `category` |
اگر پارامتر `category` ارسال نشود، خطای `400` برمی‌گردد.
---
### ویرایش و حذف (ادمین)
```
PUT {{base_url}}/api/contact-us/{id}/
PATCH {{base_url}}/api/contact-us/{id}/
DELETE {{base_url}}/api/contact-us/{id}/
```
نیاز به احراز هویت ادمین دارد.
---
## ۲. ترکیبات (Compositions)
**مسیر پایه:**
```
/api/compositions/
```
هر ترکیب می‌تواند **چند تصویر** داشته باشد.
یکی از تصاویر با فیلد `is_main: true` به‌عنوان **تصویر اصلی** مشخص می‌شود.
### فیلدها
| توضیح | الزامی | نوع | فیلد |
|-------|--------|-----|------|
| نام ترکیب | بله | string | `name` |
| توضیحات | بله | string | `description` |
| یک یا چند فایل تصویر (فقط در ایجاد/ویرایش) | خیر | file[] | `uploaded_images` |
| ایندکس تصویر اصلی (شمارش از ۰) | خیر | integer | `main_image_index` |
### محدودیت تصاویر
- فرمت‌های مجاز: JPEG، PNG، WebP، GIF
- حداکثر حجم هر فایل: **۵ مگابایت**
- مسیر ذخیره: `media/compositions/`
---
### دریافت لیست ترکیبات
```
GET {{base_url}}/api/compositions/
```
---
### دریافت یک ترکیب
```
GET {{base_url}}/api/compositions/{id}/
```
**پاسخ نمونه:**
```json
{
"id": 1,
"name": "ترکیب A",
"description": "توضیحات ترکیب",
"image": null,
"image_url": null,
"images": [
{
"id": 2,
"image": "/media/compositions/img2.jpg",
"image_url": "{{base_url}}/media/compositions/img2.jpg",
"is_main": true,
"created_at": "2025-12-17T12:00:00Z"
},
{
"id": 1,
"image": "/media/compositions/img1.jpg",
"image_url": "{{base_url}}/media/compositions/img1.jpg",
"is_main": false,
"created_at": "2025-12-17T11:00:00Z"
}
],
"main_image": {
"id": 2,
"image_url": "{{base_url}}/media/compositions/img2.jpg",
"is_main": true,
"created_at": "2025-12-17T12:00:00Z"
},
"created_at": "2025-12-17T10:00:00Z",
"updated_at": "2025-12-17T12:00:00Z"
}
```
---
### فیلتر بر اساس تاریخ ایجاد
```
GET {{base_url}}/api/compositions/by-created-at/?from=2026-06-01T00:00:00Z&to=2026-06-30T23:59:59Z
```
| توضیح | الزامی | پارامتر |
|-------|--------|---------|
| برگشت ترکیب‌هایی که `created_at >= from` | حداقل یکی | `from` |
| برگشت ترکیب‌هایی که `created_at <= to` | حداقل یکی | `to` |
فرمت تاریخ: ISO 8601 — مثال: `2026-06-01T00:00:00Z`
اگر هیچ پارامتری ارسال نشود، خطای `400` برمی‌گردد.
---
### ایجاد ترکیب (بدون تصویر)
```
POST {{base_url}}/api/compositions/
Content-Type: application/json
```
```json
{
"name": "ترکیب تست",
"description": "توضیحات ترکیب"
}
```
---
### ایجاد ترکیب با چند تصویر
```
POST {{base_url}}/api/compositions/
Content-Type: multipart/form-data
```
| مقدار نمونه | نوع | فیلد |
|-------------|-----|------|
| ترکیب A | text | `name` |
| توضیحات | text | `description` |
| image1.jpg | file | `uploaded_images` |
| image2.jpg | file | `uploaded_images` |
| 1 | text | `main_image_index` |
**نکته:** فیلد `uploaded_images` را برای هر تصویر یک‌بار تکرار کنید.
**نکته:** اگر `main_image_index` ارسال نشود، **اولین تصویر** به‌عنوان تصویر اصلی انتخاب می‌شود.
---
### افزودن تصویر به ترکیب موجود (ادمین)
```
POST {{base_url}}/api/compositions/{id}/add-images/
Content-Type: multipart/form-data
```
| نوع | فیلد |
|-----|------|
| file (یک یا چند فایل) | `uploaded_images` |
| text (اختیاری) | `main_image_index` |
---
### تنظیم تصویر اصلی (ادمین)
```
POST {{base_url}}/api/compositions/{id}/set-main-image/
Content-Type: application/json
```
```json
{
"image_id": 3
}
```
---
### حذف یک تصویر (ادمین)
```
DELETE {{base_url}}/api/compositions/{id}/images/{image_id}/
```
**کد پاسخ:** `204 No Content`
---
### ویرایش و حذف ترکیب (ادمین)
```
PUT {{base_url}}/api/compositions/{id}/
PATCH {{base_url}}/api/compositions/{id}/
DELETE {{base_url}}/api/compositions/{id}/
```
---
## ۳. کمپین‌ها (Campaigns)
**مسیر پایه:**
```
/api/campaigns/
```
### فیلدها
| توضیح | الزامی | نوع | فیلد |
|-------|--------|-----|------|
| نام کمپین | بله | string | `name` |
| توضیحات | بله | string | `description` |
| زمان شروع (ISO 8601) | بله | datetime | `start_time` |
| زمان پایان (باید بعد از `start_time` باشد) | بله | datetime | `end_time` |
| تصویر کمپین | خیر | file | `image` |
### فیلدهای محاسباتی (فقط خواندنی)
| توضیح | فیلد |
|-------|------|
| آیا کمپین الان فعال است | `is_active` |
| آیا کمپین هنوز شروع نشده | `is_upcoming` |
| آیا کمپین تمام شده | `is_ended` |
---
### دریافت لیست کمپین‌ها
```
GET {{base_url}}/api/campaigns/
```
---
### دریافت یک کمپین
```
GET {{base_url}}/api/campaigns/{id}/
```
---
### ایجاد کمپین
```
POST {{base_url}}/api/campaigns/
Content-Type: application/json
```
```json
{
"name": "کمپین نوروز",
"description": "تخفیف ویژه نوروز",
"start_time": "2026-03-01T00:00:00Z",
"end_time": "2026-03-31T23:59:59Z"
}
```
---
### کمپین‌های فعال
```
GET {{base_url}}/api/campaigns/active/
```
کمپین‌هایی که زمان فعلی بین `start_time` و `end_time` قرار دارد.
---
### کمپین‌های آینده
```
GET {{base_url}}/api/campaigns/upcoming/
```
کمپین‌هایی که هنوز شروع نشده‌اند.
---
### کمپین‌های پایان‌یافته
```
GET {{base_url}}/api/campaigns/ended/
```
---
### ویرایش و حذف (ادمین)
```
PUT {{base_url}}/api/campaigns/{id}/
PATCH {{base_url}}/api/campaigns/{id}/
DELETE {{base_url}}/api/campaigns/{id}/
```
---
## کدهای وضعیت HTTP
| معنی | کد |
|------|-----|
| موفق | `200` |
| ایجاد شد | `201` |
| حذف شد (بدون بدنه) | `204` |
| داده نامعتبر | `400` |
| دسترسی ندارید | `403` |
| یافت نشد | `404` |
---
## فایل Postman
مجموعه Postman در مسیر زیر قرار دارد:
```
backend/Zoneco_ORG_API.postman_collection.json
```
### نحوه import
1. Postman را باز کنید
2. گزینه **Import** را بزنید
3. فایل `Zoneco_ORG_API.postman_collection.json` را انتخاب کنید
4. متغیر `{{base_url}}` را مطابق محیط خود تنظیم کنید
5. برای درخواست‌های ادمین، در تب **Authorization** گزینه **Basic Auth** را فعال کنید
---
## خلاصه endpointها
| دسترسی | Endpoint | Method | # |
|--------|----------|--------|---|
| عمومی | `/api/contact-us/` | GET | 1 |
| عمومی | `/api/contact-us/` | POST | 2 |
| عمومی | `/api/contact-us/{id}/` | GET | 3 |
| عمومی | `/api/contact-us/by_category/?category=...` | GET | 4 |
| ادمین | `/api/contact-us/{id}/` | PUT/PATCH/DELETE | 5 |
| عمومی | `/api/compositions/` | GET | 6 |
| عمومی | `/api/compositions/` | POST | 7 |
| عمومی | `/api/compositions/{id}/` | GET | 8 |
| عمومی | `/api/compositions/by-created-at/?from=...&to=...` | GET | 9 |
| ادمین | `/api/compositions/{id}/add-images/` | POST | 10 |
| ادمین | `/api/compositions/{id}/set-main-image/` | POST | 11 |
| ادمین | `/api/compositions/{id}/images/{image_id}/` | DELETE | 12 |
| ادمین | `/api/compositions/{id}/` | PUT/PATCH/DELETE | 13 |
| عمومی | `/api/campaigns/` | GET | 14 |
| عمومی | `/api/campaigns/` | POST | 15 |
| عمومی | `/api/campaigns/{id}/` | GET | 16 |
| عمومی | `/api/campaigns/active/` | GET | 17 |
| عمومی | `/api/campaigns/upcoming/` | GET | 18 |
| عمومی | `/api/campaigns/ended/` | GET | 19 |
| ادمین | `/api/campaigns/{id}/` | PUT/PATCH/DELETE | 20 |
> **توجه:** در Postman قبل از هر مسیر، مقدار `{{base_url}}` را قرار دهید.
> مثال: `{{base_url}}/api/campaigns/active/`
</div>