diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..03c67af --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,104 @@ +version: '3.8' + +services: + postgres: + image: postgres:16-alpine + container_name: wac_postgres + restart: unless-stopped + environment: + POSTGRES_DB: ${DB_NAME:-whatsapp_central} + POSTGRES_USER: ${DB_USER:-whatsapp_admin} + POSTGRES_PASSWORD: ${DB_PASSWORD:?DB_PASSWORD required} + volumes: + - postgres_data:/var/lib/postgresql/data + ports: + - "5432:5432" + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${DB_USER:-whatsapp_admin}"] + interval: 10s + timeout: 5s + retries: 5 + networks: + - wac_network + + redis: + image: redis:7-alpine + container_name: wac_redis + restart: unless-stopped + command: redis-server --appendonly yes + volumes: + - redis_data:/data + ports: + - "6379:6379" + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 10s + timeout: 5s + retries: 5 + networks: + - wac_network + + whatsapp-core: + build: + context: ./services/whatsapp-core + dockerfile: Dockerfile + container_name: wac_whatsapp + restart: unless-stopped + environment: + NODE_ENV: ${NODE_ENV:-production} + REDIS_URL: redis://redis:6379 + API_GATEWAY_URL: http://api-gateway:8000 + WS_PORT: 3001 + volumes: + - whatsapp_sessions:/app/sessions + ports: + - "3001:3001" + depends_on: + redis: + condition: service_healthy + networks: + - wac_network + + api-gateway: + build: + context: ./services/api-gateway + dockerfile: Dockerfile + container_name: wac_api + restart: unless-stopped + environment: + DATABASE_URL: postgresql://${DB_USER:-whatsapp_admin}:${DB_PASSWORD}@postgres:5432/${DB_NAME:-whatsapp_central} + REDIS_URL: redis://redis:6379 + JWT_SECRET: ${JWT_SECRET:?JWT_SECRET required} + WHATSAPP_CORE_URL: http://whatsapp-core:3001 + CORS_ORIGINS: ${CORS_ORIGINS:-http://localhost:5173,http://localhost:3000} + ports: + - "8000:8000" + depends_on: + postgres: + condition: service_healthy + redis: + condition: service_healthy + networks: + - wac_network + + frontend: + build: + context: ./frontend + dockerfile: Dockerfile + container_name: wac_frontend + restart: unless-stopped + ports: + - "3000:80" + depends_on: + - api-gateway + networks: + - wac_network + +volumes: + postgres_data: + redis_data: + whatsapp_sessions: + +networks: + wac_network: + driver: bridge diff --git a/frontend/Dockerfile b/frontend/Dockerfile new file mode 100644 index 0000000..71a28dc --- /dev/null +++ b/frontend/Dockerfile @@ -0,0 +1,18 @@ +FROM node:20-alpine as builder + +WORKDIR /app + +COPY package*.json ./ +RUN npm ci + +COPY . . +RUN npm run build + +FROM nginx:alpine + +COPY --from=builder /app/dist /usr/share/nginx/html +COPY nginx.conf /etc/nginx/conf.d/default.conf + +EXPOSE 80 + +CMD ["nginx", "-g", "daemon off;"] diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 0000000..e5e1b6d --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + WhatsApp Centralizado + + +
+ + + diff --git a/frontend/nginx.conf b/frontend/nginx.conf new file mode 100644 index 0000000..53ca5fb --- /dev/null +++ b/frontend/nginx.conf @@ -0,0 +1,28 @@ +server { + listen 80; + server_name localhost; + root /usr/share/nginx/html; + index index.html; + + location / { + try_files $uri $uri/ /index.html; + } + + location /api { + proxy_pass http://api-gateway:8000; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + } + + location /auth { + proxy_pass http://api-gateway:8000; + proxy_set_header Host $host; + } + + location /ws { + proxy_pass http://whatsapp-core:3001; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + } +} diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..a5d6bab --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,30 @@ +{ + "name": "whatsapp-centralizado-frontend", + "private": true, + "version": "1.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview" + }, + "dependencies": { + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-router-dom": "^7.1.1", + "antd": "^5.23.1", + "@ant-design/icons": "^5.6.1", + "@tanstack/react-query": "^5.64.1", + "axios": "^1.7.9", + "zustand": "^5.0.3", + "socket.io-client": "^4.8.1", + "dayjs": "^1.11.13" + }, + "devDependencies": { + "@types/react": "^18.3.18", + "@types/react-dom": "^18.3.5", + "@vitejs/plugin-react": "^4.3.4", + "typescript": "^5.7.3", + "vite": "^6.0.7" + } +} diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json new file mode 100644 index 0000000..5413626 --- /dev/null +++ b/frontend/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "baseUrl": ".", + "paths": { + "@/*": ["src/*"] + } + }, + "include": ["src"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/frontend/tsconfig.node.json b/frontend/tsconfig.node.json new file mode 100644 index 0000000..42872c5 --- /dev/null +++ b/frontend/tsconfig.node.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts new file mode 100644 index 0000000..6ff8be6 --- /dev/null +++ b/frontend/vite.config.ts @@ -0,0 +1,29 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; +import path from 'path'; + +export default defineConfig({ + plugins: [react()], + resolve: { + alias: { + '@': path.resolve(__dirname, './src'), + }, + }, + server: { + port: 5173, + proxy: { + '/api': { + target: 'http://localhost:8000', + changeOrigin: true, + }, + '/auth': { + target: 'http://localhost:8000', + changeOrigin: true, + }, + '/ws': { + target: 'http://localhost:3001', + ws: true, + }, + }, + }, +}); diff --git a/nginx/nginx.conf b/nginx/nginx.conf new file mode 100644 index 0000000..e41d8ad --- /dev/null +++ b/nginx/nginx.conf @@ -0,0 +1,50 @@ +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + upstream api { + server api-gateway:8000; + } + + upstream whatsapp { + server whatsapp-core:3001; + } + + server { + listen 80; + server_name localhost; + + location / { + root /usr/share/nginx/html; + index index.html; + try_files $uri $uri/ /index.html; + } + + location /api { + proxy_pass http://api; + 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_read_timeout 300; + } + + location /auth { + proxy_pass http://api; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + } + + location /ws { + proxy_pass http://whatsapp; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $host; + proxy_read_timeout 86400; + } + } +} diff --git a/services/api-gateway/Dockerfile b/services/api-gateway/Dockerfile new file mode 100644 index 0000000..1344cdf --- /dev/null +++ b/services/api-gateway/Dockerfile @@ -0,0 +1,17 @@ +FROM python:3.11-slim + +WORKDIR /app + +RUN apt-get update && apt-get install -y --no-install-recommends \ + gcc \ + libpq-dev \ + && rm -rf /var/lib/apt/lists/* + +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +COPY app ./app + +EXPOSE 8000 + +CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"] diff --git a/services/api-gateway/app/__init__.py b/services/api-gateway/app/__init__.py new file mode 100644 index 0000000..135b831 --- /dev/null +++ b/services/api-gateway/app/__init__.py @@ -0,0 +1 @@ +# WhatsApp Centralizado - API Gateway diff --git a/services/api-gateway/requirements.txt b/services/api-gateway/requirements.txt new file mode 100644 index 0000000..386d1da --- /dev/null +++ b/services/api-gateway/requirements.txt @@ -0,0 +1,12 @@ +fastapi==0.115.6 +uvicorn[standard]==0.34.0 +sqlalchemy==2.0.36 +alembic==1.14.0 +psycopg2-binary==2.9.10 +python-jose[cryptography]==3.3.0 +passlib[bcrypt]==1.7.4 +python-multipart==0.0.20 +pydantic==2.10.4 +pydantic-settings==2.7.1 +redis==5.2.1 +httpx==0.28.1 diff --git a/services/whatsapp-core/Dockerfile b/services/whatsapp-core/Dockerfile new file mode 100644 index 0000000..5f14b44 --- /dev/null +++ b/services/whatsapp-core/Dockerfile @@ -0,0 +1,19 @@ +FROM node:20-alpine + +WORKDIR /app + +RUN apk add --no-cache python3 make g++ + +COPY package*.json ./ +RUN npm ci + +COPY tsconfig.json ./ +COPY src ./src + +RUN npm run build + +RUN mkdir -p /app/sessions + +EXPOSE 3001 + +CMD ["npm", "start"] diff --git a/services/whatsapp-core/package.json b/services/whatsapp-core/package.json new file mode 100644 index 0000000..894709c --- /dev/null +++ b/services/whatsapp-core/package.json @@ -0,0 +1,29 @@ +{ + "name": "whatsapp-core", + "version": "1.0.0", + "description": "WhatsApp Core service using Baileys", + "main": "dist/index.js", + "scripts": { + "build": "tsc", + "start": "node dist/index.js", + "dev": "ts-node-dev --respawn src/index.ts", + "lint": "eslint src/**/*.ts" + }, + "dependencies": { + "@whiskeysockets/baileys": "^6.7.16", + "express": "^4.21.2", + "socket.io": "^4.8.1", + "ioredis": "^5.4.1", + "pino": "^9.6.0", + "qrcode": "^1.5.4", + "uuid": "^11.0.5" + }, + "devDependencies": { + "@types/express": "^5.0.0", + "@types/node": "^22.10.7", + "@types/uuid": "^10.0.0", + "@types/qrcode": "^1.5.5", + "typescript": "^5.7.3", + "ts-node-dev": "^2.0.0" + } +} diff --git a/services/whatsapp-core/tsconfig.json b/services/whatsapp-core/tsconfig.json new file mode 100644 index 0000000..1951778 --- /dev/null +++ b/services/whatsapp-core/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "commonjs", + "lib": ["ES2022"], + "outDir": "./dist", + "rootDir": "./src", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +}