From 9baa6fcb30428f8558c88d16a00093efa4654ab4 Mon Sep 17 00:00:00 2001 From: icrcode Date: Sun, 21 Jun 2026 17:55:46 -0300 Subject: [PATCH] =?UTF-8?q?feat:=20adicionar=20fun=C3=A7=C3=A3o=20para=20v?= =?UTF-8?q?incular=20coordenador=20a=20curso=20e=20criar=20migra=C3=A7?= =?UTF-8?q?=C3=A3o=20para=20re-sincroniza=C3=A7=C3=A3o?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/modulos/cursos/repositorio.ts | 10 ++++++++++ backend/src/modulos/usuarios/rotas.ts | 9 +++++++++ infra/migrations/005_backfill_coordenadores_cursos.sql | 10 ++++++++++ 3 files changed, 29 insertions(+) create mode 100644 infra/migrations/005_backfill_coordenadores_cursos.sql diff --git a/backend/src/modulos/cursos/repositorio.ts b/backend/src/modulos/cursos/repositorio.ts index 9a9f2f4..fc1052f 100644 --- a/backend/src/modulos/cursos/repositorio.ts +++ b/backend/src/modulos/cursos/repositorio.ts @@ -211,6 +211,16 @@ export async function coordenadorTemCurso(coordenadorId: string, cursoId: string return (res.rowCount ?? 0) > 0; } +// Vincula um coordenador a um curso (idempotente) +export async function vincularCoordenadorAoCurso(coordenadorId: string, cursoId: string): Promise { + await pool.query( + `INSERT INTO coordenadores_cursos (coordenador_id, curso_id) + VALUES ($1, $2) + ON CONFLICT DO NOTHING`, + [coordenadorId, cursoId], + ); +} + // Lista os estudantes vinculados a um curso (somente leitura) export async function listarAlunosDoCurso(cursoId: string): Promise { const res = await pool.query( diff --git a/backend/src/modulos/usuarios/rotas.ts b/backend/src/modulos/usuarios/rotas.ts index 33bc4b6..05eb8fd 100644 --- a/backend/src/modulos/usuarios/rotas.ts +++ b/backend/src/modulos/usuarios/rotas.ts @@ -3,6 +3,7 @@ import bcrypt from 'bcryptjs'; import { autenticar } from '../../middleware/autenticacao'; import { exigirPerfil } from '../../middleware/autorizacao'; import * as repositorio from './repositorio'; +import { vincularCoordenadorAoCurso } from '../cursos/repositorio'; import { tratarErro } from '../../utils/erros'; const router = Router(); @@ -189,6 +190,10 @@ router.post('/', autenticar, exigirPerfil('admin'), async (req, res) => { curso_id: curso_id || null, }); + if (perfil === 'coordenador' && curso_id) { + await vincularCoordenadorAoCurso(usuario.id, curso_id); + } + res.status(201).json(usuario); } catch (err: unknown) { tratarErro(res, err, 'usuarios/criar'); @@ -247,6 +252,10 @@ router.put('/:id', autenticar, exigirPerfil('admin'), async (req, res) => { return; } + if (usuario.perfil === 'coordenador' && usuario.curso_id) { + await vincularCoordenadorAoCurso(usuario.id, usuario.curso_id); + } + res.json(usuario); } catch (err: unknown) { tratarErro(res, err, 'usuarios/atualizar'); diff --git a/infra/migrations/005_backfill_coordenadores_cursos.sql b/infra/migrations/005_backfill_coordenadores_cursos.sql new file mode 100644 index 0000000..605d1ef --- /dev/null +++ b/infra/migrations/005_backfill_coordenadores_cursos.sql @@ -0,0 +1,10 @@ +-- Migration 005: Re-sincroniza coordenadores_cursos para coordenadores +-- que possuem curso_id em usuarios mas não têm entrada na tabela de vínculo. +-- +-- Aplicar: +-- psql -U postgres -d valida_db -f infra/migrations/005_backfill_coordenadores_cursos.sql + +INSERT INTO coordenadores_cursos (coordenador_id, curso_id) +SELECT id, curso_id FROM usuarios +WHERE perfil = 'coordenador' AND curso_id IS NOT NULL +ON CONFLICT DO NOTHING;