diff --git a/client/Dockerfile b/client/Dockerfile index 2f21192..cf1ec97 100644 --- a/client/Dockerfile +++ b/client/Dockerfile @@ -36,8 +36,8 @@ RUN apk add --no-cache curl # Copy built application from builder stage COPY --from=builder /app/dist /usr/share/nginx/html -# Copy custom nginx configuration -COPY docker/nginx/default.conf /etc/nginx/conf.d/default.conf +# Copy nginx configuration +COPY nginx.conf /etc/nginx/conf.d/default.conf # Create nginx user and set permissions RUN chown -R nginx:nginx /usr/share/nginx/html && \ diff --git a/client/nginx.conf b/client/nginx.conf new file mode 100644 index 0000000..7e9c8a8 --- /dev/null +++ b/client/nginx.conf @@ -0,0 +1,48 @@ +server { + listen 80; + server_name localhost; + root /usr/share/nginx/html; + index index.html; + + # Security headers + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-Content-Type-Options "nosniff" always; + add_header X-XSS-Protection "1; mode=block" always; + + # Gzip compression + gzip on; + gzip_vary on; + gzip_min_length 1024; + gzip_types + text/plain + text/css + text/xml + text/javascript + application/javascript + application/json; + + # Handle React Router (SPA) + location / { + try_files $uri $uri/ /index.html; + + # Cache static assets + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + } + } + + # Health check endpoint + location /health { + access_log off; + return 200 "healthy\n"; + add_header Content-Type text/plain; + } + + # Deny access to hidden files + location ~ /\. { + deny all; + access_log off; + log_not_found off; + } +} diff --git a/docker-compose.simple.yml b/docker-compose.simple.yml new file mode 100644 index 0000000..74237d1 --- /dev/null +++ b/docker-compose.simple.yml @@ -0,0 +1,130 @@ +version: '3.8' + +services: + # PostgreSQL Database + postgres: + image: postgres:15-alpine + container_name: drone-detection-db + restart: unless-stopped + environment: + POSTGRES_DB: drone_detection + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres123 + PGDATA: /var/lib/postgresql/data/pgdata + volumes: + - postgres_data:/var/lib/postgresql/data + ports: + - "5432:5432" + networks: + - drone-network + healthcheck: + test: ["CMD-SHELL", "pg_isready -U postgres -d drone_detection"] + interval: 30s + timeout: 10s + retries: 3 + + # Redis for session management and caching + redis: + image: redis:7-alpine + container_name: drone-detection-redis + restart: unless-stopped + command: redis-server --appendonly yes + volumes: + - redis_data:/data + ports: + - "6379:6379" + networks: + - drone-network + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 30s + timeout: 10s + retries: 3 + + # Backend API Server + backend: + build: + context: ./server + dockerfile: Dockerfile + container_name: drone-detection-backend + restart: unless-stopped + environment: + NODE_ENV: production + PORT: 3001 + DB_HOST: postgres + DB_PORT: 5432 + DB_NAME: drone_detection + DB_USER: postgres + DB_PASSWORD: postgres123 + REDIS_HOST: redis + REDIS_PORT: 6379 + JWT_SECRET: ${JWT_SECRET:-your-super-secret-jwt-key-change-in-production} + TWILIO_ACCOUNT_SID: ${TWILIO_ACCOUNT_SID} + TWILIO_AUTH_TOKEN: ${TWILIO_AUTH_TOKEN} + TWILIO_PHONE_NUMBER: ${TWILIO_PHONE_NUMBER} + CORS_ORIGIN: http://localhost:3000 + ports: + - "3001:3001" + volumes: + - ./server/logs:/app/logs + networks: + - drone-network + depends_on: + postgres: + condition: service_healthy + redis: + condition: service_healthy + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:3001/api/health"] + interval: 30s + timeout: 10s + retries: 3 + + # Frontend React Application + frontend: + build: + context: ./client + dockerfile: Dockerfile + args: + VITE_API_URL: http://localhost:3001/api + VITE_WS_URL: ws://localhost:3001 + container_name: drone-detection-frontend + restart: unless-stopped + ports: + - "3000:80" + networks: + - drone-network + depends_on: + - backend + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:80/health"] + interval: 30s + timeout: 10s + retries: 3 + + # Python Drone Simulator (Optional) + simulator: + build: + context: . + dockerfile: docker/simulator/Dockerfile + container_name: drone-detection-simulator + restart: "no" + environment: + API_URL: http://backend:3001/api + networks: + - drone-network + depends_on: + - backend + profiles: + - simulation + command: python drone_simulator.py --api-url http://backend:3001/api --devices 5 --duration 3600 + +volumes: + postgres_data: + driver: local + redis_data: + driver: local + +networks: + drone-network: + driver: bridge diff --git a/server/routes/index.js b/server/routes/index.js index eb266f4..892c4d4 100644 --- a/server/routes/index.js +++ b/server/routes/index.js @@ -8,6 +8,7 @@ const deviceRoutes = require('./device'); const userRoutes = require('./user'); const alertRoutes = require('./alert'); const dashboardRoutes = require('./dashboard'); +const healthRoutes = require('./health'); // API versioning router.use('/v1/detections', droneDetectionRoutes); @@ -16,6 +17,7 @@ router.use('/v1/devices', deviceRoutes); router.use('/v1/users', userRoutes); router.use('/v1/alerts', alertRoutes); router.use('/v1/dashboard', dashboardRoutes); +router.use('/v1/health', healthRoutes); // Default routes (no version prefix for backward compatibility) router.use('/detections', droneDetectionRoutes); @@ -24,6 +26,7 @@ router.use('/devices', deviceRoutes); router.use('/users', userRoutes); router.use('/alerts', alertRoutes); router.use('/dashboard', dashboardRoutes); +router.use('/health', healthRoutes); // API documentation endpoint router.get('/', (req, res) => { @@ -36,7 +39,8 @@ router.get('/', (req, res) => { devices: '/api/devices', users: '/api/users', alerts: '/api/alerts', - dashboard: '/api/dashboard' + dashboard: '/api/dashboard', + health: '/api/health' }, documentation: '/api/docs' });