diff --git a/.dockerignore b/.dockerignore index eda2084..f51fc37 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,7 +1,57 @@ +# Dependencies node_modules + +# Build output dist + +# Git .git .gitignore + +# Docker files (não precisam estar dentro da imagem) Dockerfile docker-compose.yml -pnpm-lock.yaml +.dockerignore + +# Arquivos de lock (npm/yarn/pnpm) +package-lock.json +yarn.lock +# Mantém pnpm-lock.yaml pois é necessário para build reproduzível + +# Documentação +*.md +README.md + +# Environment files (usar apenas .env.example como referência) +.env +.env.local +.env.development +.env.production +.env.*.local + +# IDE +.vscode +.idea +*.swp +*.swo +*~ + +# Logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* + +# OS +.DS_Store +Thumbs.db + +# Testing +coverage +.nyc_output + +# Cache +.cache +.eslintcache +.stylelintcache diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..ca88e85 --- /dev/null +++ b/.env.example @@ -0,0 +1,6 @@ +# Ambiente (development ou production) +VITE_ENV=production + +# URL da API do back-end +# No Dokploy com Traefik, usar o nome do serviço interno +VITE_API_URL=http://clipperia-api:3000 diff --git a/Dockerfile b/Dockerfile index f4df3b7..a48a8dd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,10 +7,11 @@ COPY . . RUN pnpm install - ARG BASE=/ +ARG VITE_API_URL=http://clipperia-api:3000 ENV VITE_BASE_PATH=$BASE -RUN pnpm run build +ENV VITE_API_URL=$VITE_API_URL +RUN pnpm build RUN rm -rf node_modules FROM nginx:1.27-alpine diff --git a/docker-compose.yml b/docker-compose.yml index 85aec5c..5478ac4 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,18 +5,21 @@ services: dockerfile: Dockerfile args: BASE: ${BASE:-/} + VITE_API_URL: ${VITE_API_URL:-http://clipperia-api:3000} no_cache: true - image: ${IMAGE_NAME:-vite-nginx:latest} + image: ${IMAGE_NAME:-clipperia-web:latest} container_name: clipperia restart: unless-stopped networks: - dokploy-network + # Labels do Traefik gerenciados pelo Dokploy + # Se necessário configurar manualmente: # labels: # - traefik.enable=true - # - traefik.http.routers.vite.rule=Host("clipperia.com.br") - # - traefik.http.routers.vite.entrypoints=websecure - # - traefik.http.routers.vite.tls.certresolver=letsencrypt - # - traefik.http.services.vite.loadbalancer.server.port=80 + # - traefik.http.routers.clipperia.rule=Host(`clipperia.com.br`) + # - traefik.http.routers.clipperia.entrypoints=websecure + # - traefik.http.routers.clipperia.tls.certresolver=letsencrypt + # - traefik.http.services.clipperia.loadbalancer.server.port=80 networks: dokploy-network: external: true diff --git a/nginx.conf b/nginx.conf index 7ec555e..35b7822 100644 --- a/nginx.conf +++ b/nginx.conf @@ -8,13 +8,35 @@ server { root /usr/share/nginx/html; index index.html; + # Configurações para proxy reverso (Traefik) + real_ip_header X-Forwarded-For; + set_real_ip_from 0.0.0.0/0; + + # Cabeçalhos de segurança + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-Content-Type-Options "nosniff" always; + add_header X-XSS-Protection "1; mode=block" always; + + # Logs + access_log /var/log/nginx/access.log; + error_log /var/log/nginx/error.log; + + # Cache de assets estáticos location ~* \.(?:js|mjs|css|png|jpg|jpeg|gif|svg|ico|woff2?)$ { expires 7d; add_header Cache-Control "public, max-age=604800, immutable"; try_files $uri =404; } + # SPA routing - todas as rotas vão para index.html location / { try_files $uri $uri/ /index.html; } + + # Health check endpoint (opcional) + location /health { + access_log off; + return 200 "OK"; + add_header Content-Type text/plain; + } } diff --git a/src/config/axios.js b/src/config/axios.js index 65ccd34..c3a660d 100644 --- a/src/config/axios.js +++ b/src/config/axios.js @@ -2,10 +2,7 @@ import axios from "axios"; import Cookies from "js-cookie"; export const API = axios.create({ - baseURL: "http://localhost:3000", - // process.env.NODE_ENV === "development" - // ? "https://api.clipperia.com.br" - // : "http://nestjs:3000", + baseURL: import.meta.env.VITE_API_URL || "http://localhost:3000", }); API.interceptors.request.use((config) => { @@ -21,7 +18,7 @@ API.interceptors.request.use((config) => { API.interceptors.response.use( (response) => response, (error) => { - if (error.response.status === 401) { + if (error.response?.status === 401) { Cookies.remove("token"); window.location.href = "/login"; }