First commit
This commit is contained in:
@@ -0,0 +1,74 @@
|
|||||||
|
---
|
||||||
|
alwaysApply: false
|
||||||
|
---
|
||||||
|
# Backend API Architecture & Postman
|
||||||
|
|
||||||
|
## 1. URL / Routing Architecture
|
||||||
|
|
||||||
|
- **Root (config/urls.py):** API mounts under `api/<app-prefix>/` via `include()`.
|
||||||
|
- Example: `path("api/auth/", include("auth.urls"))`, `path("api/sensor-hub/", include("sensor_hub.urls"))`.
|
||||||
|
- App prefix: kebab-case (e.g. `sensor-hub`).
|
||||||
|
|
||||||
|
- **App URLs (each app’s urls.py):** Only endpoint definitions with `path()`.
|
||||||
|
- Same view can be used for several paths; distinguish by path or `kwargs` (e.g. `kwargs={"action": "active"}`).
|
||||||
|
- Order matters: more specific paths first (e.g. `active/`, `deactive/`), then path-param routes (e.g. `<uuid:uuid>/`), then base `""` for list.
|
||||||
|
- Example pattern:
|
||||||
|
- `path("active/", View.as_view(), kwargs={"action": "active"})`
|
||||||
|
- `path("deactive/", View.as_view(), kwargs={"action": "deactive"})`
|
||||||
|
- `path("<uuid:uuid>/", View.as_view(), name="...-detail")`
|
||||||
|
- `path("", View.as_view(), name="...-list")`
|
||||||
|
|
||||||
|
- **Views:** One `APIView` per resource (or per flow, e.g. auth). Dispatch by HTTP method and optionally by `request.path` or `kwargs` (e.g. `uuid`, `action`). No business logic in views; orchestration only.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Postman Collection Layout
|
||||||
|
|
||||||
|
- **Placement:** One collection per app: `<app_name>/postman/<collection_name>.json` (e.g. `sensor_hub/postman/sensor_hub.json`, `auth/postman/postman.json`).
|
||||||
|
|
||||||
|
- **Structure:**
|
||||||
|
- `info`: `name`, `schema` (v2.1.0), optional `description`.
|
||||||
|
- `item`: array of requests (one per endpoint variant/method).
|
||||||
|
- `variable`: at least `baseUrl` (e.g. `http://localhost:8000`); add `token`, `uuid` etc. when needed.
|
||||||
|
|
||||||
|
- **Request style:**
|
||||||
|
- One base URL per resource; multiple requests for different methods or path params (e.g. list vs `{{uuid}}/`).
|
||||||
|
- URL: `{{baseUrl}}/api/<app-prefix>/...` (e.g. `{{baseUrl}}/api/sensor-hub/`, `{{baseUrl}}/api/sensor-hub/{{uuid}}/`).
|
||||||
|
- Auth: where required, header `Authorization: Bearer {{token}}`.
|
||||||
|
- No random/dynamic values in body or response examples.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Postman Request Generator (when I give you routes)
|
||||||
|
|
||||||
|
Your task is to take the API routes I provide and convert them into a valid Postman collection JSON (as above).
|
||||||
|
|
||||||
|
ROUTE STYLE:
|
||||||
|
- When routes are defined as a single URL with different HTTP methods, generate one base URL and multiple requests (one per method/variant). Use the same URL for all; use path params (e.g. `<uuid>/`) or query for GET detail vs list when applicable.
|
||||||
|
|
||||||
|
RULES:
|
||||||
|
|
||||||
|
1. For each route (or each method/variant on the same URL), generate:
|
||||||
|
- Name: A descriptive, concise name for the request based on the route and HTTP method.
|
||||||
|
- Method: The HTTP method (GET, POST, PUT, DELETE, etc.).
|
||||||
|
- URL: The route URL.
|
||||||
|
- Body: If the endpoint accepts input, provide a JSON body example with appropriate keys; otherwise, leave it empty.
|
||||||
|
- Response: Provide a sample JSON response in the following format:
|
||||||
|
- If the endpoint returns no data:
|
||||||
|
{
|
||||||
|
"status": "success"
|
||||||
|
}
|
||||||
|
- If the endpoint returns data:
|
||||||
|
{
|
||||||
|
"status": "success",
|
||||||
|
"data": {}
|
||||||
|
}
|
||||||
|
- All responses must use HTTP status 200.
|
||||||
|
2. Do NOT generate random or dynamic values in the body or response.
|
||||||
|
3. Output must be a valid Postman collection JSON structure:
|
||||||
|
- Include "info" with collection name.
|
||||||
|
- Include "item" array with all requests.
|
||||||
|
4. Keep the JSON fully compatible with Postman import.
|
||||||
|
5. Do NOT include explanations outside the JSON.
|
||||||
|
|
||||||
|
Wait for me to provide the route definitions.
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
---
|
||||||
|
alwaysApply: true
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Django App (Module) Naming
|
||||||
|
|
||||||
|
| Item | Convention | Example |
|
||||||
|
|--------|------------|----------------------------|
|
||||||
|
| App name | snake_case, **بدون** پسوند `_api` | `account`, `auth`, `sensor_hub` |
|
||||||
|
|
||||||
|
- نام اپها را با `_api` تمام **نکنید**. مثلاً بهجای `account_api` از `account` استفاده کنید.
|
||||||
|
- برای ماژولهای فقط API، همان نام دامنه کافی است (مثلاً `auth`، `account`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Model and Database Field Naming
|
||||||
|
|
||||||
|
| Item | Convention | Example |
|
||||||
|
|-------------------|-------------------------|----------------------------------------------|
|
||||||
|
| Model | PascalCase | `UserProfile` |
|
||||||
|
| Fields | snake_case | `first_name`, `email_address` |
|
||||||
|
| Boolean | `is_` / `has_` + name | `is_active`, `has_paid` |
|
||||||
|
| Date/Time | `created_at` / `updated_at` | `created_at`, `updated_at` |
|
||||||
|
| ForeignKey / M2M | snake_case, often model name | `author = ForeignKey(UserProfile)` |
|
||||||
|
| Choices / Enum | UPPER_SNAKE_CASE values | `role = CharField(choices=(("ADMIN","Admin"), ("USER","User")))` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. DRF Conventions
|
||||||
|
|
||||||
|
- **Serializer:** Validation + data transformation. Use PascalCase names.
|
||||||
|
- **Service layer:** All business logic lives here.
|
||||||
|
- **View:** Orchestration only — call services and return responses. No business logic in views.
|
||||||
|
- **URLs:** Define endpoints only. Use kebab-case for URL paths.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. API Response Format
|
||||||
|
|
||||||
|
همه پاسخهای API باید فیلد `code` را برگردانند؛ مقدار آن برابر **HTTP status code** درخواست است (مثلاً 200، 201، 400، 404).
|
||||||
|
|
||||||
|
| فیلد | توضیح |
|
||||||
|
|-------|----------------------------------------|
|
||||||
|
| `code` | کد وضعیت HTTP (مثلاً 200، 404، 500) |
|
||||||
|
| `msg` | پیام (مثلاً "success" برای 2xx) |
|
||||||
|
| `data` | دادهٔ برگشتی (اختیاری) |
|
||||||
|
|
||||||
|
مثال:
|
||||||
|
```json
|
||||||
|
{"code": 200, "msg": "success", "data": {...}}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Simple Example: How the Layers Connect (users app)
|
||||||
|
|
||||||
|
```python
|
||||||
|
# models.py
|
||||||
|
class UserProfile(models.Model):
|
||||||
|
first_name = models.CharField(max_length=50)
|
||||||
|
last_name = models.CharField(max_length=50)
|
||||||
|
email_address = models.EmailField(unique=True)
|
||||||
|
is_active = models.BooleanField(default=True)
|
||||||
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
updated_at = models.DateTimeField(auto_now=True)
|
||||||
|
|
||||||
|
|
||||||
|
# serializers.py
|
||||||
|
class UserCreateSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = UserProfile
|
||||||
|
fields = ["first_name", "last_name", "email_address"]
|
||||||
|
|
||||||
|
|
||||||
|
# services.py
|
||||||
|
def create_user(first_name, last_name, email_address):
|
||||||
|
return UserProfile.objects.create(
|
||||||
|
first_name=first_name,
|
||||||
|
last_name=last_name,
|
||||||
|
email_address=email_address,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# views.py
|
||||||
|
class UserCreateAPIView(APIView):
|
||||||
|
def post(self, request):
|
||||||
|
serializer = UserCreateSerializer(data=request.data)
|
||||||
|
serializer.is_valid(raise_exception=True)
|
||||||
|
user = create_user(**serializer.validated_data)
|
||||||
|
return Response({"code": 201, "msg": "success", "data": {"id": user.id}}, status=201)
|
||||||
|
```
|
||||||
|
|
||||||
|
- All names follow the conventions above.
|
||||||
|
- Business logic is in `services.py`.
|
||||||
|
- Serializer only validates and serializes.
|
||||||
|
- View only orchestrates (calls service, returns response).
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
---
|
||||||
|
alwaysApply: false
|
||||||
|
---
|
||||||
|
You are a Django API code generator.
|
||||||
|
|
||||||
|
Your task is to generate a complete and runnable Django project based on the routes I provide.
|
||||||
|
|
||||||
|
ROUTE STYLE:
|
||||||
|
- Routes may be defined as a single URL with different HTTP methods (e.g. one path, GET for list, GET with query for detail, PUT/PATCH for update, DELETE for delete, POST for action). Use one view class that implements get, post, put, patch, delete as needed. Use query parameters (e.g. sensor_id) to distinguish list vs detail when both use GET.
|
||||||
|
|
||||||
|
STRICT RULES:
|
||||||
|
|
||||||
|
- Use Django only.
|
||||||
|
- Do NOT use Django REST Framework unless I explicitly request it.
|
||||||
|
- Do NOT connect to any database.
|
||||||
|
- Do NOT create any Models.
|
||||||
|
- Do NOT generate random or dynamic data.
|
||||||
|
- Input parameters must be accepted (body, query params, path params).
|
||||||
|
- HOWEVER, absolutely NO processing, validation, transformation, or logic may be applied to them.
|
||||||
|
- Do NOT use input values inside the response.
|
||||||
|
- No conditional logic.
|
||||||
|
- No business logic.
|
||||||
|
- No validation.
|
||||||
|
- All endpoints must always return static JSON responses only.
|
||||||
|
- ALL responses must return HTTP status code 200 only.
|
||||||
|
- No other status codes are allowed.
|
||||||
|
- No explanations outside the code.
|
||||||
|
- Return complete runnable code including project structure (views.py, urls.py, settings if needed, etc.).
|
||||||
|
|
||||||
|
--------------------------------------------------
|
||||||
|
RESPONSE FORMAT (STRICTLY ENFORCED)
|
||||||
|
|
||||||
|
If the endpoint does NOT require returning data:
|
||||||
|
|
||||||
|
{
|
||||||
|
"status": "success"
|
||||||
|
}
|
||||||
|
|
||||||
|
If the endpoint requires returning data:
|
||||||
|
|
||||||
|
{
|
||||||
|
"status": "success",
|
||||||
|
"data": {}
|
||||||
|
}
|
||||||
|
|
||||||
|
Mandatory rules:
|
||||||
|
- The "status" field MUST always be exactly "success".
|
||||||
|
- If "data" is present, it MUST be exactly an empty object {}.
|
||||||
|
- If data is not required, DO NOT include the "data" field.
|
||||||
|
- No additional fields are allowed.
|
||||||
|
--------------------------------------------------
|
||||||
|
|
||||||
|
COMMENTING REQUIREMENTS (VERY IMPORTANT):
|
||||||
|
|
||||||
|
Each endpoint MUST include professional, multi-line docstring documentation.
|
||||||
|
|
||||||
|
The documentation MUST include:
|
||||||
|
|
||||||
|
1. Clear description of the endpoint purpose.
|
||||||
|
2. Complete description of ALL input parameters:
|
||||||
|
- Parameter name
|
||||||
|
- Data type
|
||||||
|
- Location (body / query / path)
|
||||||
|
- Description of its intended purpose
|
||||||
|
3. Full description of the response structure:
|
||||||
|
- status field
|
||||||
|
- data field (if applicable)
|
||||||
|
4. Explicit statement that no processing or validation is performed on inputs.
|
||||||
|
|
||||||
|
Use clean, professional API documentation style.
|
||||||
|
Do not write anything outside the code.
|
||||||
|
Wait for my route definitions.
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
!.env.example
|
||||||
|
.git
|
||||||
|
__pycache__
|
||||||
|
*.pyc
|
||||||
|
.venv
|
||||||
|
venv
|
||||||
|
*.egg-info
|
||||||
|
.pytest_cache
|
||||||
|
.coverage
|
||||||
|
htmlcov
|
||||||
|
*.log
|
||||||
|
media
|
||||||
|
staticfiles
|
||||||
|
.cursor
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
# Django
|
||||||
|
SECRET_KEY=your-secret-key-change-in-production
|
||||||
|
DEBUG=1
|
||||||
|
ALLOWED_HOSTS=localhost,127.0.0.1,0.0.0.0
|
||||||
|
|
||||||
|
# Database (MySQL) - used by Django in Docker
|
||||||
|
DB_ENGINE=django.db.backends.mysql
|
||||||
|
DB_NAME=croplogic
|
||||||
|
DB_USER=croplogic
|
||||||
|
DB_PASSWORD=changeme
|
||||||
|
DB_HOST=db
|
||||||
|
DB_PORT=3306
|
||||||
|
|
||||||
|
# Optional: for running manage.py from host (local DB)
|
||||||
|
# DB_HOST=127.0.0.1
|
||||||
+59
@@ -0,0 +1,59 @@
|
|||||||
|
# Environment
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
*.env
|
||||||
|
!*.env.example
|
||||||
|
|
||||||
|
# Python
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
*.so
|
||||||
|
.Python
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
wheels/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
|
||||||
|
# Virtual environments
|
||||||
|
.venv/
|
||||||
|
venv/
|
||||||
|
ENV/
|
||||||
|
env/
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
|
|
||||||
|
# Django
|
||||||
|
*.log
|
||||||
|
local_settings.py
|
||||||
|
db.sqlite3
|
||||||
|
media/
|
||||||
|
staticfiles/
|
||||||
|
*.pot
|
||||||
|
|
||||||
|
# Testing / Coverage
|
||||||
|
.coverage
|
||||||
|
htmlcov/
|
||||||
|
.pytest_cache/
|
||||||
|
.tox/
|
||||||
|
.nox/
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
+22
@@ -0,0 +1,22 @@
|
|||||||
|
FROM python:3.12-slim
|
||||||
|
|
||||||
|
ENV PYTHONDONTWRITEBYTECODE=1
|
||||||
|
ENV PYTHONUNBUFFERED=1
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# System deps for MySQL client (pkg-config required by mysqlclient to find libs)
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
default-libmysqlclient-dev \
|
||||||
|
build-essential \
|
||||||
|
pkg-config \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
COPY requirements.txt .
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
EXPOSE 8000
|
||||||
|
|
||||||
|
CMD ["gunicorn", "config.wsgi:application", "--bind", "0.0.0.0:8000"]
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
|
from django.core.asgi import get_asgi_application
|
||||||
|
|
||||||
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings")
|
||||||
|
|
||||||
|
application = get_asgi_application()
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
|
load_dotenv()
|
||||||
|
|
||||||
|
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||||
|
|
||||||
|
SECRET_KEY = os.environ.get("SECRET_KEY", "django-insecure-dev-only")
|
||||||
|
DEBUG = os.environ.get("DEBUG", "0") == "1"
|
||||||
|
ALLOWED_HOSTS = os.environ.get("ALLOWED_HOSTS", "localhost,127.0.0.1").split(",")
|
||||||
|
|
||||||
|
INSTALLED_APPS = [
|
||||||
|
"django.contrib.admin",
|
||||||
|
"django.contrib.auth",
|
||||||
|
"django.contrib.contenttypes",
|
||||||
|
"django.contrib.sessions",
|
||||||
|
"django.contrib.messages",
|
||||||
|
"django.contrib.staticfiles",
|
||||||
|
"auth.apps.AuthConfig",
|
||||||
|
"account",
|
||||||
|
"sensor_hub",
|
||||||
|
"dashboard",
|
||||||
|
"rest_framework",
|
||||||
|
"corsheaders",
|
||||||
|
]
|
||||||
|
|
||||||
|
MIDDLEWARE = [
|
||||||
|
"django.middleware.security.SecurityMiddleware",
|
||||||
|
"corsheaders.middleware.CorsMiddleware",
|
||||||
|
"django.contrib.sessions.middleware.SessionMiddleware",
|
||||||
|
"django.middleware.common.CommonMiddleware",
|
||||||
|
"django.middleware.csrf.CsrfViewMiddleware",
|
||||||
|
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
||||||
|
"django.contrib.messages.middleware.MessageMiddleware",
|
||||||
|
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
||||||
|
]
|
||||||
|
|
||||||
|
ROOT_URLCONF = "config.urls"
|
||||||
|
WSGI_APPLICATION = "config.wsgi.application"
|
||||||
|
|
||||||
|
TEMPLATES = [
|
||||||
|
{
|
||||||
|
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
||||||
|
"DIRS": [],
|
||||||
|
"APP_DIRS": True,
|
||||||
|
"OPTIONS": {
|
||||||
|
"context_processors": [
|
||||||
|
"django.template.context_processors.debug",
|
||||||
|
"django.template.context_processors.request",
|
||||||
|
"django.contrib.auth.context_processors.auth",
|
||||||
|
"django.contrib.messages.context_processors.messages",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
DATABASES = {
|
||||||
|
"default": {
|
||||||
|
"ENGINE": os.environ.get("DB_ENGINE", "django.db.backends.mysql"),
|
||||||
|
"NAME": os.environ.get("DB_NAME", "ai"),
|
||||||
|
"USER": os.environ.get("DB_USER", "ai"),
|
||||||
|
"PASSWORD": os.environ.get("DB_PASSWORD", ""),
|
||||||
|
"HOST": os.environ.get("DB_HOST", "127.0.0.1"),
|
||||||
|
"PORT": os.environ.get("DB_PORT", "3306"),
|
||||||
|
"OPTIONS": {
|
||||||
|
"charset": "utf8mb4",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AUTH_PASSWORD_VALIDATORS = [
|
||||||
|
{"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator"},
|
||||||
|
{"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator"},
|
||||||
|
{"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator"},
|
||||||
|
{"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator"},
|
||||||
|
]
|
||||||
|
|
||||||
|
LANGUAGE_CODE = "en-us"
|
||||||
|
TIME_ZONE = "UTC"
|
||||||
|
USE_I18N = True
|
||||||
|
USE_TZ = True
|
||||||
|
|
||||||
|
STATIC_URL = "static/"
|
||||||
|
STATIC_ROOT = BASE_DIR / "staticfiles"
|
||||||
|
|
||||||
|
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
|
||||||
|
|
||||||
|
CACHES = {
|
||||||
|
"default": {
|
||||||
|
"BACKEND": "django.core.cache.backends.locmem.LocMemCache",
|
||||||
|
"LOCATION": "croplogic-auth-otp",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
REST_FRAMEWORK = {
|
||||||
|
"DEFAULT_PERMISSION_CLASSES": [
|
||||||
|
"rest_framework.permissions.AllowAny",
|
||||||
|
],
|
||||||
|
"DEFAULT_AUTHENTICATION_CLASSES": [
|
||||||
|
"rest_framework_simplejwt.authentication.JWTAuthentication",
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
CORS_ALLOW_ALL_ORIGINS = DEBUG
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
from django.urls import include, path
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path("admin/", admin.site.urls),
|
||||||
|
path("api/auth/", include("auth.urls")),
|
||||||
|
path("api/account/", include("account.urls")),
|
||||||
|
path("api/sensor-hub/", include("sensor_hub.urls")),
|
||||||
|
path("api/farm-dashboard-config/", include("dashboard.urls_config")),
|
||||||
|
path("api/farm-dashboard/", include("dashboard.urls")),
|
||||||
|
]
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
|
from django.core.wsgi import get_wsgi_application
|
||||||
|
|
||||||
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings")
|
||||||
|
|
||||||
|
application = get_wsgi_application()
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
# Production: no source mount; image contains code
|
||||||
|
name: ai
|
||||||
|
|
||||||
|
services:
|
||||||
|
db:
|
||||||
|
image: mysql:8.0
|
||||||
|
container_name: ai-db
|
||||||
|
environment:
|
||||||
|
MYSQL_DATABASE: ${DB_NAME:-ai}
|
||||||
|
MYSQL_USER: ${DB_USER:-ai}
|
||||||
|
MYSQL_PASSWORD: ${DB_PASSWORD}
|
||||||
|
MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
|
||||||
|
volumes:
|
||||||
|
- ai_mysql_data:/var/lib/mysql
|
||||||
|
restart: unless-stopped
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p${DB_ROOT_PASSWORD}"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
|
||||||
|
phpmyadmin:
|
||||||
|
image: phpmyadmin:latest
|
||||||
|
container_name: ai-phpmyadmin
|
||||||
|
environment:
|
||||||
|
PMA_HOST: db
|
||||||
|
PMA_PORT: 3306
|
||||||
|
UPLOAD_LIMIT: 64M
|
||||||
|
ports:
|
||||||
|
- "8082:80"
|
||||||
|
depends_on:
|
||||||
|
db:
|
||||||
|
condition: service_healthy
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
web:
|
||||||
|
build: .
|
||||||
|
container_name: ai-web
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
|
environment:
|
||||||
|
DB_HOST: db
|
||||||
|
depends_on:
|
||||||
|
db:
|
||||||
|
condition: service_healthy
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "8020:8000"
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
ai_mysql_data:
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
# Development: volumes mount source so code updates apply without rebuild
|
||||||
|
name: ai
|
||||||
|
|
||||||
|
services:
|
||||||
|
db:
|
||||||
|
image: mysql:8.0
|
||||||
|
container_name: ai-db
|
||||||
|
environment:
|
||||||
|
MYSQL_DATABASE: ${DB_NAME:-ai}
|
||||||
|
MYSQL_USER: ${DB_USER:-ai}
|
||||||
|
MYSQL_PASSWORD: ${DB_PASSWORD:-changeme}
|
||||||
|
MYSQL_ROOT_PASSWORD: ${DB_PASSWORD:-changeme}
|
||||||
|
volumes:
|
||||||
|
- ai_mysql_data:/var/lib/mysql
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p${DB_PASSWORD:-changeme}"]
|
||||||
|
interval: 5s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
|
||||||
|
phpmyadmin:
|
||||||
|
image: phpmyadmin:latest
|
||||||
|
container_name: ai-phpmyadmin
|
||||||
|
environment:
|
||||||
|
PMA_HOST: db
|
||||||
|
PMA_PORT: 3306
|
||||||
|
UPLOAD_LIMIT: 64M
|
||||||
|
ports:
|
||||||
|
- "8082:80"
|
||||||
|
depends_on:
|
||||||
|
db:
|
||||||
|
condition: service_healthy
|
||||||
|
|
||||||
|
web:
|
||||||
|
build: .
|
||||||
|
container_name: ai-web
|
||||||
|
command: python manage.py runserver 0.0.0.0:8000
|
||||||
|
volumes:
|
||||||
|
- .:/app
|
||||||
|
ports:
|
||||||
|
- "8020:8000"
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
|
environment:
|
||||||
|
DB_HOST: db
|
||||||
|
depends_on:
|
||||||
|
db:
|
||||||
|
condition: service_healthy
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
ai_mysql_data:
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
"""Django's command-line utility for administrative tasks."""
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Run administrative tasks."""
|
||||||
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings")
|
||||||
|
try:
|
||||||
|
from django.core.management import execute_from_command_line
|
||||||
|
except ImportError as exc:
|
||||||
|
raise ImportError(
|
||||||
|
"Couldn't import Django. Are you sure it's installed and "
|
||||||
|
"available on your PYTHONPATH environment variable? Did you "
|
||||||
|
"forget to activate a virtual environment?"
|
||||||
|
) from exc
|
||||||
|
execute_from_command_line(sys.argv)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
Django>=5.0,<6
|
||||||
|
djangorestframework>=3.14,<4
|
||||||
|
djangorestframework-simplejwt>=5.3,<6
|
||||||
|
django-cors-headers>=4.3,<5
|
||||||
|
mysqlclient>=2.2,<3
|
||||||
|
gunicorn>=22,<25
|
||||||
|
python-dotenv>=1.0,<2
|
||||||
Reference in New Issue
Block a user