From 3f006b3bd8cd552f76c315a58addd8d20dd6d431 Mon Sep 17 00:00:00 2001 From: Eduardo Lauer Date: Thu, 31 Jul 2025 17:34:00 -0300 Subject: [PATCH 1/6] set TEST_ENVIRONMENT to true (local development) --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 91267c3..3b992ac 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -42,7 +42,7 @@ services: # to new `SECRET` run openssl rand -hex 32 SECRET: b8a3054ba3457614e95a88cc0807384430c1b338a54e95e4245f41e060da68bc ACCESS_TOKEN_EXPIRE_MINUTES: 30 - TEST_ENVIRONMENT: 'False' + TEST_ENVIRONMENT: 'True' MAIL_USERNAME: '' MAIL_FROM: admin@api-pgd.gov.br MAIL_PORT: 25 From 674267e3d09ae2bbb8e2844369dc2578cb18dc0a Mon Sep 17 00:00:00 2001 From: Eduardo Lauer Date: Thu, 31 Jul 2025 17:35:21 -0300 Subject: [PATCH 2/6] include create_audit_ddl on api startup --- src/api.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/api.py b/src/api.py index 94e5681..8e69b2f 100644 --- a/src/api.py +++ b/src/api.py @@ -21,6 +21,7 @@ from db_config import ( check_db_connection, create_db_and_tables, + create_audit_ddl, DbContextManager, get_db, ) @@ -66,6 +67,8 @@ async def lifespan(application: FastAPI): # pylint: disable=unused-argument """Executa as rotinas de inicialização da API.""" try: await create_db_and_tables() + if not TEST_ENVIRONMENT: + await create_audit_ddl() await crud_auth.init_user_admin() except OperationalError as exception: logger.error("A inicialização do banco de dados falhou: %s", exception) From e47a6faba0f72cafe563a555b80e4800de2df3dc Mon Sep 17 00:00:00 2001 From: Eduardo Lauer Date: Thu, 31 Jul 2025 17:36:09 -0300 Subject: [PATCH 3/6] create triggers for audit table --- src/db_audit.py | 69 ++++++++++++++++++++++++++++++++++++++++++++++++ src/db_config.py | 6 +++++ 2 files changed, 75 insertions(+) create mode 100644 src/db_audit.py diff --git a/src/db_audit.py b/src/db_audit.py new file mode 100644 index 0000000..631be1d --- /dev/null +++ b/src/db_audit.py @@ -0,0 +1,69 @@ +AUDIT_DDL = """ + CREATE SCHEMA IF NOT EXISTS auditoria; + + CREATE TABLE IF NOT EXISTS auditoria.auditoria_db ( + id SERIAL PRIMARY KEY, + operacao TEXT, + tabela TEXT, + registro_antigo JSONB, + registro_novo JSONB, + usuario TEXT, + data_operacao TIMESTAMPTZ DEFAULT now() + ); + + CREATE OR REPLACE FUNCTION auditoria.fn_auditoria() RETURNS TRIGGER AS $$ + BEGIN + IF TG_OP = 'INSERT' THEN + INSERT INTO auditoria.auditoria_db (operacao, tabela, registro_novo, usuario) + VALUES ('INSERT', TG_TABLE_NAME, to_jsonb(NEW), current_user); + + ELSIF TG_OP = 'UPDATE' THEN + INSERT INTO auditoria.auditoria_db (operacao, tabela, registro_antigo, registro_novo, usuario) + VALUES ('UPDATE', TG_TABLE_NAME, to_jsonb(OLD), to_jsonb(NEW), current_user); + + ELSIF TG_OP = 'DELETE' THEN + INSERT INTO auditoria.auditoria_db (operacao, tabela, registro_antigo, usuario) + VALUES ('DELETE', TG_TABLE_NAME, to_jsonb(OLD), current_user); + END IF; + + RETURN NULL; + END; + $$ LANGUAGE plpgsql; + + DROP TRIGGER IF EXISTS tr_auditoria_pt ON plano_trabalho; + DROP TRIGGER IF EXISTS tr_auditoria_pe ON plano_entregas; + DROP TRIGGER IF EXISTS tr_auditoria_part ON participante; + DROP TRIGGER IF EXISTS tr_auditoria_us ON users; + DROP TRIGGER IF EXISTS tr_auditoria_co ON contribuicao; + DROP TRIGGER IF EXISTS tr_auditoria_en ON entrega; + DROP TRIGGER IF EXISTS tr_auditoria_are ON avaliacao_registros_execucao; + + + CREATE TRIGGER tr_auditoria_pt + AFTER INSERT OR UPDATE OR DELETE ON plano_trabalho + FOR EACH ROW EXECUTE FUNCTION auditoria.fn_auditoria(); + + CREATE TRIGGER tr_auditoria_pe + AFTER INSERT OR UPDATE OR DELETE ON plano_entregas + FOR EACH ROW EXECUTE FUNCTION auditoria.fn_auditoria(); + + CREATE TRIGGER tr_auditoria_part + AFTER INSERT OR UPDATE OR DELETE ON participante + FOR EACH ROW EXECUTE FUNCTION auditoria.fn_auditoria(); + + CREATE TRIGGER tr_auditoria_us + AFTER INSERT OR UPDATE OR DELETE ON users + FOR EACH ROW EXECUTE FUNCTION auditoria.fn_auditoria(); + + CREATE TRIGGER tr_auditoria_co + AFTER INSERT OR UPDATE OR DELETE ON contribuicao + FOR EACH ROW EXECUTE FUNCTION auditoria.fn_auditoria(); + + CREATE TRIGGER tr_auditoria_en + AFTER INSERT OR UPDATE OR DELETE ON entrega + FOR EACH ROW EXECUTE FUNCTION auditoria.fn_auditoria(); + + CREATE TRIGGER tr_auditoria_are + AFTER INSERT OR UPDATE OR DELETE ON avaliacao_registros_execucao + FOR EACH ROW EXECUTE FUNCTION auditoria.fn_auditoria(); +""" \ No newline at end of file diff --git a/src/db_config.py b/src/db_config.py index fab3450..2236485 100644 --- a/src/db_config.py +++ b/src/db_config.py @@ -8,6 +8,7 @@ from sqlalchemy.orm import DeclarativeBase from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine from sqlalchemy.sql import text +from db_audit import AUDIT_DDL SQLALCHEMY_DATABASE_URL = os.environ["SQLALCHEMY_DATABASE_URL"] @@ -29,6 +30,11 @@ async def create_db_and_tables(): async with engine.begin() as conn: await conn.run_sync(Base.metadata.create_all) +async def create_audit_ddl(): + + async with engine.begin() as conn: + await conn.execute(text(AUDIT_DDL)) + async def get_async_session() -> AsyncGenerator[AsyncSession, None]: """Retorna a sessão do banco de dados. From c2a4c564d5a31f933137d966569266e0db075a3c Mon Sep 17 00:00:00 2001 From: Eduardo Lauer Date: Tue, 5 Aug 2025 15:18:02 -0300 Subject: [PATCH 4/6] add env var DB_AUDIT_LOGS --- docker-compose.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/docker-compose.yml b/docker-compose.yml index 3b992ac..94c8167 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -43,6 +43,7 @@ services: SECRET: b8a3054ba3457614e95a88cc0807384430c1b338a54e95e4245f41e060da68bc ACCESS_TOKEN_EXPIRE_MINUTES: 30 TEST_ENVIRONMENT: 'True' + DB_AUDIT_LOGS_ENABLED: 'True' MAIL_USERNAME: '' MAIL_FROM: admin@api-pgd.gov.br MAIL_PORT: 25 From 9f884783852111f07eaa0b97fc60ca3fc836806c Mon Sep 17 00:00:00 2001 From: Eduardo Lauer Date: Tue, 5 Aug 2025 15:19:51 -0300 Subject: [PATCH 5/6] add condition for new env var --- src/api.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/api.py b/src/api.py index 8e69b2f..bfed964 100644 --- a/src/api.py +++ b/src/api.py @@ -22,6 +22,7 @@ check_db_connection, create_db_and_tables, create_audit_ddl, + remove_audit_triggers, DbContextManager, get_db, ) @@ -35,7 +36,7 @@ os.environ.get("ACCESS_TOKEN_EXPIRE_MINUTES", DEFAULT_TOKEN_EXPIRE_MINS) ) TEST_ENVIRONMENT = os.environ.get("TEST_ENVIRONMENT", "False") == "True" - +DB_AUDIT_LOGS_ENABLED = os.environ.get("DB_AUDIT_LOGS_ENABLED", "False") == "True" # ## INIT -------------------------------------------------- with open( @@ -67,8 +68,10 @@ async def lifespan(application: FastAPI): # pylint: disable=unused-argument """Executa as rotinas de inicialização da API.""" try: await create_db_and_tables() - if not TEST_ENVIRONMENT: + if DB_AUDIT_LOGS_ENABLED: await create_audit_ddl() + else: + await remove_audit_triggers() await crud_auth.init_user_admin() except OperationalError as exception: logger.error("A inicialização do banco de dados falhou: %s", exception) From fae76ac6e23b377950ebb431816aa6763163bd85 Mon Sep 17 00:00:00 2001 From: Eduardo Lauer Date: Tue, 5 Aug 2025 15:20:33 -0300 Subject: [PATCH 6/6] add ddl for removing audit triggers --- src/db_audit.py | 10 ++++++++++ src/db_config.py | 7 ++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/db_audit.py b/src/db_audit.py index 631be1d..a6a1002 100644 --- a/src/db_audit.py +++ b/src/db_audit.py @@ -66,4 +66,14 @@ CREATE TRIGGER tr_auditoria_are AFTER INSERT OR UPDATE OR DELETE ON avaliacao_registros_execucao FOR EACH ROW EXECUTE FUNCTION auditoria.fn_auditoria(); +""" + +REMOVE_AUDIT_TRIGGERS = """ + DROP TRIGGER IF EXISTS tr_auditoria_pt ON plano_trabalho; + DROP TRIGGER IF EXISTS tr_auditoria_pe ON plano_entregas; + DROP TRIGGER IF EXISTS tr_auditoria_part ON participante; + DROP TRIGGER IF EXISTS tr_auditoria_us ON users; + DROP TRIGGER IF EXISTS tr_auditoria_co ON contribuicao; + DROP TRIGGER IF EXISTS tr_auditoria_en ON entrega; + DROP TRIGGER IF EXISTS tr_auditoria_are ON avaliacao_registros_execucao; """ \ No newline at end of file diff --git a/src/db_config.py b/src/db_config.py index 2236485..29064ca 100644 --- a/src/db_config.py +++ b/src/db_config.py @@ -8,7 +8,7 @@ from sqlalchemy.orm import DeclarativeBase from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine from sqlalchemy.sql import text -from db_audit import AUDIT_DDL +from db_audit import AUDIT_DDL, REMOVE_AUDIT_TRIGGERS SQLALCHEMY_DATABASE_URL = os.environ["SQLALCHEMY_DATABASE_URL"] @@ -35,6 +35,11 @@ async def create_audit_ddl(): async with engine.begin() as conn: await conn.execute(text(AUDIT_DDL)) +async def remove_audit_triggers(): + + async with engine.begin() as conn: + await conn.execute(text(REMOVE_AUDIT_TRIGGERS)) + async def get_async_session() -> AsyncGenerator[AsyncSession, None]: """Retorna a sessão do banco de dados.