Files
site_backend/README.md
Shayan Azadi 50c9cf613f Add admin login API and remove hardcoded secrets
- Add token-based admin login/logout/me endpoints
- Bootstrap admin from ADMIN_USERNAME/ADMIN_PASSWORD in Docker entrypoint
- Read ALLOWED_HOSTS and CORS from env only (no hardcoded server IPs)
- Keep docs/Postman/tests free of real credentials
- Cover login and auth flows with 31 local API tests

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-16 17:29:47 +03:30

324 lines
7.8 KiB
Markdown

# Zoneco ORG Backend API
A Django REST Framework backend for Zoneco ORG with PostgreSQL database.
---
## Test Status
```
Ran 31 tests — OK (0 failures)
```
| Group | Tests | Status |
|-------|-------|--------|
| Admin Login | 7 | ✅ All pass |
| Contact Us | 6 | ✅ All pass |
| Compositions | 11 | ✅ All pass |
| Campaigns | 7 | ✅ All pass |
Run tests at any time:
```bash
cd backend
python manage.py test api -v 2
```
---
## Features
- **Admin Login API** — Token-based login with username/password
- **Contact Us API** — Contact form submissions with category filter
- **Composition API** — Multi-image upload, main image flag, date-range filter
- **Campaign API** — Campaigns with active / upcoming / ended filters
- **Admin Panel** — Full Django admin at `/admin/`
- **Image Upload** — JPEG, PNG, WebP, GIF, max 5 MB, stored in `media/`
- **CORS Enabled** — Configured for frontend integration
- **Pagination** — 20 items per page
---
## Quick Start
### 1. Install dependencies
```bash
pip install -r requirements.txt
```
### 2. Create PostgreSQL database
```sql
CREATE DATABASE "Zoneco_ORG";
```
### 3. Apply migrations
```bash
python manage.py migrate
```
### 4. Run development server
```bash
python manage.py runserver
```
API available at `{{base_url}}/api/`
---
## Admin Credentials
Set these in the server `.env` (never hardcode in code):
| Env var | Purpose |
|---------|---------|
| `ADMIN_USERNAME` | Admin username for Docker bootstrap |
| `ADMIN_PASSWORD` | Admin password for Docker bootstrap |
| Endpoint | Path |
|----------|------|
| Django Admin Panel | `{{base_url}}/admin/` |
| API Login | `POST {{base_url}}/api/admin/login/` |
On Docker deploy, `entrypoint.sh` creates/updates the admin from `ADMIN_USERNAME` / `ADMIN_PASSWORD`.
---
## Admin Login API
### Login
```
POST {{base_url}}/api/admin/login/
Content-Type: application/json
```
```json
{
"username": "{{admin_username}}",
"password": "{{admin_password}}"
}
```
**Response:**
```json
{
"token": "<token>",
"user": {
"id": 1,
"username": "<admin_username>",
"is_staff": true,
"is_superuser": true
}
}
```
### Use the token on protected endpoints
```
Authorization: Token <your_token_here>
```
### Current admin user
```
GET {{base_url}}/api/admin/me/
Authorization: Token <token>
```
### Logout (deletes the token)
```
POST {{base_url}}/api/admin/logout/
Authorization: Token <token>
```
---
## API Endpoints
### Authentication
| Action | Access |
|--------|--------|
| GET (list, retrieve, custom filters) | Public — no login required |
| POST (create) | Public — no login required |
| PUT / PATCH / DELETE | Admin only — Token required |
| Admin login / logout / me | See Admin Login API above |
---
### Contact Us — `/api/contact-us/`
| Method | Endpoint | Access | Description |
|--------|----------|--------|-------------|
| GET | `/api/contact-us/` | Public | List all submissions |
| POST | `/api/contact-us/` | Public | Submit a contact form |
| GET | `/api/contact-us/{id}/` | Public | Get one submission |
| GET | `/api/contact-us/by_category/?category=...` | Public | Filter by category |
| PATCH | `/api/contact-us/{id}/` | Admin | Partial update |
| PUT | `/api/contact-us/{id}/` | Admin | Full update |
| DELETE | `/api/contact-us/{id}/` | Admin | Delete |
**Category choices:** `همکاری` · `فروش` · `پشتیبانی` · `درخواست مشاور` · `سایر`
**Fields:**
| Field | Type | Required |
|-------|------|----------|
| `name` | string | Yes |
| `email_or_phone` | string | Yes |
| `description` | string | Yes |
| `category` | string | Yes |
---
### Compositions — `/api/compositions/`
Each composition can have **multiple images**. One image can be flagged as the **main image** (`is_main: true`).
| Method | Endpoint | Access | Description |
|--------|----------|--------|-------------|
| GET | `/api/compositions/` | Public | List all compositions |
| POST | `/api/compositions/` | Public | Create composition (with optional images) |
| GET | `/api/compositions/{id}/` | Public | Get one composition |
| GET | `/api/compositions/by-created-at/?from=...&to=...` | Public | Filter by creation date range |
| POST | `/api/compositions/{id}/add-images/` | Admin | Add images to existing composition |
| POST | `/api/compositions/{id}/set-main-image/` | Admin | Set main image |
| DELETE | `/api/compositions/{id}/images/{image_id}/` | Admin | Delete a single image |
| PATCH | `/api/compositions/{id}/` | Admin | Partial update |
| PUT | `/api/compositions/{id}/` | Admin | Full update |
| DELETE | `/api/compositions/{id}/` | Admin | Delete composition |
**Fields:**
| Field | Type | Required | Notes |
|-------|------|----------|-------|
| `name` | string | Yes | |
| `description` | string | Yes | |
| `uploaded_images` | file[] | No | One or more image files (multipart) |
| `main_image_index` | integer | No | 0-based index of main image in `uploaded_images`. Defaults to 0. |
**Image constraints:** JPEG, PNG, WebP, GIF — max **5 MB** each — stored at `media/compositions/`
**`by-created-at` query params:**
| Param | Required | Format |
|-------|----------|--------|
| `from` | At least one | ISO 8601 e.g. `2026-06-01T00:00:00Z` |
| `to` | At least one | ISO 8601 e.g. `2026-06-30T23:59:59Z` |
---
### Campaigns — `/api/campaigns/`
| Method | Endpoint | Access | Description |
|--------|----------|--------|-------------|
| GET | `/api/campaigns/` | Public | List all campaigns |
| POST | `/api/campaigns/` | Public | Create campaign |
| GET | `/api/campaigns/{id}/` | Public | Get one campaign |
| GET | `/api/campaigns/active/` | Public | Currently active campaigns |
| GET | `/api/campaigns/upcoming/` | Public | Not started yet |
| GET | `/api/campaigns/ended/` | Public | Already finished |
| PATCH | `/api/campaigns/{id}/` | Admin | Partial update |
| PUT | `/api/campaigns/{id}/` | Admin | Full update |
| DELETE | `/api/campaigns/{id}/` | Admin | Delete |
**Fields:**
| Field | Type | Required | Notes |
|-------|------|----------|-------|
| `name` | string | Yes | |
| `description` | string | Yes | |
| `start_time` | datetime | Yes | ISO 8601 |
| `end_time` | datetime | Yes | Must be after `start_time` |
| `image` | file | No | Campaign image (multipart) |
**Read-only computed fields:** `is_active`, `is_upcoming`, `is_ended`
---
## Models
### ContactUs
| Field | Type |
|-------|------|
| `name` | CharField |
| `email_or_phone` | CharField |
| `description` | TextField |
| `category` | CharField (choices) |
| `created_at` | DateTimeField (auto) |
| `updated_at` | DateTimeField (auto) |
### Composition
| Field | Type |
|-------|------|
| `name` | CharField |
| `description` | TextField |
| `image` | ImageField (legacy) |
| `created_at` | DateTimeField (auto) |
| `updated_at` | DateTimeField (auto) |
### CompositionImage
| Field | Type |
|-------|------|
| `composition` | ForeignKey → Composition |
| `image` | ImageField |
| `is_main` | BooleanField |
| `created_at` | DateTimeField (auto) |
DB constraint: only one `is_main=True` per composition.
### Campaign
| Field | Type |
|-------|------|
| `name` | CharField |
| `description` | TextField |
| `start_time` | DateTimeField |
| `end_time` | DateTimeField |
| `image` | ImageField |
| `created_at` | DateTimeField (auto) |
| `updated_at` | DateTimeField (auto) |
---
## Media Files
| Content | Path |
|---------|------|
| Composition images | `media/compositions/` |
| Campaign images | `media/campaigns/` |
---
## Postman Collection
Import `backend/Zoneco_ORG_API.postman_collection.json` into Postman.
Set the `base_url` collection variable to your server address.
---
## Persian Documentation
Full Persian API documentation: `backend/API_DOCS_FA.md`
---
## HTTP Status Codes
| Code | Meaning |
|------|---------|
| 200 | OK |
| 201 | Created |
| 204 | Deleted (no body) |
| 400 | Invalid data |
| 403 | Forbidden (login required) |
| 404 | Not found |