Skip to content

Commit 5ab87cd

Browse files
committed
Enhance CI workflow and code quality checks; update Alembic migration scripts and improve code formatting. Added new steps for code quality checks using Black, Ruff, and Mypy in the CI pipeline. Refactored migration scripts for consistency and clarity. Updated FastAPI routes to improve type hints and formatting.
1 parent dfa5dce commit 5ab87cd

File tree

15 files changed

+449
-81
lines changed

15 files changed

+449
-81
lines changed
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
name: Bug Report
2+
description: Reporta un fallo reproducible en la app
3+
labels: [bug]
4+
title: "bug: [componente] resumen breve"
5+
body:
6+
- type: textarea
7+
id: resumen
8+
attributes:
9+
label: Resumen
10+
description: ¿Qué está pasando?
11+
placeholder: Descripción breve del bug
12+
validations:
13+
required: true
14+
- type: textarea
15+
id: pasos
16+
attributes:
17+
label: Pasos para reproducir
18+
description: Cómo reproducir el problema
19+
placeholder: |
20+
1. ...
21+
2. ...
22+
3. ...
23+
validations:
24+
required: true
25+
- type: textarea
26+
id: esperado
27+
attributes:
28+
label: Comportamiento esperado
29+
validations:
30+
required: true
31+
- type: input
32+
id: version
33+
attributes:
34+
label: Versión/commit
35+
placeholder: vX.Y.Z o SHA
36+
- type: textarea
37+
id: logs
38+
attributes:
39+
label: Logs relevantes
40+
render: shell

.github/ISSUE_TEMPLATE/config.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
blank_issues_enabled: false
2+
contact_links:
3+
- name: Q&A / Soporte
4+
url: https://github.com/OWNER/REPO/discussions
5+
about: Usa Discussions para preguntas generales.
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
name: Lint/Build Error
2+
description: Error de linter, type-check o build (creado desde logs)
3+
labels: [lint, automation]
4+
title: "lint: [tool] breve mensaje"
5+
body:
6+
- type: input
7+
id: tool
8+
attributes:
9+
label: Herramienta
10+
placeholder: ruff | mypy | pytest | eslint | otros
11+
validations:
12+
required: true
13+
- type: input
14+
id: archivo
15+
attributes:
16+
label: Archivo
17+
placeholder: ruta/archivo:línea
18+
- type: textarea
19+
id: mensaje
20+
attributes:
21+
label: Mensaje
22+
description: Mensaje exacto del error
23+
validations:
24+
required: true
25+
- type: textarea
26+
id: reproduccion
27+
attributes:
28+
label: Reproducción
29+
description: Comando exacto para reproducir
30+
placeholder: |
31+
ejemplo: ruff check app/
32+
validations:
33+
required: true
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
name: Tech Debt
2+
description: Trabajo de mantenimiento o refactor sin bug directo
3+
labels: [tech-debt]
4+
title: "debt: [área] resumen breve"
5+
body:
6+
- type: textarea
7+
id: motivacion
8+
attributes:
9+
label: Motivación / Riesgo
10+
description: ¿Por qué es necesario?
11+
validations:
12+
required: true
13+
- type: textarea
14+
id: alcance
15+
attributes:
16+
label: Alcance
17+
description: Qué archivos o módulos toca
18+
validations:
19+
required: true
20+
- type: textarea
21+
id: criterio
22+
attributes:
23+
label: Criterios de aceptación
24+
description: Definición de Done

.github/workflows/ci.yml

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: CI
1+
name: CI 🧪 FastAPI Quality Pipeline
22

33
on:
44
pull_request:
@@ -9,37 +9,61 @@ on:
99
jobs:
1010
test:
1111
runs-on: ubuntu-latest
12+
1213
steps:
13-
- name: Checkout
14+
- name: 📥 Checkout repository
1415
uses: actions/checkout@v4
1516

16-
- name: Set up Python
17+
- name: 🐍 Set up Python
1718
uses: actions/setup-python@v5
1819
with:
1920
python-version: '3.11'
2021

