From c687fd2bd1ae4335178a57652b135d60cd40d73d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cau=C3=AA=20Faleiros?= Date: Thu, 9 Apr 2026 09:45:02 -0300 Subject: [PATCH] first commit --- .gitea/workflows/build-deploy.yml | 44 +++++++++++++++++++++ .gitignore | 5 +++ Dockerfile | 30 +++++++++++++++ dist/controllers/webhook.controller.js | 40 +++++++++++++++++++ dist/index.js | 20 ++++++++++ dist/routes/webhook.route.js | 7 ++++ dist/services/n8n.service.js | 30 +++++++++++++++ docker-compose.yml | 22 +++++++++++ src/controllers/webhook.controller.ts | 53 ++++++++++++++++++++++++++ src/index.ts | 21 ++++++++++ src/routes/webhook.route.ts | 8 ++++ src/services/n8n.service.ts | 25 ++++++++++++ 12 files changed, 305 insertions(+) create mode 100644 .gitea/workflows/build-deploy.yml create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 dist/controllers/webhook.controller.js create mode 100644 dist/index.js create mode 100644 dist/routes/webhook.route.js create mode 100644 dist/services/n8n.service.js create mode 100644 docker-compose.yml create mode 100644 src/controllers/webhook.controller.ts create mode 100644 src/index.ts create mode 100644 src/routes/webhook.route.ts create mode 100644 src/services/n8n.service.ts diff --git a/.gitea/workflows/build-deploy.yml b/.gitea/workflows/build-deploy.yml new file mode 100644 index 0000000..e762a54 --- /dev/null +++ b/.gitea/workflows/build-deploy.yml @@ -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 new file mode 100644 index 0000000..c73c44f --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.env +package.json +package-lock.json +tsconfig.json +node_modules diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..bebdf4c --- /dev/null +++ b/Dockerfile @@ -0,0 +1,30 @@ +# Build stage +FROM node:20-alpine AS builder + +WORKDIR /app + +# Copy package files and install all dependencies (including devDependencies for TypeScript) +COPY package*.json ./ +RUN npm ci + +# Copy source code and build +COPY . . +RUN npm run build + +# Production stage +FROM node:20-alpine + +WORKDIR /app + +# Copy package files and install ONLY production dependencies +COPY package*.json ./ +RUN npm ci --omit=dev + +# Copy built code from the builder stage +COPY --from=builder /app/dist ./dist + +# Expose the port the app runs on +EXPOSE 3000 + +# Command to run the application +CMD ["npm", "start"] diff --git a/dist/controllers/webhook.controller.js b/dist/controllers/webhook.controller.js new file mode 100644 index 0000000..7d48e23 --- /dev/null +++ b/dist/controllers/webhook.controller.js @@ -0,0 +1,40 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.handleTinyOrderUpdate = void 0; +const n8n_service_1 = require("../services/n8n.service"); +const handleTinyOrderUpdate = async (req, res) => { + try { + let payload = req.body; + // Parse 'dados' if it comes as a JSON string + if (typeof payload.dados === 'string') { + try { + payload.dados = JSON.parse(payload.dados); + } + catch (e) { + console.error('Could not parse "dados" as JSON string.', e); + } + } + console.log('Received webhook from Tiny:', JSON.stringify(payload, null, 2)); + // Acknowledge Tiny immediately + res.status(200).send('OK'); + if (!payload || Object.keys(payload).length === 0) { + console.warn('Received empty payload from Tiny webhook.'); + return; + } + const transformedData = { + source: 'tiny_webhook_middleware', + event: payload.tipo || 'atualizacao_pedido', + dados: payload.dados, + raw_payload: payload + }; + // Forward to n8n asynchronously + await (0, n8n_service_1.sendToN8n)(transformedData); + } + catch (error) { + console.error('Error handling Tiny webhook:', error); + if (!res.headersSent) { + res.status(500).json({ error: 'Internal server error processing webhook.' }); + } + } +}; +exports.handleTinyOrderUpdate = handleTinyOrderUpdate; diff --git a/dist/index.js b/dist/index.js new file mode 100644 index 0000000..11d919f --- /dev/null +++ b/dist/index.js @@ -0,0 +1,20 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const express_1 = __importDefault(require("express")); +const dotenv_1 = __importDefault(require("dotenv")); +const webhook_route_1 = __importDefault(require("./routes/webhook.route")); +dotenv_1.default.config(); +const app = (0, express_1.default)(); +const port = process.env.PORT || 3000; +app.use(express_1.default.json()); +app.use(express_1.default.urlencoded({ extended: true })); +app.use('/api/webhooks', webhook_route_1.default); +app.get('/health', (req, res) => { + res.status(200).json({ status: 'OK', message: 'Tiny-n8n middleware is running.' }); +}); +app.listen(port, () => { + console.log(`[server]: Middleware server is running at http://localhost:${port}`); +}); diff --git a/dist/routes/webhook.route.js b/dist/routes/webhook.route.js new file mode 100644 index 0000000..b619792 --- /dev/null +++ b/dist/routes/webhook.route.js @@ -0,0 +1,7 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const express_1 = require("express"); +const webhook_controller_1 = require("../controllers/webhook.controller"); +const router = (0, express_1.Router)(); +router.post('/tiny/order-status', webhook_controller_1.handleTinyOrderUpdate); +exports.default = router; diff --git a/dist/services/n8n.service.js b/dist/services/n8n.service.js new file mode 100644 index 0000000..16f8937 --- /dev/null +++ b/dist/services/n8n.service.js @@ -0,0 +1,30 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.sendToN8n = void 0; +const axios_1 = __importDefault(require("axios")); +const sendToN8n = async (data) => { + const n8nWebhookUrl = process.env.N8N_WEBHOOK_URL; + if (!n8nWebhookUrl) { + console.error('N8N_WEBHOOK_URL is not defined in environment variables.'); + return; + } + try { + console.log(`Forwarding payload to n8n at ${n8nWebhookUrl}...`); + const response = await axios_1.default.post(n8nWebhookUrl, data, { + headers: { + 'Content-Type': 'application/json' + } + }); + console.log(`Successfully forwarded to n8n. Status: ${response.status}`); + } + catch (error) { + console.error('Failed to forward payload to n8n.', error.message); + if (error.response) { + console.error('n8n Response:', error.response.status, error.response.data); + } + } +}; +exports.sendToN8n = sendToN8n; diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..5fe362e --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + middleware: + # If using CI/CD, you will replace 'build: .' with your Gitea registry image: + # image: gitea.yourdomain.com/youruser/api-tiny-n8n:latest + build: . + container_name: api-tiny-n8n + restart: unless-stopped + ports: + # Maps port 3030 on your host to 3000 inside the container. + # Nginx Proxy Manager will point to YOUR_SERVER_IP:3030 + - "3030:3000" + env_file: + - .env + # Optional: If you use a shared Docker network for Nginx Proxy Manager, you can attach it here: + # networks: + # - nginx-proxy-manager-network + +# networks: +# nginx-proxy-manager-network: +# external: true diff --git a/src/controllers/webhook.controller.ts b/src/controllers/webhook.controller.ts new file mode 100644 index 0000000..030e18c --- /dev/null +++ b/src/controllers/webhook.controller.ts @@ -0,0 +1,53 @@ +import { Request, Response } from 'express'; +import { sendToN8n } from '../services/n8n.service'; + +export const handleTinyOrderUpdate = async (req: Request, res: Response): Promise => { + try { + // 1. Security Check: Verify token from Tiny + const expectedToken = process.env.TINY_WEBHOOK_SECRET; + const providedToken = req.query.token; + + if (expectedToken && providedToken !== expectedToken) { + console.warn('Unauthorized webhook attempt. Invalid or missing token.'); + res.status(401).json({ error: 'Unauthorized' }); + return; + } + + let payload = req.body; + + // Parse 'dados' if it comes as a JSON string + if (typeof payload.dados === 'string') { + try { + payload.dados = JSON.parse(payload.dados); + } catch (e) { + console.error('Could not parse "dados" as JSON string.', e); + } + } + + console.log('Received webhook from Tiny:', JSON.stringify(payload, null, 2)); + + // Acknowledge Tiny immediately + res.status(200).send('OK'); + + if (!payload || Object.keys(payload).length === 0) { + console.warn('Received empty payload from Tiny webhook.'); + return; + } + + const transformedData = { + source: 'tiny_webhook_middleware', + event: payload.tipo || 'atualizacao_pedido', + dados: payload.dados, + raw_payload: payload + }; + + // Forward to n8n asynchronously + await sendToN8n(transformedData); + + } catch (error) { + console.error('Error handling Tiny webhook:', error); + if (!res.headersSent) { + res.status(500).json({ error: 'Internal server error processing webhook.' }); + } + } +}; diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..bb01aa9 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,21 @@ +import express, { Express, Request, Response } from 'express'; +import dotenv from 'dotenv'; +import webhookRoutes from './routes/webhook.route'; + +dotenv.config(); + +const app: Express = express(); +const port = process.env.PORT || 3000; + +app.use(express.json()); +app.use(express.urlencoded({ extended: true })); + +app.use('/api/webhooks', webhookRoutes); + +app.get('/health', (req: Request, res: Response) => { + res.status(200).json({ status: 'OK', message: 'Tiny-n8n middleware is running.' }); +}); + +app.listen(port, () => { + console.log(`[server]: Middleware server is running at http://localhost:${port}`); +}); diff --git a/src/routes/webhook.route.ts b/src/routes/webhook.route.ts new file mode 100644 index 0000000..48ecea6 --- /dev/null +++ b/src/routes/webhook.route.ts @@ -0,0 +1,8 @@ +import { Router } from 'express'; +import { handleTinyOrderUpdate } from '../controllers/webhook.controller'; + +const router = Router(); + +router.post('/tiny/order-status', handleTinyOrderUpdate); + +export default router; diff --git a/src/services/n8n.service.ts b/src/services/n8n.service.ts new file mode 100644 index 0000000..53b52dc --- /dev/null +++ b/src/services/n8n.service.ts @@ -0,0 +1,25 @@ +import axios from 'axios'; + +export const sendToN8n = async (data: any): Promise => { + const n8nWebhookUrl = process.env.N8N_WEBHOOK_URL; + + if (!n8nWebhookUrl) { + console.error('N8N_WEBHOOK_URL is not defined in environment variables.'); + return; + } + + try { + console.log(`Forwarding payload to n8n at ${n8nWebhookUrl}...`); + const response = await axios.post(n8nWebhookUrl, data, { + headers: { + 'Content-Type': 'application/json' + } + }); + console.log(`Successfully forwarded to n8n. Status: ${response.status}`); + } catch (error: any) { + console.error('Failed to forward payload to n8n.', error.message); + if (error.response) { + console.error('n8n Response:', error.response.status, error.response.data); + } + } +};