diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index e3660d2b20577804ebcf5a8782d885eec81bc900..f7e2b780414caeee17d67063de5c3104293eb228 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -9,6 +9,10 @@ variables: GCP_REGION: us-central1 TF_ROOT: infra +default: + tags: + - shared + # ───────────────────────────────────────────────────────────── # STAGE: validate # Valida a sintaxe do Terraform antes de qualquer build ou deploy diff --git a/backend/.dockerignore b/backend/.dockerignore deleted file mode 100644 index cab67ef40e2b00a93d7f7d9ae3a55e3bf37a9562..0000000000000000000000000000000000000000 --- 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 0000000000000000000000000000000000000000..135f86b6be7597bd5ef71184645e1c8d4e29777e --- /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 f8e4bb729dabf7bfb29f88c1f5df2e168da9e070..c5407199bc195aa8fc1c974e5bd7c14292044cd0 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 9adccb9a8ea8713598f12e344c3d056736eee586..9e9bdeba80a421e3fab25de0f6ad66749f111cab 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 4f87e4a18bc9b12a21390f99fb0df4afdf7c7c73..11d3f5ed10a4cd01457bcb0208157a0c6df1c7d1 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 f2ccdb4ed502d78767e44b6af8acee217a446630..0000000000000000000000000000000000000000 --- a/frontend/.dockerignore +++ /dev/null @@ -1,4 +0,0 @@ -node_modules -dist -.env -.git diff --git a/frontend/Dockerfile b/frontend/Dockerfile index eb127ae40628c414594c35ed32dc2938e26379a0..ec022dce0c3fc1076d969750252a35d1dde509ec 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 af8b6fef78d84106614ce4e3325a3dd543b805c7..b334baa5f1e580b72c67c7fb125fc99cafee95b5 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -2,8 +2,8 @@
-Carregando...
} {!loading && challenges.length === 0 &&Nenhum desafio ainda.
} +{ch.description}
}