From 7e72e88caec83295b37c4fbbc2d53bdb5e969386 Mon Sep 17 00:00:00 2001 From: Tiago Giertyas Matana Date: Wed, 13 May 2026 15:53:47 -0300 Subject: [PATCH 1/2] Update .gitlab-ci.yml file --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index d434fe3..07198fe 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -13,7 +13,7 @@ variables: default: tags: - - docker + - shared # ───────────────────────────────────────────────────────────── # STAGE: validate -- GitLab From 972343b5a56a3bfb1070688cc4211c31388d4db2 Mon Sep 17 00:00:00 2001 From: Tiago Matana Date: Wed, 13 May 2026 17:28:36 -0300 Subject: [PATCH 2/2] update repos --- backend/.dockerignore | 4 -- backend/.env.example | 3 + backend/Dockerfile | 20 +++--- backend/src/index.js | 2 + backend/src/routes/challenges.js | 58 +++++++++++++--- frontend/.dockerignore | 4 -- frontend/Dockerfile | 17 ++--- frontend/index.html | 2 +- frontend/nginx.conf | 18 ----- frontend/src/App.jsx | 113 +++++++++++++++++++++++++++---- frontend/vite.config.js | 3 + 11 files changed, 170 insertions(+), 74 deletions(-) delete mode 100644 backend/.dockerignore create mode 100644 backend/.env.example delete mode 100644 frontend/.dockerignore delete mode 100644 frontend/nginx.conf diff --git a/backend/.dockerignore b/backend/.dockerignore deleted file mode 100644 index cab67ef..0000000 --- a/backend/.dockerignore +++ /dev/null @@ -1,4 +0,0 @@ -node_modules -npm-debug.log -.env -.git diff --git a/backend/.env.example b/backend/.env.example new file mode 100644 index 0000000..135f86b --- /dev/null +++ b/backend/.env.example @@ -0,0 +1,3 @@ +# Copie este arquivo para .env e ajuste se necessário +PORT=4000 +DATABASE_URL=postgres://desafios_user:desafios_pass@db:5432/desafios diff --git a/backend/Dockerfile b/backend/Dockerfile index f8e4bb7..c540719 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -1,17 +1,13 @@ -# Stage 1: instalar dependências de produção -FROM node:18-alpine AS deps +FROM node:20-alpine + WORKDIR /app + COPY package*.json ./ -RUN npm ci --only=production +RUN npm install -# Stage 2: imagem final mínima com usuário não-root -FROM node:18-alpine AS runner -RUN addgroup -S appgroup && adduser -S appuser -G appgroup -WORKDIR /app -COPY --from=deps /app/node_modules ./node_modules COPY src ./src -USER appuser + +ENV PORT=4000 EXPOSE 4000 -HEALTHCHECK --interval=30s --timeout=5s --start-period=10s \ - CMD wget -qO- http://localhost:4000/health || exit 1 -CMD ["node", "src/index.js"] + +CMD ["npm", "start"] diff --git a/backend/src/index.js b/backend/src/index.js index 9adccb9..9e9bdeb 100644 --- a/backend/src/index.js +++ b/backend/src/index.js @@ -10,6 +10,7 @@ const PORT = process.env.PORT || 4000; app.use(cors()); app.use(express.json()); +// Rota de saúde app.get('/health', async (req, res) => { try { await pool.query('SELECT 1'); @@ -19,6 +20,7 @@ app.get('/health', async (req, res) => { } }); +// API de desafios app.use('/api/challenges', challengesRouter); app.listen(PORT, () => { diff --git a/backend/src/routes/challenges.js b/backend/src/routes/challenges.js index 4f87e4a..11d3f5e 100644 --- a/backend/src/routes/challenges.js +++ b/backend/src/routes/challenges.js @@ -2,6 +2,7 @@ const express = require('express'); const router = express.Router(); const { pool } = require('../db'); +// Cria tabela se não existir async function ensureTable() { await pool.query(` CREATE TABLE IF NOT EXISTS challenges ( @@ -21,6 +22,7 @@ async function ensureTable() { } ensureTable().catch(console.error); +// LISTAR todos router.get('/', async (req, res) => { try { const result = await pool.query( @@ -33,11 +35,18 @@ router.get('/', async (req, res) => { } }); +// CRIAR router.post('/', async (req, res) => { try { const { - title, description, difficulty, status, - category, start_date, end_date, progress + title, + description, + difficulty, + status, + category, + start_date, + end_date, + progress } = req.body; const result = await pool.query( @@ -56,6 +65,7 @@ router.post('/', async (req, res) => { progress || 0 ] ); + res.status(201).json(result.rows[0]); } catch (err) { console.error('Erro POST /challenges', err); @@ -63,27 +73,51 @@ router.post('/', async (req, res) => { } }); +// ATUALIZAR router.put('/:id', async (req, res) => { try { const id = Number(req.params.id); const { - title, description, difficulty, status, - category, start_date, end_date, progress + title, + description, + difficulty, + status, + category, + start_date, + end_date, + progress } = req.body; const result = await pool.query( `UPDATE challenges - SET title=$1, description=$2, difficulty=$3, status=$4, - category=$5, start_date=$6, end_date=$7, progress=$8, - updated_at=NOW() - WHERE id=$9 + SET title = $1, + description = $2, + difficulty = $3, + status = $4, + category = $5, + start_date = $6, + end_date = $7, + progress = $8, + updated_at = NOW() + WHERE id = $9 RETURNING *`, - [title, description, difficulty, status, category, start_date, end_date, progress, id] + [ + title, + description, + difficulty, + status, + category, + start_date, + end_date, + progress, + id + ] ); if (result.rowCount === 0) { return res.status(404).json({ error: 'Desafio não encontrado' }); } + res.json(result.rows[0]); } catch (err) { console.error('Erro PUT /challenges/:id', err); @@ -91,16 +125,20 @@ router.put('/:id', async (req, res) => { } }); +// REMOVER router.delete('/:id', async (req, res) => { try { const id = Number(req.params.id); + const result = await pool.query( - 'DELETE FROM challenges WHERE id = $1', [id] + 'DELETE FROM challenges WHERE id = $1', + [id] ); if (result.rowCount === 0) { return res.status(404).json({ error: 'Desafio não encontrado' }); } + res.status(204).send(); } catch (err) { console.error('Erro DELETE /challenges/:id', err); diff --git a/frontend/.dockerignore b/frontend/.dockerignore deleted file mode 100644 index f2ccdb4..0000000 --- a/frontend/.dockerignore +++ /dev/null @@ -1,4 +0,0 @@ -node_modules -dist -.env -.git diff --git a/frontend/Dockerfile b/frontend/Dockerfile index eb127ae..ec022dc 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -1,18 +1,15 @@ -# Stage 1: build da aplicação React -FROM node:18-alpine AS build +FROM node:20-alpine AS build + WORKDIR /app COPY package*.json ./ -RUN npm ci +RUN npm install + COPY . . -ARG VITE_API_URL=/ -ENV VITE_API_URL=$VITE_API_URL RUN npm run build -# Stage 2: servidor NGINX mínimo -FROM nginx:alpine AS runner +FROM nginx:alpine + COPY --from=build /app/dist /usr/share/nginx/html -COPY nginx.conf /etc/nginx/conf.d/default.conf EXPOSE 80 -HEALTHCHECK --interval=30s --timeout=5s \ - CMD wget -qO- http://localhost/nginx-health || exit 1 + CMD ["nginx", "-g", "daemon off;"] diff --git a/frontend/index.html b/frontend/index.html index af8b6fe..b334baa 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -2,8 +2,8 @@ - App de Desafios +
diff --git a/frontend/nginx.conf b/frontend/nginx.conf deleted file mode 100644 index 814e0d4..0000000 --- a/frontend/nginx.conf +++ /dev/null @@ -1,18 +0,0 @@ -server { - listen 80; - server_name _; - root /usr/share/nginx/html; - index index.html; - - location / { - try_files $uri $uri/ /index.html; - } - - location /nginx-health { - return 200 'ok'; - add_header Content-Type text/plain; - } - - gzip on; - gzip_types text/plain text/css application/json application/javascript text/xml application/xml; -} diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 70dbab1..48bbcc1 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -1,6 +1,6 @@ import React, { useEffect, useState } from 'react'; -const API_BASE = import.meta.env.VITE_API_URL || 'http://localhost:4000'; +const API_BASE = 'http://localhost:4000'; const defaultForm = { title: '', @@ -32,7 +32,9 @@ export default function App() { } } - useEffect(() => { loadChallenges(); }, []); + useEffect(() => { + loadChallenges(); + }, []); function handleChange(e) { const { name, value } = e.target; @@ -41,6 +43,7 @@ export default function App() { async function handleSubmit(e) { e.preventDefault(); + const method = editingId ? 'PUT' : 'POST'; const url = editingId ? `${API_BASE}/api/challenges/${editingId}` @@ -52,7 +55,11 @@ export default function App() { body: JSON.stringify(form) }); - if (!res.ok) { alert('Erro ao salvar desafio'); return; } + if (!res.ok) { + alert('Erro ao salvar desafio'); + return; + } + setForm(defaultForm); setEditingId(null); loadChallenges(); @@ -74,8 +81,16 @@ export default function App() { async function handleDelete(id) { if (!confirm('Tem certeza que deseja remover este desafio?')) return; - const res = await fetch(`${API_BASE}/api/challenges/${id}`, { method: 'DELETE' }); - if (!res.ok) { alert('Erro ao remover desafio'); return; } + + const res = await fetch(`${API_BASE}/api/challenges/${id}`, { + method: 'DELETE' + }); + + if (!res.ok) { + alert('Erro ao remover desafio'); + return; + } + loadChallenges(); } @@ -87,33 +102,87 @@ export default function App() {

{editingId ? 'Editar desafio' : 'Novo desafio'}

- -