FASHIONISTAR CI/CD
🔄 Celery Queues Deploy: d915798567b1052828794606dda8e62b4500f337 [GitHub Actions]
607bf71
Raw
History Blame Contribute Delete
33.3 kB
.PHONY: help install install-dev dev run run-asgi run-daphne asgi wsgi uvicorn daphne migrate test lint clean shell docker-build docker-up docker-down start-redis stop-redis stress-redis stress-health test-auth test-common test-store test-vendor test-customer test-payments test-async test-unit test-integration test-smoke test-cov cov-html
.DEFAULT_GOAL := help
# Default to development settings for local make workflows while still allowing
# overrides like: `make DJANGO_SETTINGS_MODULE=backend.config.production <target>`.
DJANGO_SETTINGS_MODULE ?= backend.config.development
export DJANGO_SETTINGS_MODULE
# Keep uv cache inside the repo so local Windows/dev-sandbox runs never depend
# on a global AppData cache with different filesystem permissions.
UV_CACHE_DIR ?= $(subst /,\,$(CURDIR))\.uv-cache
export UV_CACHE_DIR
# Windows `make` recipes run through cmd.exe in this repo, so Redis/bootstrap
# helpers need Windows-friendly commands instead of POSIX shell fragments.
TMP_REDIS_DIR := $(subst /,\,$(abspath ../.tmp_redis))
TMP_REDIS_EXE := $(TMP_REDIS_DIR)\redis-server.exe
ENSURE_TMP_REDIS = powershell -NoProfile -ExecutionPolicy Bypass -Command "if ((Test-NetConnection -ComputerName 127.0.0.1 -Port 6379 -WarningAction SilentlyContinue).TcpTestSucceeded) { exit 0 }; if (Test-Path '$(TMP_REDIS_EXE)') { Start-Process -FilePath '$(TMP_REDIS_EXE)' -ArgumentList '--port','6379' -WorkingDirectory '$(TMP_REDIS_DIR)' -WindowStyle Hidden; Start-Sleep -Seconds 1 } else { Write-Host 'Portable Redis not found at $(TMP_REDIS_EXE)' }"
# ─── Colors ───
CYAN := \033[0;36m
GREEN := \033[0;32m
YELLOW := \033[0;33m
RED := \033[0;31m
BOLD := \033[1m
NC := \033[0m
##@ Help
help: ## Display this help message
@echo "$(BOLD)$(CYAN)━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━$(NC)"
@echo "$(BOLD)$(CYAN) FASHIONISTAR AI — Backend Developer Commands$(NC)"
@echo "$(CYAN) Django 6.0 · Python 3.14+ · Dual-Engine (DRF + Ninja)$(NC)"
@echo "$(BOLD)$(CYAN)━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━$(NC)"
@echo ""
@awk 'BEGIN {FS = ":.*##"; printf "Usage:\n make $(CYAN)<target>$(NC)\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " $(CYAN)%-26s$(NC) %s\n", $$1, $$2 } /^##@/ { printf "\n$(YELLOW)%s$(NC)\n", substr($$0, 5) } ' $(MAKEFILE_LIST)
# ═══════════════════════════════════════════════════════════════
##@ Development
# ═══════════════════════════════════════════════════════════════
install: ## Install Python dependencies from requirements.txt
@echo "$(CYAN)Installing dependencies...$(NC)"
uv sync
@echo "$(GREEN)✓ Dependencies installed$(NC)"
install-dev: ## Install dev dependencies (linting, testing, typing)
@echo "$(CYAN)Installing dev dependencies...$(NC)"
uv sync
@echo "$(GREEN)✓ Dev dependencies installed$(NC)"
setup: install install-dev migrate static ## Full first-time setup
@echo "$(GREEN)✓ Setup complete — run 'make dev' to start$(NC)"
shell: ## Open Django interactive shell (development settings)
uv run manage.py shell --settings=$(DJANGO_SETTINGS_MODULE)
shell-plus: ## Open enhanced Django shell (requires django-extensions)
uv run manage.py shell_plus --ipython --settings=$(DJANGO_SETTINGS_MODULE) 2>NUL || uv run manage.py shell --settings=$(DJANGO_SETTINGS_MODULE)
# ═══════════════════════════════════════════════════════════════
##@ Database & Migrations
# ═══════════════════════════════════════════════════════════════
migrate: ## Run makemigrations + migrate
@echo "$(CYAN)Running migrations...$(NC)"
uv run manage.py makemigrations
uv run manage.py migrate
@echo "$(GREEN)✓ Migrations applied$(NC)"
mmig: ## Make migrations (optionally for a specific app: make mmig app=authentication)
@if [ -z "$(app)" ]; then \
uv run manage.py makemigrations; \
else \
uv run manage.py makemigrations "$(app)"; \
fi
mig: ## Apply migrations (optionally for a specific app: make mig app=authentication)
@if [ -z "$(app)" ]; then \
uv run manage.py migrate; \
else \
uv run manage.py migrate "$(app)"; \
fi
showmig: ## Show migration status for all apps
uv run manage.py showmigrations
squash: ## Squash migrations for an app (usage: make squash app=authentication start=0001)
uv run manage.py squashmigrations $(app) $(start)
db-reset: ## ⚠️ Reset SQLite database (destructive — dev only)
@echo "$(RED)⚠ Resetting database...$(NC)"
rm -f db.sqlite3
uv run manage.py makemigrations
uv run manage.py migrate
@echo "$(GREEN)✓ Database reset complete$(NC)"
db-shell: ## Open database shell (dbshell)
uv run manage.py dbshell
# ═══════════════════════════════════════════════════════════════
##@ Admin & Users
# ═══════════════════════════════════════════════════════════════
superuser: ## Create a Django superuser (interactive — uses UnifiedUser via correct settings)
uv run manage.py createsuperuser --settings=$(DJANGO_SETTINGS_MODULE)
su: ## Create UnifiedUser superuser non-interactively (make su EMAIL=x PASS=y)
uv run scripts/create_superuser.py "$(EMAIL)" "$(PASS)"
changepass: ## Change a UnifiedUser password (uses correct settings)
uv run manage.py changepassword --settings=$(DJANGO_SETTINGS_MODULE)
static: ## Collect static files
@echo "$(CYAN)Collecting static files...$(NC)"
uv run manage.py collectstatic --noinput
@echo "$(GREEN)✓ Static files collected$(NC)"
# ═══════════════════════════════════════════════════════════════
##@ Code Quality & Linting
# ═══════════════════════════════════════════════════════════════
lint: ## Run Ruff linter on the entire project
@echo "$(CYAN)Running Ruff linter...$(NC)"
uv run ruff check . --fix
@echo "$(GREEN)✓ Linting complete$(NC)"
format: ## Format code with Ruff formatter
@echo "$(CYAN)Formatting code...$(NC)"
uv run ruff format .
@echo "$(GREEN)✓ Code formatted$(NC)"
type-check: ## Run mypy static type checking
@echo "$(CYAN)Running mypy type check...$(NC)"
uv run mypy apps/ --ignore-missing-imports
@echo "$(GREEN)✓ Type check passed$(NC)"
quality: lint format type-check ## Run all code quality checks (lint + format + types)
@echo "$(GREEN)✓ All quality checks passed$(NC)"
# ═══════════════════════════════════════════════════════════════
##@ Testing
# ═══════════════════════════════════════════════════════════════
test: ## Run full test suite with pytest
@echo "$(CYAN)Running tests...$(NC)"
uv run pytest --disable-warnings -vv -x
@echo "$(GREEN)✓ Tests passed$(NC)"
test-cov: ## Run tests with HTML coverage report
@echo "$(CYAN)Running tests with coverage...$(NC)"
uv run pytest --cov=apps --cov-report=html --cov-report=term-missing -vv
@echo "$(GREEN)✓ Coverage report generated → htmlcov/index.html$(NC)"
test-fast: ## Run tests without warnings (fast mode)
uv run pytest --disable-warnings -q
test-app: ## Run tests for a specific app (usage: make test-app app=authentication)
uv run pytest apps/$(app)/ -vv
test-watch: ## Run tests in watch mode (requires pytest-watch)
uv run ptw -- --disable-warnings -vv
# ═══════════════════════════════════════════════════════════════
##@ Celery & Background Tasks
# ═══════════════════════════════════════════════════════════════
celery: ## Start Celery worker — general queue (dev settings, console email)
@echo "$(CYAN)Ensuring Redis is running ...$(NC)"
@$(ENSURE_TMP_REDIS)
@echo "$(CYAN)Starting Celery worker (DJANGO_SETTINGS_MODULE=development)...$(NC)"
@echo "$(YELLOW) Pool: solo (Windows-safe: no prefork shared memory)&$(NC)"
@echo "$(YELLOW) Email: console.EmailBackend (OTP printed to THIS terminal)$(NC)"
uv run celery -A backend worker --loglevel=info --pool=solo --events
celery-emails: ## Start Celery worker for email queue (dev, console email visible)
@$(ENSURE_TMP_REDIS)
uv run celery -A backend worker -Q emails --loglevel=info --pool=solo
celery-critical: ## Start Celery worker for critical queue (auto-starts Redis)
@$(ENSURE_TMP_REDIS)
uv run celery -A backend worker -Q critical --loglevel=info --concurrency=2
celery-analytics: ## Start Celery worker for analytics queue
uv run celery -A backend worker -Q analytics --loglevel=info --concurrency=1
celery-beat: ## Start Celery Beat scheduler
uv run celery -A backend beat --loglevel=info --scheduler django_celery_beat.schedulers:DatabaseScheduler
flower: ## Start Flower monitoring dashboard (port 5555)
@echo "$(CYAN)Starting Flower at http://localhost:5555$(NC)"
uv run celery -A backend flower --port=5555
purge-tasks: ## ⚠️ Purge all queued Celery tasks
@echo "$(RED)⚠ Purging all queued tasks...$(NC)"
uv run celery -A backend purge -f
inspect-active: ## Inspect currently active Celery tasks
uv run celery -A backend inspect active
inspect-stats: ## Show Celery worker statistics
uv run celery -A backend inspect stats
start-workers: ## Display instructions to start all workers
@echo "$(BOLD)$(CYAN)Start each in a separate terminal:$(NC)"
@echo " $(CYAN)Terminal 1:$(NC) make celery"
@echo " $(CYAN)Terminal 2:$(NC) make celery-emails"
@echo " $(CYAN)Terminal 3:$(NC) make celery-critical"
@echo " $(CYAN)Terminal 4:$(NC) make celery-beat"
@echo " $(CYAN)Terminal 5:$(NC) make flower"
# ═══════════════════════════════════════════════════════════════
##@ Docker — Development
# ═══════════════════════════════════════════════════════════════
docker-build: ## Build Docker image (no cache)
@echo "$(CYAN)Building Docker image...$(NC)"
docker-compose -f ../docker-compose.yml build --no-cache
@echo "$(GREEN)✓ Docker image built$(NC)"
docker-up: ## Start development containers (detached)
@echo "$(CYAN)Starting Docker containers...$(NC)"
docker-compose -f ../docker-compose.yml up -d
@echo "$(GREEN)✓ Containers started$(NC)"
docker-down: ## Stop and remove containers
@echo "$(YELLOW)Stopping containers...$(NC)"
docker-compose -f ../docker-compose.yml down
@echo "$(GREEN)✓ Containers stopped$(NC)"
docker-down-v: ## Stop containers and remove volumes (⚠️ data loss)
@echo "$(RED)⚠ Stopping containers and removing volumes...$(NC)"
docker-compose -f ../docker-compose.yml down -v
docker-restart: docker-down docker-up ## Restart all containers
docker-logs: ## Tail container logs (all services)
docker-compose -f ../docker-compose.yml logs -f
docker-logs-web: ## Tail logs for web service only
docker-compose -f ../docker-compose.yml logs -f web
docker-logs-celery: ## Tail logs for Celery workers
docker-compose -f ../docker-compose.yml logs -f celery-worker celery-beat
docker-ps: ## Show running containers
docker-compose -f ../docker-compose.yml ps
docker-exec: ## Open shell in web container
docker-compose -f ../docker-compose.yml exec web /bin/sh
docker-exec-db: ## Open PostgreSQL shell in db container
docker-compose -f ../docker-compose.yml exec db psql -U $${DB_USER:-postgres} -d $${DB_NAME:-fashionistar}
docker-rebuild: ## Full rebuild (stop → clean → build → start)
@echo "$(CYAN)Full Docker rebuild...$(NC)"
docker-compose -f ../docker-compose.yml down -v
docker-compose -f ../docker-compose.yml build --no-cache
docker-compose -f ../docker-compose.yml up -d
@echo "$(GREEN)✓ Full rebuild complete$(NC)"
docker-compose -f ../docker-compose.yml logs -f
# ═══════════════════════════════════════════════════════════════
##@ Docker — Production
# ═══════════════════════════════════════════════════════════════
prod-up: ## Start production environment
@echo "$(CYAN)Starting production environment...$(NC)"
docker-compose -f ../docker-compose.prod.yml up -d --build
@echo "$(GREEN)✓ Production environment started$(NC)"
prod-down: ## Stop production environment
docker-compose -f ../docker-compose.prod.yml down
prod-logs: ## Tail production logs
docker-compose -f ../docker-compose.prod.yml logs -f
prod-restart: prod-down prod-up ## Restart production environment
# ═══════════════════════════════════════════════════════════════
##@ Infrastructure
# ═══════════════════════════════════════════════════════════════
infra-up: ## Start Redis + PostgreSQL locally via Docker
@echo "$(CYAN)Starting infrastructure services...$(NC)"
docker run -d --name fashionistar-redis -p 6379:6379 redis:7-alpine || echo "$(YELLOW)Redis already running$(NC)"
docker run -d --name fashionistar-postgres -p 5432:5432 \
-e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=postgres -e POSTGRES_DB=fashionistar \
postgres:17-alpine || echo "$(YELLOW)PostgreSQL already running$(NC)"
@echo "$(GREEN)✓ Infrastructure ready$(NC)"
infra-down: ## Stop infrastructure containers
docker stop fashionistar-redis fashionistar-postgres 2>/dev/null || true
docker rm fashionistar-redis fashionistar-postgres 2>/dev/null || true
@echo "$(GREEN)✓ Infrastructure stopped$(NC)"
# ═══════════════════════════════════════════════════════════════
##@ Local Redis (Windows Portable)
# ═══════════════════════════════════════════════════════════════
start-redis: ## Start local portable Redis server on port 6379 (background)
@echo "$(CYAN)Starting local portable Redis server...$(NC)"
@$(ENSURE_TMP_REDIS)
@echo "$(GREEN)✓ Redis start command issued for 127.0.0.1:6379$(NC)"
stop-redis: ## Stop local portable Redis server
@echo "$(CYAN)Stopping local portable Redis server...$(NC)"
taskkill /F /IM redis-server.exe /T 2>NUL || echo "$(YELLOW)Redis is not running$(NC)"
@echo "$(GREEN)✓ Redis stopped$(NC)"
# ═══════════════════════════════════════════════════════════════
##@ Health Checks & Monitoring
# ═══════════════════════════════════════════════════════════════
health: ## Check API health endpoint
@echo "$(CYAN)Checking system health...$(NC)"
@curl -sf http://localhost:8001/health/ | uv run python -m json.tool 2>/dev/null || echo "$(RED)✗ API not running on port 8001$(NC)"
health-redis: ## Check Redis connectivity
@echo "$(CYAN)Checking Redis...$(NC)"
@uv run python -c "import redis; r = redis.from_url('$${REDIS_URL:-redis://localhost:6379/0}'); r.ping(); print('\033[0;32m✓ Redis connected\033[0m')" 2>/dev/null || echo "$(RED)✗ Redis not available$(NC)"
test-metrics: ## Check Prometheus metrics endpoint
@echo "$(CYAN)Testing metrics...$(NC)"
@curl -sf http://localhost:8001/metrics/ | head -10 || echo "$(RED)✗ Metrics endpoint not available$(NC)"
# ═══════════════════════════════════════════════════════════════
##@ CI/CD Pipeline
# ═══════════════════════════════════════════════════════════════
ci: quality test ## Run full CI pipeline (lint + format + types + tests)
@echo "$(GREEN)✓ CI pipeline passed$(NC)"
ci-fast: lint test-fast ## Run fast CI pipeline (lint + quick tests)
@echo "$(GREEN)✓ Fast CI pipeline passed$(NC)"
pre-commit: quality ## Pre-commit hook: run all quality checks
@echo "$(GREEN)✓ Pre-commit checks passed$(NC)"
# ═══════════════════════════════════════════════════════════════
##@ Cleanup
# ═══════════════════════════════════════════════════════════════
clean: ## Remove Python cache files (.pyc, __pycache__)
@echo "$(YELLOW)Cleaning Python cache...$(NC)"
find . -type f -name '*.pyc' -delete
find . -type d -name '__pycache__' -exec rm -rf {} + 2>/dev/null || true
find . -type d -name '.mypy_cache' -exec rm -rf {} + 2>/dev/null || true
find . -type d -name '.pytest_cache' -exec rm -rf {} + 2>/dev/null || true
rm -rf htmlcov/ .coverage
@echo "$(GREEN)✓ Cleaned$(NC)"
clean-docker: ## Remove all Docker containers, images, and volumes
@echo "$(RED)⚠ Cleaning all Docker resources...$(NC)"
docker-compose down -v
docker rmi $$(docker images -q --filter "reference=fashionistar*") 2>/dev/null || true
@echo "$(GREEN)✓ Docker cleaned$(NC)"
clean-all: clean clean-docker ## Nuclear clean (Python cache + Docker)
@echo "$(GREEN)✓ Everything cleaned$(NC)"
# ═══════════════════════════════════════════════════════════════
##@ Environment & Configuration
# ═══════════════════════════════════════════════════════════════
env-setup: ## Create .env from .env.example (safe — won't overwrite)
@if [ ! -f .env ]; then \
cp .env.example .env; \
echo "$(GREEN)✓ Created .env from .env.example — edit with your secrets$(NC)"; \
else \
echo "$(YELLOW)⚠ .env already exists — skipped$(NC)"; \
fi
env-check: ## Display current environment configuration
@echo "$(CYAN)Current environment:$(NC)"
@if [ -f .env ]; then \
grep -v '^\s*#' .env | grep -v '^\s*$$' | sed 's/=.*/=***/' ; \
else \
echo "$(RED)✗ .env not found — run 'make env-setup'$(NC)"; \
fi
# ═══════════════════════════════════════════════════════════════
##@ Project Information
# ═══════════════════════════════════════════════════════════════
info: ## Display project information
@echo "$(BOLD)$(CYAN)━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━$(NC)"
@echo "$(BOLD) FASHIONISTAR AI — Backend$(NC)"
@echo "$(CYAN)━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━$(NC)"
@echo " Python: $$(uv run python --version 2>&1)"
@echo " Django: $$(uv run python -c 'import django; print(django.VERSION)' 2>/dev/null || echo 'not installed')"
@echo " Architecture: Dual-Engine (DRF Sync + Ninja Async)"
@echo " Database: PostgreSQL 17 / SQLite (dev)"
@echo " Cache: Redis"
@echo " Task Engine: Celery → Django 6.0 Native Tasks"
@echo " API Docs: http://localhost:8001/swagger/"
@echo "$(CYAN)━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━$(NC)"
urls: ## Display key API endpoints
@echo "$(BOLD)$(CYAN)Key Endpoints:$(NC)"
@echo " $(CYAN)API Root:$(NC) http://localhost:8001/api/"
@echo " $(CYAN)Swagger:$(NC) http://localhost:8001/swagger/"
@echo " $(CYAN)ReDoc:$(NC) http://localhost:8001/redoc/"
@echo " $(CYAN)Admin:$(NC) http://localhost:8001/$${DJANGO_SECRET_ADMIN_URL:-admin/}"
@echo " $(CYAN)Ninja Async:$(NC) http://localhost:8001/api/v1/ninja/"
deps: ## List installed Python packages
uv pip list --format=columns
outdated: ## Check for outdated Python packages
uv pip list --outdated
req-update: ## Freeze current packages to requirements.txt
@echo "$(YELLOW)⚠ Updating requirements.txt from installed packages...$(NC)"
uv pip freeze > requirements.txt
@echo "$(GREEN)✓ requirements.txt updated$(NC)"
# ═══════════════════════════════════════════════════════════════
##@ Quick Commands
# ═══════════════════════════════════════════════════════════════
quick-start: env-setup install install-dev migrate static dev ## 🚀 First-time setup → run
quick-docker: docker-build docker-up ## 🐳 Build and start Docker
quick-test: lint test-cov ## 🧪 Lint + test with coverage
full-reset: clean db-reset install migrate static ## 🔄 Nuclear reset → fresh start
@echo "$(GREEN)✓ Full reset complete — run 'make dev' to start$(NC)"
dashboards: ## 📊 Show all service URLs
@echo "$(BOLD)$(CYAN)━━━ Service Dashboards ━━━$(NC)"
@echo " $(CYAN)Django API:$(NC) http://localhost:8001"
@echo " $(CYAN)Swagger UI:$(NC) http://localhost:8001/swagger/"
@echo " $(CYAN)Admin:$(NC) http://localhost:8001/$${DJANGO_SECRET_ADMIN_URL:-admin/}"
@echo " $(CYAN)Flower:$(NC) http://localhost:5555"
@echo " $(CYAN)Prometheus:$(NC) http://localhost:9090"
@echo " $(CYAN)Grafana:$(NC) http://localhost:3000"
# ═══════════════════════════════════════════════════════════════
##@ ngrok — Public Tunnels (Cloudinary Webhooks / Mobile Testing)
# ═══════════════════════════════════════════════════════════════
## ngrok launcher — Directly targets your installed system ngrok (v3.39+)
## Bypasses outdated pnpm wrappers to ensure flawless Windows/Agent compatibility.
NGROK ?= ngrok
NGROK_WEB_ADDR ?= 127.0.0.1:4040
NGROK_URL ?= 127.0.0.1:8001
ngrok-backend: ## 🌐 🔗 Start ngrok tunnel for backend port 8001 → localhost:8001 (Django ASGI/dev)
@echo "$(CYAN)Starting ngrok tunnel for backend port 8001 → http://$(NGROK_URL) ...$(NC)"
@echo "$(YELLOW) Webhook URL: https://<tunnel>.ngrok-free.app/api/v1/upload/webhook/cloudinary/$(NC)"
@echo "$(YELLOW) ngrok Web UI: http://$(NGROK_WEB_ADDR)$(NC)"
@echo "$(YELLOW) Authtoken: global (stored locally)$(NC)"
@echo "$(YELLOW) Dev server must be running: make uvicorn (port 8001)$(NC)"
$(NGROK) http 8001
ngrok-dev: ## 🌐 🔗 Start ngrok tunnel for backend port 8001 → localhost:8001 (Alias for ngrok-backend)
@echo "$(CYAN)Starting ngrok tunnel for backend port 8001 → http://$(NGROK_URL) ...$(NC)"
@echo "$(YELLOW) Webhook URL: https://<tunnel>.ngrok-free.app/api/v1/upload/webhook/cloudinary/$(NC)"
@echo "$(YELLOW) ngrok Web UI: http://$(NGROK_WEB_ADDR)$(NC)"
@echo "$(YELLOW) Authtoken: global (stored locally)$(NC)"
@echo "$(YELLOW) Dev server must be running: make uvicorn (port 8001)$(NC)"
$(NGROK) http 8001
ngrok-asgi: ## 🔗 🌐 Start ngrok tunnel for backend port 8001 → localhost:8001 (Django ASGI/dev)
@echo "$(CYAN)Starting ngrok tunnel → http://$(NGROK_URL) (ASGI) ...$(NC)"
@echo "$(YELLOW) Webhook URL: https://<tunnel>.ngrok-free.app/api/v1/upload/webhook/cloudinary/$(NC)"
@echo "$(YELLOW) ngrok Web UI: http://$(NGROK_WEB_ADDR)$(NC)"
@echo "$(YELLOW) Authtoken: global (stored locally)$(NC)"
@echo "$(YELLOW) Dev server must be running: make run-asgi (port 8001)$(NC)"
$(NGROK) http 8001
ngrok-url: ## 🔍 Print current active backend ngrok tunnel public URL (inspector API)
@echo "$(CYAN)Active backend ngrok tunnels:$(NC)"
@curl -s http://127.0.0.1:4040/api/tunnels 2>/dev/null | \
uv run python -c "import sys,json; d=json.load(sys.stdin); [print(' ✔ ' + t['public_url'] + ' -> port 8001:' + t['config']['addr']) for t in d.get('tunnels',[])]" \
|| echo "$(RED)✗ ngrok not running — start with / Run: 'make ngrok-backend' or 'make ngrok-asgi'$(NC)"
ngrok-inspect: ## 🔍 Open ngrok web inspector in browser (localhost:4040)
@echo "$(CYAN)ngrok Web Inspector: http://127.0.0.1:4040$(NC)"
@start http://127.0.0.1:4040 2>/dev/null || xdg-open http://127.0.0.1:4040 2>/dev/null || echo "Open: http://127.0.0.1:4040"
# ═══════════════════════════════════════════════════════════════
##@ Local Server Startup Commands
# ═══════════════════════════════════════════════════════════════
dev: ## Start Django development server (sync WSGI — port 8001, console email)
@echo "$(CYAN)Starting Django dev server (WSGI, DEBUG=True, ConsoleEmail)...$(NC)"
@echo "$(YELLOW) Settings: backend.config.development$(NC)"
@echo "$(YELLOW) Email: console (OTP printed to this terminal)$(NC)"
@echo "$(YELLOW) URL: http://127.0.0.1:8001/$(NC)"
uv run manage.py runserver 0.0.0.0:8001 --settings=$(DJANGO_SETTINGS_MODULE)
uvicorn: ## Start Uvicorn ASGI (dev, port 8001, console email, access logs)
@echo "$(CYAN)Starting Uvicorn ASGI server (development settings)...$(NC)"
@$(ENSURE_TMP_REDIS)
@echo "$(YELLOW) Settings: backend.config.development (ALLOWED_HOSTS=*)$(NC)"
@echo "$(YELLOW) URL: http://127.0.0.1:8001/ or http://localhost:8001/$(NC)"
@echo "$(YELLOW) Logs: Access log printed here (ALL requests — 2xx, 4xx, 5xx)$(NC)"
uv run uvicorn backend.asgi:application --host 0.0.0.0 --port 8001 --reload --ws auto --log-config uvicorn_log_config.json
run-asgi: ## Start ASGI + Uvicorn (auto-starts Redis first)
@echo "$(CYAN)Ensuring Redis is running ...$(NC)"
@$(ENSURE_TMP_REDIS)
@echo "$(CYAN)Starting Uvicorn ASGI server (access logs on)...$(NC)"
uv run uvicorn backend.asgi:application --host 0.0.0.0 --port 8001 --reload --ws auto --log-config uvicorn_log_config.json
run-daphne: ## Start Daphne ASGI (WebSocket — auto-starts Redis first)
@echo "$(CYAN)Ensuring Redis is running ...$(NC)"
@$(ENSURE_TMP_REDIS)
@echo "$(CYAN)Starting Daphne ASGI server (development settings)...$(NC)"
uv run daphne -b 0.0.0.0 -p 8001 backend.asgi:application
# ═══════════════════════════════════════════════════════════════
##@ Server & Tunnel Integration Commands
# ═══════════════════════════════════════════════════════════════
# These cleanly mapped commands handle starting your Django/Uvicorn
# backend alongside ngrok tunnels. Use these to securely expose your
# local environment to the internet for Webhooks or frontend testing.
# ═══════════════════════════════════════════════════════════════
dev-tunnel: ## 🚀 Start Django WSGI (:8001) dev server & display tunnel instructions
@echo "$(BOLD)$(CYAN)━━━ FASHIONISTAR WSGI + Tunnel ━━━$(NC)"
@echo "$(CYAN)Step 1:$(NC) Starting Django WSGI dev server → :8001 ..."
uv run manage.py runserver 0.0.0.0:8001 --settings=$(DJANGO_SETTINGS_MODULE) &
@echo ""
@echo "$(YELLOW)Step 2 (Action Required): Open a second terminal window and run:$(NC)"
@echo " $(CYAN)make ngrok-dev$(NC)"
asgi-tunnel: ## 🚀 Start Uvicorn ASGI (:8001) server & display tunnel instructions
@echo "$(BOLD)$(CYAN)━━━ FASHIONISTAR ASGI + Tunnel ━━━$(NC)"
@echo "$(CYAN)Step 1:$(NC) Starting Uvicorn ASGI server → :8001 ..."
@$(ENSURE_TMP_REDIS)
@echo ""
@echo "$(YELLOW)Step 2 (Action Required): Open a second terminal window and run:$(NC)"
@echo " $(CYAN)make ngrok-asgi$(NC)"
uv run uvicorn backend.asgi:application --host 0.0.0.0 --port 8001 --reload --ws auto --log-config uvicorn_log_config.json
# ─────────────────────────────────────────────────────────────────
# Sequential Execution Alternatives
# (Runs both processes gracefully in the exact same terminal window)
# ─────────────────────────────────────────────────────────────────
run-dev-then-ngrok: ## 🚀 Sequential: Start Django WSGI (:8001) → then start ngrok
@echo "$(CYAN)Starting Django dev server ...$(NC)"
uv run manage.py runserver 0.0.0.0:8001 --settings=$(DJANGO_SETTINGS_MODULE)
run-ngrok-then-dev: ## 🚀 Sequential: Start ngrok tunnel → then start Django WSGI (:8001)
@echo "$(CYAN)Starting ngrok tunnel → :8001 ...$(NC)"
$(NGROK) http 8001
@echo ""
@echo "$(CYAN)Starting Django dev server ...$(NC)"
uv run manage.py runserver 0.0.0.0:8001 --settings=$(DJANGO_SETTINGS_MODULE)
run-asgi-then-ngrok: ## 🚀 Sequential: Start Uvicorn ASGI (:8001) → then start ngrok
@echo "$(CYAN)Starting Uvicorn ASGI server → :8001 ...$(NC)"
@$(ENSURE_TMP_REDIS)
uv run uvicorn backend.asgi:application --host 0.0.0.0 --port 8001 --reload --ws auto --log-config uvicorn_log_config.json
run-ngrok-then-asgi: ## 🚀 Sequential: Start ngrok tunnel → then start Uvicorn ASGI (:8001)
@echo "$(CYAN)Starting ngrok tunnel → :8001 ...$(NC)"
$(NGROK) http 8001
@echo ""
@echo "$(CYAN)Starting Uvicorn ASGI server ...$(NC)"
@$(ENSURE_TMP_REDIS)
uv run uvicorn backend.asgi:application --host 0.0.0.0 --port 8001 --reload --ws auto --log-config uvicorn_log_config.json
# ═══════════════════════════════════════════════════════════════
##@ Alternative Aliases & Production Start
# ═══════════════════════════════════════════════════════════════
run-dev: dev ## Alias: Start Django dev server (same as make dev)
run-uvicorn: uvicorn ## Alias: Start Uvicorn ASGI server (same as make uvicorn)
asgi: run-asgi ## Alias: Start ASGI server with auto-Redis (same as make run-asgi)
daphne: run-daphne ## Alias: Start Daphne ASGI with auto-Redis (same as make run-daphne)
wsgi: ## 🚀 Start Gunicorn WSGI server (sync production — port 8001)
@echo "$(CYAN)Starting Gunicorn WSGI server (Production Mode)...$(NC)"
uv run gunicorn backend.wsgi:application --bind 0.0.0.0:8001 --workers 4 --timeout 120