21-
- name: Install dependencies
22+
- name: 📦 Install dependencies
2223
run: |
2324
python -m pip install --upgrade pip
2425
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
2526
if [ -f requirements-dev.txt ]; then pip install -r requirements-dev.txt || true; fi
27+
pip install black isort ruff mypy pre-commit yamllint
2628
27-
- name: Prepare env
29+
- name: ⚙️ Prepare environment
2830
run: |
2931
cp .env.example .env || true
3032
env:
31-
# Defaults for CI
3233
ENVIRONMENT: testing
3334

34-
- name: Run Alembic migrations
35+
- name: 🧼 Code Quality Checks
36+
run: |
37+
echo "Running Black, Ruff, and Mypy checks..."
38+
black --check .
39+
isort --check-only .
40+
ruff check .
41+
mypy --install-types --non-interactive .
42+
echo "✅ Code Quality stage completed."
43+
44+
- name: 🧩 Pre-commit hooks
45+
run: |
46+
pre-commit run --all-files || true
47+
continue-on-error: true
48+
49+
- name: 🧱 Run Alembic migrations
3550
env:
3651
DATABASE_URL: sqlite+aiosqlite:///./test.db
3752
run: |
3853
alembic upgrade head
3954
40-
- name: Run tests
55+
- name: 🧪 Run tests (pytest)
4156
env:
4257
DATABASE_URL: sqlite+aiosqlite:///./test.db
4358
PYTEST_ADDOPTS: "-q"
4459
run: |
45-
pytest
60+
pytest --maxfail=1 --disable-warnings -q
61+
62+
- name: 📊 Upload coverage
63+
if: success()
64+
uses: codecov/codecov-action@v4
65+
with:
66+
token: ${{ secrets.CODECOV_TOKEN }}
67+
files: ./coverage.xml
68+
fail_ci_if_error: false
69+
verbose: true

alembic/env.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@
22
import os
33
from logging.config import fileConfig
44

5+
from alembic import context
56
from sqlalchemy import pool
67
from sqlalchemy.engine import Connection
78
from sqlalchemy.ext.asyncio import async_engine_from_config
89

9-
from alembic import context
1010
from app import models # noqa: F401 - ensure models are imported
1111
from app.database import Base
1212

alembic/versions/8fe0bc0038f8_init_rbac.py

