diff --git a/.env.example b/.env.example index be1af5b..b40ebae 100644 --- a/.env.example +++ b/.env.example @@ -1,23 +1,23 @@ -# Django Settings -DJANGO_SECRET_KEY=django-insecure-your-secret-key-here -DJANGO_DEBUG=True -ALLOWED_HOSTS=localhost,127.0.0.1,* +# Django Production Settings +DJANGO_SECRET_KEY=generate-a-strong-random-secret-key-here +DJANGO_DEBUG=False +ALLOWED_HOSTS=* CORS_ALLOW_ALL_ORIGINS=True -CORS_ALLOWED_ORIGINS=http://localhost:3000,http://localhost:5173 +CSRF_TRUSTED_ORIGINS=http://localhost,http://127.0.0.1,http://YOUR_SERVER_IP # Database Configuration (PostgreSQL) POSTGRES_DB=divar_crawler POSTGRES_USER=postgres -POSTGRES_PASSWORD=postgres -POSTGRES_HOST=localhost +POSTGRES_PASSWORD=your_secure_postgres_password +POSTGRES_HOST=db POSTGRES_PORT=5432 # Redis & Celery -REDIS_URL=redis://localhost:6379/0 +REDIS_URL=redis://redis:6379/0 -# LLM Config (OpenAI / Gemini) -OPENAI_API_KEY=your-openai-api-key-here -# Optional: LLM_MODEL_NAME=gpt-4o-mini +# LLM Config (OpenAI / OpenRouter) +OPENAI_API_KEY=sk-or-v1-your-openrouter-key +OPENAI_MODEL=google/gemma-4-26b-a4b-it:free # Telegram Config TELEGRAM_BOT_TOKEN=your-telegram-bot-token-here diff --git a/backend/config/settings.py b/backend/config/settings.py index 6e8a410..97ab6c7 100644 --- a/backend/config/settings.py +++ b/backend/config/settings.py @@ -57,6 +57,7 @@ INSTALLED_APPS = [ MIDDLEWARE = [ 'corsheaders.middleware.CorsMiddleware', 'django.middleware.security.SecurityMiddleware', + 'whitenoise.middleware.WhiteNoiseMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', @@ -147,19 +148,32 @@ USE_TZ = True # https://docs.djangoproject.com/en/6.0/howto/static-files/ STATIC_URL = 'static/' +STATIC_ROOT = BASE_DIR / 'staticfiles' STATICFILES_DIRS = [ BASE_DIR.parent / 'frontend' / 'dist', -] +] if (BASE_DIR.parent / 'frontend' / 'dist').exists() else [] + +STORAGES = { + "default": { + "BACKEND": "django.db.models.fields.files.Storage", + }, + "staticfiles": { + "BACKEND": "whitenoise.storage.CompressedManifestStaticFilesStorage", + }, +} # Default primary key field type # https://docs.djangoproject.com/en/6.0/ref/settings/#default-auto-field DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' -# CORS Settings +# CORS & CSRF Settings CORS_ALLOW_ALL_ORIGINS = os.getenv('CORS_ALLOW_ALL_ORIGINS', 'True').lower() in ('true', '1', 't') if not CORS_ALLOW_ALL_ORIGINS: CORS_ALLOWED_ORIGINS = [origin.strip() for origin in os.getenv('CORS_ALLOWED_ORIGINS', '').split(',') if origin.strip()] +csrf_origins = os.getenv('CSRF_TRUSTED_ORIGINS', 'http://localhost,http://127.0.0.1,http://localhost:5173,http://localhost:8000') +CSRF_TRUSTED_ORIGINS = [origin.strip() for origin in csrf_origins.split(',') if origin.strip()] + # Django REST Framework Settings REST_FRAMEWORK = { 'DEFAULT_PERMISSION_CLASSES': [ diff --git a/docker-compose.yml b/docker-compose.yml index b8961d3..d91c587 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,7 +2,7 @@ services: db: image: postgres:16-alpine container_name: divar_postgres - restart: always + restart: unless-stopped environment: POSTGRES_DB: ${POSTGRES_DB:-divar_crawler} POSTGRES_USER: ${POSTGRES_USER:-postgres} @@ -20,6 +20,7 @@ services: redis: image: redis:7-alpine container_name: divar_redis + restart: unless-stopped ports: - "6379:6379" healthcheck: @@ -33,7 +34,8 @@ services: context: . dockerfile: Dockerfile.backend container_name: divar_backend - command: python backend/manage.py runserver 0.0.0.0:8000 + restart: unless-stopped + command: gunicorn config.wsgi:application --bind 0.0.0.0:8000 --workers 4 ports: - "8000:8000" environment: @@ -55,6 +57,7 @@ services: context: . dockerfile: Dockerfile.backend container_name: divar_celery + restart: unless-stopped command: celery -A config worker --loglevel=info -P gevent -c 20 environment: - REDIS_URL=redis://redis:6379/0 @@ -75,6 +78,7 @@ services: context: . dockerfile: Dockerfile.backend container_name: divar_celery_beat + restart: unless-stopped command: celery -A config beat --loglevel=info --scheduler django_celery_beat.schedulers:DatabaseScheduler environment: - REDIS_URL=redis://redis:6379/0 @@ -95,13 +99,10 @@ services: context: . dockerfile: frontend/Dockerfile container_name: divar_frontend + restart: unless-stopped ports: - - "5173:5173" - environment: - - VITE_BACKEND_URL=http://backend:8000 - volumes: - - ./frontend:/app/frontend - - /app/frontend/node_modules + - "80:80" + - "5173:80" depends_on: - backend diff --git a/entrypoint.sh b/entrypoint.sh index 27bf202..ec92701 100644 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -25,10 +25,12 @@ except Exception: echo "PostgreSQL database is ready!" fi -# Automatically run Django migrations before starting backend server -if [ "$1" = "python" ] && [ "$2" = "backend/manage.py" ] && [ "$3" = "runserver" ]; then +# Automatically run Django migrations and collectstatic before starting backend server +if [ "$1" = "gunicorn" ] || ([ "$1" = "python" ] && [ "$2" = "backend/manage.py" ]); then echo "Running Django migrations..." python backend/manage.py migrate --noinput + echo "Collecting static files..." + python backend/manage.py collectstatic --noinput --clear || true fi exec "$@" diff --git a/frontend/Dockerfile b/frontend/Dockerfile index ac27cb3..149cdef 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -1,12 +1,20 @@ -FROM node:22-slim +# Stage 1: Build Vite frontend +FROM node:22-alpine AS build WORKDIR /app/frontend COPY frontend/package*.json ./ -RUN npm install +RUN npm ci || npm install COPY frontend ./ +RUN npm run build -EXPOSE 5173 +# Stage 2: Production Nginx server +FROM nginx:alpine -CMD ["npm", "run", "dev", "--", "--host", "0.0.0.0"] +COPY --from=build /app/frontend/dist /usr/share/nginx/html +COPY frontend/nginx.conf /etc/nginx/conf.d/default.conf + +EXPOSE 80 + +CMD ["nginx", "-g", "daemon off;"] diff --git a/frontend/nginx.conf b/frontend/nginx.conf new file mode 100644 index 0000000..708b711 --- /dev/null +++ b/frontend/nginx.conf @@ -0,0 +1,43 @@ +server { + listen 80; + server_name _; + + root /usr/share/nginx/html; + index index.html; + + # Gzip compression for fast asset loading + gzip on; + gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript; + + # Frontend SPA routing + location / { + try_files $uri $uri/ /index.html; + } + + # Proxy Django API requests + location /api/ { + proxy_pass http://backend:8000; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + # Proxy Django Admin interface + location /admin/ { + proxy_pass http://backend:8000; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + # Proxy Django static files (admin assets, etc.) + location /static/ { + proxy_pass http://backend:8000; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } +} diff --git a/requirements.txt b/requirements.txt index 33a8f19..4e65b68 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,4 +11,5 @@ openai>=2.53.0,<2.54 pydantic>=2.13.4,<2.14 python-dotenv>=1.2.2,<1.3 gevent>=24.2.0,<25.0 - +gunicorn>=22.0.0,<23.0 +whitenoise>=6.6.0,<7.0