diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 1f7997a..c6680eb 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -1,4 +1,3 @@ -version: '3.8' # ========================================== # CoopCredits Production Docker Compose @@ -71,7 +70,7 @@ services: container_name: coop-backend environment: - NODE_ENV=production - - DATABASE_URL=postgresql://coop:${POSTGRES_PASSWORD:-coop_secure_password}@postgres:5432/coop_credits?schema=public + - DATABASE_URL=postgresql://coop:$POSTGRES_PASSWORD@postgres:5432/coop_credits?schema=public - REDIS_URL=redis://:${REDIS_PASSWORD:-redis_secure_password}@redis:6379 - PORT=3002 - JWT_SECRET=${JWT_SECRET} @@ -82,7 +81,7 @@ services: - PLEX_CLIENT_SECRET=${PLEX_CLIENT_SECRET} - PLEX_REDIRECT_URI=${PLEX_REDIRECT_URI:-https://coop.hobokenchicken.com/auth/callback} # Tautulli (172.20.1.255:8181) - - TAUTULLI_URL=http://172.20.1.255:8181 + - TAUTULLI_URL=http://172.20.1.225:8181 - TAUTULLI_API_KEY=${TAUTULLI_API_KEY} - TAUTULLI_WEBHOOK_SECRET=${TAUTULLI_WEBHOOK_SECRET} # Overseer (172.20.1.225:5055) @@ -139,7 +138,7 @@ services: - NEXT_PUBLIC_SOLANA_NETWORK=devnet - NEXT_PUBLIC_SOLANA_RPC_URL=https://api.devnet.solana.com ports: - - "0.0.0.0:3000:3000" # Only accessible via Nginx + - "0.0.0.0:3000:3000" depends_on: - backend networks: @@ -150,49 +149,6 @@ services: options: max-size: "50m" max-file: "5" - - # ========================================== - # Nginx Reverse Proxy - # ========================================== - nginx: - image: nginx:alpine - container_name: coop-nginx - ports: - - "80:80" - - "443:443" - volumes: - - ./docker/nginx/nginx.prod.conf:/etc/nginx/nginx.conf:ro - - ./docker/nginx/ssl:/etc/nginx/ssl:ro - - ./docker/nginx/logs:/var/log/nginx - # Let's Encrypt certificates (if using certbot) - - ./docker/nginx/letsencrypt:/etc/letsencrypt:ro - - ./docker/nginx/www:/var/www/certbot:ro - depends_on: - - frontend - - backend - networks: - - coop-internal - restart: unless-stopped - logging: - driver: "json-file" - options: - max-size: "50m" - max-file: "5" - - # ========================================== - # Certbot (for Let's Encrypt SSL) - # ========================================== - certbot: - image: certbot/certbot - container_name: coop-certbot - volumes: - - ./docker/nginx/letsencrypt:/etc/letsencrypt - - ./docker/nginx/www:/var/www/certbot - entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;'" - networks: - - coop-internal - restart: unless-stopped - # ========================================== # Volumes # ========================================== diff --git a/docker/nginx/nginx.prod.conf b/docker/nginx/nginx.prod.conf index 7bf019b..c4dee33 100644 --- a/docker/nginx/nginx.prod.conf +++ b/docker/nginx/nginx.prod.conf @@ -1,6 +1,7 @@ # ========================================== # CoopCredits Nginx Configuration -# Production - coop.hobokenchicken.com +# Production — coop.hobokenchicken.com +# SSL handled by Caddy; this serves plain HTTP # ========================================== user nginx; @@ -18,7 +19,6 @@ http { include /etc/nginx/mime.types; default_type application/octet-stream; - # Logging format log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for" ' @@ -27,7 +27,6 @@ http { access_log /var/log/nginx/access.log main; - # Performance sendfile on; tcp_nopush on; tcp_nodelay on; @@ -35,69 +34,29 @@ http { types_hash_max_size 2048; client_max_body_size 50M; - # Gzip compression gzip on; gzip_vary on; gzip_proxied any; gzip_comp_level 6; - gzip_types text/plain text/css text/xml application/json application/javascript + gzip_types text/plain text/css text/xml application/json application/javascript application/rss+xml application/atom+xml image/svg+xml; - # Rate limiting zones limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s; limit_req_zone $binary_remote_addr zone=webhooks:10m rate=100r/s; limit_req_zone $binary_remote_addr zone=login:10m rate=5r/m; limit_conn_zone $binary_remote_addr zone=addr:10m; - # Upstream servers upstream frontend { server frontend:3000 max_fails=3 fail_timeout=30s; } upstream backend { - server backend:3001 max_fails=3 fail_timeout=30s; + server backend:3002 max_fails=3 fail_timeout=30s; } - # ========================================== - # HTTP Server - Redirect to HTTPS - # ========================================== server { listen 80; server_name coop.hobokenchicken.com; - - # Let's Encrypt challenge - location /.well-known/acme-challenge/ { - root /var/www/certbot; - } - - # Redirect all HTTP to HTTPS - location / { - return 301 https://$server_name$request_uri; - } - } - - # ========================================== - # HTTPS Server - Main Application - # ========================================== - server { - listen 443 ssl http2; - server_name coop.hobokenchicken.com; - - # SSL Certificates - ssl_certificate /etc/nginx/ssl/cert.pem; - ssl_certificate_key /etc/nginx/ssl/key.pem; - - # Alternative: Let's Encrypt certificates - # ssl_certificate /etc/letsencrypt/live/coop.hobokenchicken.com/fullchain.pem; - # ssl_certificate_key /etc/letsencrypt/live/coop.hobokenchicken.com/privkey.pem; - - # SSL Configuration - ssl_protocols TLSv1.2 TLSv1.3; - ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384; - ssl_prefer_server_ciphers off; - ssl_session_cache shared:SSL:10m; - ssl_session_timeout 10m; - ssl_session_tickets off; # Security Headers add_header X-Frame-Options "SAMEORIGIN" always; @@ -107,9 +66,6 @@ http { add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self'; connect-src 'self' https://api.devnet.solana.com wss:;" always; add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always; - # ========================================== - # Frontend - Next.js App - # ========================================== location / { proxy_pass http://frontend; proxy_http_version 1.1; @@ -118,51 +74,42 @@ http { 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_set_header X-Forwarded-Proto https; proxy_cache_bypass $http_upgrade; proxy_connect_timeout 60s; proxy_send_timeout 60s; proxy_read_timeout 60s; } - # ========================================== - # API Endpoints - # ========================================== location /api/ { limit_req zone=api burst=20 nodelay; limit_conn addr 10; - + proxy_pass http://backend/api/; proxy_http_version 1.1; 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_set_header X-Forwarded-Proto https; proxy_connect_timeout 30s; proxy_send_timeout 30s; proxy_read_timeout 30s; } - # ========================================== - # Webhooks (higher rate limit) - # ========================================== location /webhooks/ { limit_req zone=webhooks burst=50 nodelay; - + proxy_pass http://backend/webhooks/; proxy_http_version 1.1; 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_set_header X-Forwarded-Proto https; proxy_connect_timeout 30s; proxy_send_timeout 30s; proxy_read_timeout 30s; } - # ========================================== - # WebSocket Support (Socket.io) - # ========================================== location /socket.io/ { proxy_pass http://backend/socket.io/; proxy_http_version 1.1; @@ -171,32 +118,23 @@ http { 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_set_header X-Forwarded-Proto https; proxy_connect_timeout 7d; proxy_send_timeout 7d; proxy_read_timeout 7d; } - # ========================================== - # Static Assets (cache optimization) - # ========================================== location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { proxy_pass http://frontend; expires 1y; add_header Cache-Control "public, immutable"; } - # ========================================== - # Health Check Endpoint - # ========================================== location /health { proxy_pass http://backend/health; access_log off; } - # ========================================== - # Error Pages - # ========================================== error_page 500 502 503 504 /50x.html; location = /50x.html { root /usr/share/nginx/html; diff --git a/frontend/next.config.js b/frontend/next.config.js index 5174633..dd78471 100644 --- a/frontend/next.config.js +++ b/frontend/next.config.js @@ -6,11 +6,11 @@ const nextConfig = { return [ { source: '/api/:path*', - destination: 'http://localhost:3002/api/:path*', + destination: 'http://backend:3002/api/:path*', }, { source: '/webhooks/:path*', - destination: 'http://localhost:3002/webhooks/:path*', + destination: 'http://backend:3002/webhooks/:path*', }, ]; }, diff --git a/frontend/src/app/dashboard/page.tsx b/frontend/src/app/dashboard/page.tsx index 8c9d327..1614729 100644 --- a/frontend/src/app/dashboard/page.tsx +++ b/frontend/src/app/dashboard/page.tsx @@ -48,6 +48,7 @@ export default function DashboardPage() { const [isLoading, setIsLoading] = useState(true); const [isSearchModalOpen, setIsSearchModalOpen] = useState(false); const [isBuyModalOpen, setIsBuyModalOpen] = useState(false); + const [refreshCount, setRefreshCount] = useState(0); useEffect(() => { if (!isAuthenticated) { @@ -100,7 +101,9 @@ export default function DashboardPage() { const handleRefresh = () => { loadWalletAndPending(); + loadCosts(); syncPending(); + setRefreshCount((c) => c + 1); }; if (isLoading) @@ -277,7 +280,7 @@ export default function DashboardPage() { - + @@ -297,7 +300,7 @@ export default function DashboardPage() {
- +