Lines changed: 79 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,12 @@
55
Create Date: 2025-10-30 10:58:02.168458
66
77
"""
8-
from alembic import op
8+
99
import sqlalchemy as sa
10+
from alembic import op
1011

1112
# revision identifiers, used by Alembic.
12-
revision = '8fe0bc0038f8'
13+
revision = "8fe0bc0038f8"
1314
down_revision = None
1415
branch_labels = None
1516
depends_on = None
@@ -18,88 +19,103 @@
1819
def upgrade() -> None:
1920
# Roles
2021
op.create_table(
21-
'roles',
22-
sa.Column('id', sa.String(length=36), primary_key=True, nullable=False),
23-
sa.Column('name', sa.String(length=50), nullable=False),
24-
sa.Column('description', sa.String(length=255), nullable=True),
25-
sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
26-
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False),
27-
sa.UniqueConstraint('name', name='uq_roles_name'),
22+
"roles",
23+
sa.Column("id", sa.String(length=36), primary_key=True, nullable=False),
24+
sa.Column("name", sa.String(length=50), nullable=False),
25+
sa.Column("description", sa.String(length=255), nullable=True),
26+
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
27+
sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False),
28+
sa.UniqueConstraint("name", name="uq_roles_name"),
2829
)
29-
op.create_index('ix_roles_name', 'roles', ['name'], unique=True)
30+
op.create_index("ix_roles_name", "roles", ["name"], unique=True)
3031

3132
# Permissions
3233
op.create_table(
33-
'permissions',
34-
sa.Column('id', sa.String(length=36), primary_key=True, nullable=False),
35-
sa.Column('name', sa.String(length=100), nullable=False),
36-
sa.Column('resource', sa.String(length=50), nullable=False),
37-
sa.Column('action', sa.String(length=50), nullable=False),
38-
sa.Column('description', sa.String(length=255), nullable=True),
39-
sa.UniqueConstraint('name', name='uq_permissions_name'),
34+
"permissions",
35+
sa.Column("id", sa.String(length=36), primary_key=True, nullable=False),
36+
sa.Column("name", sa.String(length=100), nullable=False),
37+
sa.Column("resource", sa.String(length=50), nullable=False),
38+
sa.Column("action", sa.String(length=50), nullable=False),
39+
sa.Column("description", sa.String(length=255), nullable=True),
40+
sa.UniqueConstraint("name", name="uq_permissions_name"),
4041
)
41-
op.create_index('ix_permissions_name', 'permissions', ['name'], unique=True)
42-
op.create_index('ix_permissions_resource', 'permissions', ['resource'], unique=False)
43-
op.create_index('ix_permissions_action', 'permissions', ['action'], unique=False)
42+
op.create_index("ix_permissions_name", "permissions", ["name"], unique=True)
43+
op.create_index(
44+
"ix_permissions_resource", "permissions", ["resource"], unique=False
45+
)
46+
op.create_index("ix_permissions_action", "permissions", ["action"], unique=False)
4447

4548
# Users
4649
op.create_table(
47-
'users',
48-
sa.Column('id', sa.String(length=36), primary_key=True, nullable=False),
49-
sa.Column('username', sa.String(length=50), nullable=False),
50-
sa.Column('email', sa.String(length=255), nullable=False),
51-
sa.Column('hashed_password', sa.String(length=255), nullable=False),
52-
sa.Column('full_name', sa.String(length=255), nullable=True),
53-
sa.Column('is_active', sa.Boolean(), nullable=False, server_default=sa.text('1')),
54-
sa.Column('is_superuser', sa.Boolean(), nullable=False, server_default=sa.text('0')),
55-
sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
56-
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False),
57-
sa.UniqueConstraint('username', name='uq_users_username'),
58-
sa.UniqueConstraint('email', name='uq_users_email'),
50+
"users",
51+
sa.Column("id", sa.String(length=36), primary_key=True, nullable=False),
52+
sa.Column("username", sa.String(length=50), nullable=False),
53+
sa.Column("email", sa.String(length=255), nullable=False),
54+
sa.Column("hashed_password", sa.String(length=255), nullable=False),
55+
sa.Column("full_name", sa.String(length=255), nullable=True),
56+
sa.Column(
57+
"is_active", sa.Boolean(), nullable=False, server_default=sa.text("1")
58+
),
59+
sa.Column(
60+
"is_superuser", sa.Boolean(), nullable=False, server_default=sa.text("0")
61+
),
62+
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
63+
sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False),
64+
sa.UniqueConstraint("username", name="uq_users_username"),
65+
sa.UniqueConstraint("email", name="uq_users_email"),
5966
)
60-
op.create_index('ix_users_username', 'users', ['username'], unique=True)
61-
op.create_index('ix_users_email', 'users', ['email'], unique=True)
67+
op.create_index("ix_users_username", "users", ["username"], unique=True)
68+
op.create_index("ix_users_email", "users", ["email"], unique=True)
6269

6370
# Association: user_roles
6471
op.create_table(
65-
'user_roles',
66-
sa.Column('user_id', sa.String(length=36), nullable=False),
67-
sa.Column('role_id', sa.String(length=36), nullable=False),
68-
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ondelete='CASCADE'),
69-
sa.ForeignKeyConstraint(['role_id'], ['roles.id'], ondelete='CASCADE'),
72+
"user_roles",
73+
sa.Column("user_id", sa.String(length=36), nullable=False),
74+
sa.Column("role_id", sa.String(length=36), nullable=False),
75+
sa.ForeignKeyConstraint(["user_id"], ["users.id"], ondelete="CASCADE"),
76+
sa.ForeignKeyConstraint(["role_id"], ["roles.id"], ondelete="CASCADE"),
7077
)
71-
op.create_index('ix_user_roles_user_id', 'user_roles', ['user_id'], unique=False)
72-
op.create_index('ix_user_roles_role_id', 'user_roles', ['role_id'], unique=False)
78+
op.create_index("ix_user_roles_user_id", "user_roles", ["user_id"], unique=False)
79+
op.create_index("ix_user_roles_role_id", "user_roles", ["role_id"], unique=False)
7380

7481
# Association: role_permissions
7582
op.create_table(
76-
'role_permissions',
77-
sa.Column('role_id', sa.String(length=36), nullable=False),
78-
sa.Column('permission_id', sa.String(length=36), nullable=False),
79-
sa.ForeignKeyConstraint(['role_id'], ['roles.id'], ondelete='CASCADE'),
80-
sa.ForeignKeyConstraint(['permission_id'], ['permissions.id'], ondelete='CASCADE'),
83+
"role_permissions",
84+
sa.Column("role_id", sa.String(length=36), nullable=False),
85+
sa.Column("permission_id", sa.String(length=36), nullable=False),
86+
sa.ForeignKeyConstraint(["role_id"], ["roles.id"], ondelete="CASCADE"),
87+
sa.ForeignKeyConstraint(
88+
["permission_id"], ["permissions.id"], ondelete="CASCADE"
89+
),
90+
)
91+
op.create_index(
92+
"ix_role_permissions_role_id", "role_permissions", ["role_id"], unique=False
93+
)
94+
op.create_index(
95+
"ix_role_permissions_permission_id",
96+
"role_permissions",
97+
["permission_id"],
98+
unique=False,
8199
)
82-
op.create_index('ix_role_permissions_role_id', 'role_permissions', ['role_id'], unique=False)
83-
op.create_index('ix_role_permissions_permission_id', 'role_permissions', ['permission_id'], unique=False)
84100

85101

86102
def downgrade() -> None:
87-
op.drop_index('ix_role_permissions_permission_id', table_name='role_permissions')
88-
op.drop_index('ix_role_permissions_role_id', table_name='role_permissions')
89-
op.drop_table('role_permissions')
103+
op.drop_index("ix_role_permissions_permission_id", table_name="role_permissions")
104+
op.drop_index("ix_role_permissions_role_id", table_name="role_permissions")
105+
op.drop_table("role_permissions")
90106

91-
op.drop_index('ix_user_roles_role_id', table_name='user_roles')
92-
op.drop_index('ix_user_roles_user_id', table_name='user_roles')
93-
op.drop_table('user_roles')
107+
op.drop_index("ix_user_roles_role_id", table_name="user_roles")
108+
op.drop_index("ix_user_roles_user_id", table_name="user_roles")
109+
op.drop_table("user_roles")
94110

95-
op.drop_index('ix_users_email', table_name='users')
96-
op.drop_index('ix_users_username', table_name='users')
97-
op.drop_table('users')
111+
op.drop_index("ix_users_email", table_name="users")
112+
op.drop_index("ix_users_username", table_name="users")
113+
op.drop_table("users")
98114

99-
op.drop_index('ix_permissions_action', table_name='permissions')
100-
op.drop_index('ix_permissions_resource', table_name='permissions')
101-
op.drop_index('ix_permissions_name', table_name='permissions')
102-
op.drop_table('permissions')
115+
op.drop_index("ix_permissions_action", table_name="permissions")
116+
op.drop_index("ix_permissions_resource", table_name="permissions")
117+
op.drop_index("ix_permissions_name", table_name="permissions")
118+
op.drop_table("permissions")
103119

104-
op.drop_index('ix_roles_name', table_name='roles')
105-
op.drop_table('roles')
120+
op.drop_index("ix_roles_name", table_name="roles")
121+
op.drop_table("roles")

app/backoffice/router.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,11 +124,14 @@ async def search_transactions(
124124
"status": random.choice( # nosec B311
125125
["completed", "pending", "failed", "cancelled"]
126126
),
127-
"type": random.choice(["transfer", "deposit", "withdrawal", "payment"]), # nosec B311
127+
"type": random.choice(
128+
["transfer", "deposit", "withdrawal", "payment"]
129+
), # nosec B311
128130
"user_id": random.randint(1000, 9999), # nosec B311
129131
"description": f"Transaction {tx_id}",
130132
"created_at": (
131-
datetime.now() - timedelta(hours=random.randint(1, 72)) # nosec B311
133+
datetime.now()
134+
- timedelta(hours=random.randint(1, 72)) # nosec B311
132135
).isoformat(),
133136
}
134137
)

0 commit comments

Comments
 (0)