From 28c75bbe13b03dd84f43a7f8ce1670b04db71444 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cau=C3=AA=20Faleiros?= Date: Mon, 23 Feb 2026 15:21:28 -0300 Subject: [PATCH] feat: setup docker, backend, and gitea pipeline for production --- .env.example | 11 ++ .gitea/workflows/build-deploy.yaml | 44 +++++++ .gitignore | 8 ++ Dockerfile | 37 ++++++ GEMINI.md | 58 +++++++++ agenciac_comia.sql | 199 +++++++++++++++++++++++++++++ backend/db.js | 10 +- backend/index.js | 14 +- backend/package.json | 3 + docker-compose.yml | 44 +++++++ package.json | 5 +- 11 files changed, 425 insertions(+), 8 deletions(-) create mode 100644 .env.example create mode 100644 .gitea/workflows/build-deploy.yaml create mode 100644 Dockerfile create mode 100644 GEMINI.md create mode 100644 agenciac_comia.sql create mode 100644 backend/package.json create mode 100644 docker-compose.yml diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..e2b6492 --- /dev/null +++ b/.env.example @@ -0,0 +1,11 @@ +PORT=3001 +DB_HOST=db +DB_USER=root +DB_PASSWORD=root_password +DB_NAME=agenciac_comia + +# Gitea Runner Configuration +GITEA_INSTANCE_URL=https://gitea.blyzer.com.br +GITEA_RUNNER_REGISTRATION_TOKEN=your_token_here +GITEA_RUNNER_NAME=fasto-runner +GITEA_RUNNER_LABELS=ubuntu-latest:docker://node:16-bullseye diff --git a/.gitea/workflows/build-deploy.yaml b/.gitea/workflows/build-deploy.yaml new file mode 100644 index 0000000..e762a54 --- /dev/null +++ b/.gitea/workflows/build-deploy.yaml @@ -0,0 +1,44 @@ +name: Build and Deploy + +on: + push: + branches: + - main + - master + +jobs: + build-and-push: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Convert repository name to lowercase + run: echo "repo_name=${GITHUB_REPOSITORY,,}" >> $GITHUB_ENV + + - name: Login to Gitea Registry + uses: docker/login-action@v3 + with: + registry: gitea.blyzer.com.br + username: ${{ secrets.REGISTRY_USERNAME }} + password: ${{ secrets.REGISTRY_TOKEN }} + + - name: Build and push + uses: docker/build-push-action@v5 + with: + context: . + push: true + tags: | + gitea.blyzer.com.br/${{ env.repo_name }}:latest + gitea.blyzer.com.br/${{ env.repo_name }}:${{ gitea.sha }} + + - name: Deploy to Portainer + run: | + if [ -n "${{ secrets.PORTAINER_WEBHOOK }}" ]; then + curl -k -X POST "${{ secrets.PORTAINER_WEBHOOK }}" + else + echo "PORTAINER_WEBHOOK secret not set, skipping deployment trigger." + fi diff --git a/.gitignore b/.gitignore index a547bf3..7184f23 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,11 @@ dist-ssr *.njsproj *.sln *.sw? + +# Environment and Secrets +.env +.env.* +!.env.example + +# Runner Data +fasto_runner/ diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..fef1400 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,37 @@ +# Stage 1: Build Frontend +FROM node:22-alpine AS builder + +WORKDIR /app + +COPY package.json ./ +# In a real scenario, copy package-lock.json too +# COPY package-lock.json ./ + +RUN npm install + +COPY . . + +RUN npm run build + +# Stage 2: Production Runtime +FROM node:22-alpine + +WORKDIR /app + +ENV NODE_ENV=production + +COPY package.json ./ +COPY backend/package.json ./backend/ + +# Install dependencies (including production deps for backend) +RUN npm install --omit=dev + +# Copy backend source +COPY backend/ ./backend/ + +# Copy built frontend from builder stage +COPY --from=builder /app/dist ./dist + +EXPOSE 3001 + +CMD ["node", "backend/index.js"] diff --git a/GEMINI.md b/GEMINI.md new file mode 100644 index 0000000..ebba9a5 --- /dev/null +++ b/GEMINI.md @@ -0,0 +1,58 @@ +# Fasto Project Documentation + +## Overview +Fasto is a commercial team management system built with React (Vite) on the frontend and Node.js (Express) on the backend. It uses a MySQL database. + +## Architecture +- **Frontend**: React, TypeScript, Vite. +- **Backend**: Node.js, Express, MySQL2. +- **Database**: MySQL 8.0. +- **Deployment**: Docker Compose for local development; Gitea Actions for CI/CD pushing to a Gitea Registry and deploying via Portainer webhook. + +## Prerequisites +- Docker & Docker Compose +- Node.js (for local development outside Docker) + +## Setup & Running + +### 1. Environment Variables +Copy `.env.example` to `.env` and adjust the values: +```bash +cp .env.example .env +``` +Ensure you set the database credentials and Gitea Runner token if you plan to run the runner locally. + +### 2. Database +The project expects a MySQL database. A `docker-compose.yml` file is provided which spins up a MySQL container and initializes it with `agenciac_comia.sql`. + +### 3. Running with Docker Compose +To start the application, database, and runner: +```bash +docker-compose up -d --build +``` +- Frontend/Backend: http://localhost:3001 +- Database: Exposed on port 3306 (internal to network mostly, but mapped if needed) + +### 4. Gitea Runner +The `docker-compose.yml` includes a service for a Gitea Runner (`fasto-runner`). +- Ensure `GITEA_RUNNER_REGISTRATION_TOKEN` is set in `.env`. +- The runner data is persisted in `./fasto_runner/data`. + +## CI/CD Pipeline +The project uses Gitea Actions defined in `.gitea/workflows/build-deploy.yaml`. +- **Triggers**: Push to `main` or `master`. +- **Steps**: + 1. Checkout code. + 2. Build Docker image. + 3. Push to `gitea.blyzer.com.br`. + 4. Trigger Portainer webhook. +- **Secrets Required in Gitea**: + - `REGISTRY_USERNAME` + - `REGISTRY_TOKEN` + - `PORTAINER_WEBHOOK` + - `API_KEY` (Optional build arg) + +## Development +- **Frontend**: `npm run dev` (Runs on port 3000) +- **Backend**: `node backend/index.js` (Runs on port 3001) +*Note: For local dev, you might need to run a local DB or point to the dockerized one.* diff --git a/agenciac_comia.sql b/agenciac_comia.sql new file mode 100644 index 0000000..d062fc4 --- /dev/null +++ b/agenciac_comia.sql @@ -0,0 +1,199 @@ +-- phpMyAdmin SQL Dump +-- version 5.2.2 +-- https://www.phpmyadmin.net/ +-- +-- Host: localhost:3306 +-- Tempo de geração: 23-Fev-2026 às 10:41 +-- Versão do servidor: 8.0.45 +-- versão do PHP: 8.3.30 + +SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO"; +START TRANSACTION; +SET time_zone = "+00:00"; + + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8mb4 */; + +-- +-- Base de dados: `agenciac_comia` +-- + +-- -------------------------------------------------------- + +-- +-- Estrutura da tabela `attendances` +-- + +CREATE TABLE `attendances` ( + `id` varchar(36) NOT NULL, + `tenant_id` varchar(36) NOT NULL, + `user_id` varchar(36) NOT NULL, + `summary` text, + `score` int DEFAULT NULL, + `first_response_time_min` int DEFAULT '0', + `handling_time_min` int DEFAULT '0', + `funnel_stage` enum('Sem atendimento','Identificação','Negociação','Ganhos','Perdidos') NOT NULL, + `origin` enum('WhatsApp','Instagram','Website','LinkedIn','Referral') NOT NULL, + `product_requested` varchar(255) DEFAULT NULL, + `product_sold` varchar(255) DEFAULT NULL, + `converted` tinyint(1) DEFAULT '0', + `attention_points` json DEFAULT NULL, + `improvement_points` json DEFAULT NULL, + `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP +) ; + +-- +-- Extraindo dados da tabela `attendances` +-- + +INSERT INTO `attendances` (`id`, `tenant_id`, `user_id`, `summary`, `score`, `first_response_time_min`, `handling_time_min`, `funnel_stage`, `origin`, `product_requested`, `product_sold`, `converted`, `attention_points`, `improvement_points`, `created_at`) VALUES +('att_demo_1', 'tenant_123', 'u2', 'Cliente interessado no plano Enterprise.', 95, 5, 30, 'Ganhos', 'LinkedIn', 'Suíte Enterprise', 'Suíte Enterprise', 1, '[]', '[\"Oferecer desconto anual na próxima\"]', '2026-02-20 12:42:10'); + +-- -------------------------------------------------------- + +-- +-- Estrutura da tabela `teams` +-- + +CREATE TABLE `teams` ( + `id` varchar(36) NOT NULL, + `tenant_id` varchar(36) NOT NULL, + `name` varchar(255) NOT NULL, + `description` text, + `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3; + +-- +-- Extraindo dados da tabela `teams` +-- + +INSERT INTO `teams` (`id`, `tenant_id`, `name`, `description`, `created_at`) VALUES +('sales_1', 'tenant_123', 'Vendas Alpha', NULL, '2026-02-20 12:42:10'), +('sales_2', 'tenant_123', 'Vendas Beta', NULL, '2026-02-20 12:42:10'); + +-- -------------------------------------------------------- + +-- +-- Estrutura da tabela `tenants` +-- + +CREATE TABLE `tenants` ( + `id` varchar(36) NOT NULL, + `name` varchar(255) NOT NULL, + `slug` varchar(255) NOT NULL, + `admin_email` varchar(255) DEFAULT NULL, + `logo_url` text, + `status` enum('active','inactive','trial') DEFAULT 'active', + `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3; + +-- +-- Extraindo dados da tabela `tenants` +-- + +INSERT INTO `tenants` (`id`, `name`, `slug`, `admin_email`, `logo_url`, `status`, `created_at`, `updated_at`) VALUES +('system', 'System Admin', 'system', 'root@system.com', NULL, 'active', '2026-02-20 12:42:10', '2026-02-20 12:42:10'), +('tenant_101', 'Soylent Green', 'soylent', 'admin@soylent.com', NULL, 'active', '2023-02-10 14:20:00', '2026-02-20 12:42:10'), +('tenant_123', 'Fasto Corp', 'fasto', 'admin@fasto.com', NULL, 'active', '2023-01-15 13:00:00', '2026-02-20 12:42:10'), +('tenant_456', 'Acme Inc', 'acme-inc', 'contact@acme.com', NULL, 'trial', '2023-06-20 17:30:00', '2026-02-20 12:42:10'), +('tenant_789', 'Globex Utils', 'globex', 'sysadmin@globex.com', NULL, 'inactive', '2022-11-05 12:15:00', '2026-02-20 12:42:10'); + +-- -------------------------------------------------------- + +-- +-- Estrutura da tabela `users` +-- + +CREATE TABLE `users` ( + `id` varchar(36) NOT NULL, + `tenant_id` varchar(36) DEFAULT NULL, + `team_id` varchar(36) DEFAULT NULL, + `name` varchar(255) NOT NULL, + `email` varchar(255) NOT NULL, + `password_hash` varchar(255) NOT NULL DEFAULT 'hash_placeholder', + `avatar_url` text, + `role` enum('super_admin','admin','manager','agent') NOT NULL DEFAULT 'agent', + `bio` text, + `status` enum('active','inactive') DEFAULT 'active', + `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3; + +-- +-- Extraindo dados da tabela `users` +-- + +INSERT INTO `users` (`id`, `tenant_id`, `team_id`, `name`, `email`, `password_hash`, `avatar_url`, `role`, `bio`, `status`, `created_at`) VALUES +('sa1', 'system', NULL, 'Super Administrator', 'root@system.com', 'hash_placeholder', 'https://ui-avatars.com/api/?name=Super+Admin&background=0f172a&color=fff', 'super_admin', 'Administrador Global', 'active', '2026-02-20 12:42:10'), +('u1', 'tenant_123', 'sales_1', 'Lidya Chan', 'lidya@fasto.com', 'hash_placeholder', 'https://picsum.photos/id/1011/200/200', 'manager', 'Gerente de Vendas Experiente', 'active', '2026-02-20 12:42:10'), +('u2', 'tenant_123', 'sales_1', 'Alex Noer', 'alex@fasto.com', 'hash_placeholder', 'https://picsum.photos/id/1012/200/200', 'agent', 'Top performer Q3', 'active', '2026-02-20 12:42:10'), +('u3', 'tenant_123', 'sales_1', 'Angela Moss', 'angela@fasto.com', 'hash_placeholder', 'https://picsum.photos/id/1013/200/200', 'agent', '', 'inactive', '2026-02-20 12:42:10'), +('u4', 'tenant_123', 'sales_2', 'Brian Samuel', 'brian@fasto.com', 'hash_placeholder', 'https://picsum.photos/id/1014/200/200', 'agent', '', 'active', '2026-02-20 12:42:10'), +('u5', 'tenant_123', 'sales_2', 'Benny Chagur', 'benny@fasto.com', 'hash_placeholder', 'https://picsum.photos/id/1025/200/200', 'agent', '', 'active', '2026-02-20 12:42:10'); + +-- +-- Índices para tabelas despejadas +-- + +-- +-- Índices para tabela `attendances` +-- +ALTER TABLE `attendances` + ADD PRIMARY KEY (`id`), + ADD KEY `tenant_id` (`tenant_id`), + ADD KEY `user_id` (`user_id`); + +-- +-- Índices para tabela `teams` +-- +ALTER TABLE `teams` + ADD PRIMARY KEY (`id`), + ADD KEY `tenant_id` (`tenant_id`); + +-- +-- Índices para tabela `tenants` +-- +ALTER TABLE `tenants` + ADD PRIMARY KEY (`id`), + ADD UNIQUE KEY `slug` (`slug`); + +-- +-- Índices para tabela `users` +-- +ALTER TABLE `users` + ADD PRIMARY KEY (`id`), + ADD UNIQUE KEY `email` (`email`), + ADD KEY `tenant_id` (`tenant_id`), + ADD KEY `team_id` (`team_id`); + +-- +-- Restrições para despejos de tabelas +-- + +-- +-- Limitadores para a tabela `attendances` +-- +ALTER TABLE `attendances` + ADD CONSTRAINT `attendances_ibfk_1` FOREIGN KEY (`tenant_id`) REFERENCES `tenants` (`id`) ON DELETE CASCADE, + ADD CONSTRAINT `attendances_ibfk_2` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE; + +-- +-- Limitadores para a tabela `teams` +-- +ALTER TABLE `teams` + ADD CONSTRAINT `teams_ibfk_1` FOREIGN KEY (`tenant_id`) REFERENCES `tenants` (`id`) ON DELETE CASCADE; + +-- +-- Limitadores para a tabela `users` +-- +ALTER TABLE `users` + ADD CONSTRAINT `users_ibfk_1` FOREIGN KEY (`tenant_id`) REFERENCES `tenants` (`id`) ON DELETE CASCADE, + ADD CONSTRAINT `users_ibfk_2` FOREIGN KEY (`team_id`) REFERENCES `teams` (`id`) ON DELETE SET NULL; +COMMIT; + +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; diff --git a/backend/db.js b/backend/db.js index f0dc232..23333c2 100644 --- a/backend/db.js +++ b/backend/db.js @@ -4,15 +4,13 @@ const mysql = require('mysql2/promise'); // Configuração da conexão com o banco de dados // Em produção, estes valores devem vir de variáveis de ambiente (.env) const pool = mysql.createPool({ - host: '162.240.103.190', - user: 'agenciac_comia', - password: 'Blyzer@2025#', - database: 'agenciac_comia', + host: process.env.DB_HOST || 'localhost', + user: process.env.DB_USER || 'root', + password: process.env.DB_PASSWORD || 'password', + database: process.env.DB_NAME || 'agenciac_comia', waitForConnections: true, connectionLimit: 10, queueLimit: 0, - // Opções de SSL podem ser necessárias dependendo do servidor, - // mas vamos tentar sem SSL inicialmente dado o host. }); // Teste de conexão simples ao iniciar diff --git a/backend/index.js b/backend/index.js index ce70987..99a2304 100644 --- a/backend/index.js +++ b/backend/index.js @@ -1,14 +1,20 @@ const express = require('express'); const cors = require('cors'); +const path = require('path'); const pool = require('./db'); const app = express(); -const PORT = 3001; // Porta do backend +const PORT = process.env.PORT || 3001; // Porta do backend app.use(cors()); // Permite que o React (localhost:3000) acesse este servidor app.use(express.json()); +// Serve static files from the React app +if (process.env.NODE_ENV === 'production') { + app.use(express.static(path.join(__dirname, '../dist'))); +} + // --- Rotas de Usuários --- // Listar Usuários (com filtro opcional de tenant) @@ -124,6 +130,12 @@ app.get('/api/tenants', async (req, res) => { } }); +// Serve index.html for any unknown routes (for client-side routing) +if (process.env.NODE_ENV === 'production') { + app.get('*', (req, res) => { + res.sendFile(path.join(__dirname, '../dist/index.html')); + }); +} app.listen(PORT, () => { console.log(`🚀 Servidor Backend rodando em http://localhost:${PORT}`); diff --git a/backend/package.json b/backend/package.json new file mode 100644 index 0000000..5bbefff --- /dev/null +++ b/backend/package.json @@ -0,0 +1,3 @@ +{ + "type": "commonjs" +} diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..d1eab54 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,44 @@ +services: + app: + build: . + image: gitea.blyzer.com.br/blyzer/fasto:latest + container_name: fasto-app + ports: + - "3001:3001" + environment: + - NODE_ENV=production + - PORT=3001 + - DB_HOST=db + - DB_USER=${DB_USER:-root} + - DB_PASSWORD=${DB_PASSWORD:-root_password} + - DB_NAME=${DB_NAME:-agenciac_comia} + depends_on: + - db + restart: unless-stopped + + db: + image: mysql:8.0 + container_name: fasto-db + environment: + MYSQL_ROOT_PASSWORD: ${DB_PASSWORD:-root_password} + MYSQL_DATABASE: ${DB_NAME:-agenciac_comia} + volumes: + - ./agenciac_comia.sql:/docker-entrypoint-initdb.d/init.sql + - db_data:/var/lib/mysql + restart: unless-stopped + + runner: + image: gitea/act_runner:latest + container_name: fasto-runner + environment: + - GITEA_INSTANCE_URL=https://gitea.example.com + - GITEA_RUNNER_REGISTRATION_TOKEN=CHANGE_ME_TOKEN_FROM_SERVER + - GITEA_RUNNER_NAME=${GITEA_RUNNER_NAME:-fasto-runner} + - GITEA_RUNNER_LABELS=${GITEA_RUNNER_LABELS:-ubuntu-latest:docker://node:16-bullseye} + volumes: + - ./fasto_runner/data:/data + - /var/run/docker.sock:/var/run/docker.sock + restart: always + +volumes: + db_data: diff --git a/package.json b/package.json index d5b63ce..912faf2 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,10 @@ "react-dom": "^19.2.4", "react-router-dom": "^7.13.0", "lucide-react": "^0.574.0", - "recharts": "^3.7.0" + "recharts": "^3.7.0", + "express": "^4.18.2", + "cors": "^2.8.5", + "mysql2": "^3.9.1" }, "devDependencies": { "@types/node": "^22.14.0",