feat: setup docker, backend, and gitea pipeline for production
All checks were successful
Build and Deploy / build-and-push (push) Successful in 4m19s

This commit is contained in:
Cauê Faleiros
2026-02-23 15:21:28 -03:00
parent 3250ad7537
commit 28c75bbe13
11 changed files with 425 additions and 8 deletions

11
.env.example Normal file
View File

@@ -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

View File

@@ -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

8
.gitignore vendored
View File

@@ -22,3 +22,11 @@ dist-ssr
*.njsproj
*.sln
*.sw?
# Environment and Secrets
.env
.env.*
!.env.example
# Runner Data
fasto_runner/

37
Dockerfile Normal file
View File

@@ -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"]

58
GEMINI.md Normal file
View File

@@ -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.*

199
agenciac_comia.sql Normal file
View File

@@ -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 */;

View File

@@ -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

View File

@@ -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}`);

3
backend/package.json Normal file
View File

@@ -0,0 +1,3 @@
{
"type": "commonjs"
}

44
docker-compose.yml Normal file
View File

@@ -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:

View File

@@ -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",