Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
625667c
Merge pull request #1 from fastapi/master
Romularques Mar 20, 2026
51e3c6b
feat: add PJ (Pessoa Jurídica) basic registration
devin-ai-integration[bot] Mar 23, 2026
99c8ee5
fix: regenerate TanStack Router route tree with companies route
devin-ai-integration[bot] Mar 23, 2026
7f19d4c
Merge pull request #4 from EluminiIT/devin/1774289905-cadastro-pj
DanielResgateTI Mar 24, 2026
8eaaaf8
feat: add resume upload with auto-fill for PJ registration form
devin-ai-integration[bot] Mar 24, 2026
84610a4
Merge pull request #5 from EluminiIT/devin/1774360322-resume-upload-a…
DanielResgateTI Mar 25, 2026
a07dfb6
feat: implement PJ invite flow with email token-based registration
devin-ai-integration[bot] Mar 26, 2026
68264bd
chore: remove unused useNavigate import from pj-registration
devin-ai-integration[bot] Mar 26, 2026
39fda6c
fix: address Devin Review bugs - razao_social in invite, complete_reg…
devin-ai-integration[bot] Mar 26, 2026
d47f870
Merge pull request #7 from EluminiIT/devin/1774543164-pj-invite-flow
DanielResgateTI Mar 26, 2026
807fc45
feat: implement user administration with role-based access and audit …
devin-ai-integration[bot] Mar 27, 2026
8e155b7
fix: address Devin Review - privilege escalation guards, read_user_by…
devin-ai-integration[bot] Mar 27, 2026
16826a7
fix: remove password fields from Edit User dialog
devin-ai-integration[bot] Mar 27, 2026
adf0de0
Merge pull request #8 from EluminiIT/devin/1774626691-admin-users-roles
DanielResgateTI Mar 30, 2026
6c4a66b
fix: add ApiError type cast in pj-registration onError handler
Romularques Mar 30, 2026
dff39d4
feat: adapt project from PostgreSQL to SQL Server
Romularques Mar 30, 2026
9a91b86
Merge pull request #10 from EluminiIT/devin/1774899336-fix-frontend-b…
Romularques Mar 30, 2026
689b68a
Merge remote-tracking branch 'origin/master' into devin/1774905208-ad…
Romularques Mar 30, 2026
c5f801b
fix: address CI failures and Devin Review comments
Romularques Mar 30, 2026
a01bbf2
fix: add missing env vars (FRONTEND_HOST, DOCKER_IMAGE_*) to CI env s…
Romularques Mar 30, 2026
e4e1642
fix: move USER_ROLE_LABELS/USER_MANAGER_ROLES to non-generated consta…
Romularques Mar 30, 2026
57f365a
fix: add Changethis1! to default secret check (Devin Review)
Romularques Mar 30, 2026
e2eb954
fix: add SQL Server readiness timeout in CI workflow
Romularques Mar 30, 2026
1953065
fix: update .env from POSTGRES_* to MSSQL_* variables for SQL Server …
Romularques Mar 30, 2026
cae79b8
Merge pull request #11 from EluminiIT/devin/1774905208-adapt-sqlserver
DanielResgateTI Mar 31, 2026
561a756
Hide Items page from delivery; document CRUD as reference
Apr 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions .env
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,13 @@ SMTP_TLS=True
SMTP_SSL=False
SMTP_PORT=587

# Postgres
POSTGRES_SERVER=localhost
POSTGRES_PORT=5432
POSTGRES_DB=app
POSTGRES_USER=postgres
POSTGRES_PASSWORD=changethis
# SQL Server
MSSQL_SERVER=localhost
MSSQL_PORT=1433
MSSQL_DB=app
MSSQL_USER=sa
MSSQL_PASSWORD=Changethis1!
MSSQL_DRIVER=ODBC Driver 18 for SQL Server

SENTRY_DSN=

Expand Down
7 changes: 6 additions & 1 deletion .github/workflows/deploy-production.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,12 @@ jobs:
SMTP_USER: ${{ secrets.SMTP_USER }}
SMTP_PASSWORD: ${{ secrets.SMTP_PASSWORD }}
EMAILS_FROM_EMAIL: ${{ secrets.EMAILS_FROM_EMAIL }}
POSTGRES_PASSWORD: ${{ secrets.POSTGRES_PASSWORD }}
MSSQL_PASSWORD: ${{ secrets.MSSQL_PASSWORD }}
MSSQL_SERVER: ${{ secrets.MSSQL_SERVER }}
MSSQL_PORT: ${{ secrets.MSSQL_PORT }}
MSSQL_DB: ${{ secrets.MSSQL_DB }}
MSSQL_USER: ${{ secrets.MSSQL_USER }}
MSSQL_DRIVER: ${{ secrets.MSSQL_DRIVER }}
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
steps:
- name: Checkout
Expand Down
7 changes: 6 additions & 1 deletion .github/workflows/deploy-staging.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,12 @@ jobs:
SMTP_USER: ${{ secrets.SMTP_USER }}
SMTP_PASSWORD: ${{ secrets.SMTP_PASSWORD }}
EMAILS_FROM_EMAIL: ${{ secrets.EMAILS_FROM_EMAIL }}
POSTGRES_PASSWORD: ${{ secrets.POSTGRES_PASSWORD }}
MSSQL_PASSWORD: ${{ secrets.MSSQL_PASSWORD }}
MSSQL_SERVER: ${{ secrets.MSSQL_SERVER }}
MSSQL_PORT: ${{ secrets.MSSQL_PORT }}
MSSQL_DB: ${{ secrets.MSSQL_DB }}
MSSQL_USER: ${{ secrets.MSSQL_USER }}
MSSQL_DRIVER: ${{ secrets.MSSQL_DRIVER }}
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
steps:
- name: Checkout
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/playwright.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ jobs:
working-directory: backend
- run: bun ci
working-directory: frontend
- name: Generate .env for CI
run: bash scripts/ci-generate-env.sh db
- run: bash scripts/generate-client.sh
- run: docker compose build
- run: docker compose down -v --remove-orphans
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/pre-commit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ jobs:
requirements**.txt
pyproject.toml
uv.lock
- name: Generate .env for CI
run: bash scripts/ci-generate-env.sh localhost
- name: Install backend dependencies
run: uv sync --all-packages
- name: Install frontend dependencies
Expand Down
19 changes: 19 additions & 0 deletions .github/workflows/test-backend.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,27 @@ jobs:
python-version: "3.10"
- name: Install uv
uses: astral-sh/setup-uv@v7
- name: Install ODBC Driver 18 for SQL Server
run: |
curl -fsSL https://packages.microsoft.com/keys/microsoft.asc | sudo gpg --batch --yes --dearmor -o /usr/share/keyrings/microsoft-prod.gpg
echo "deb [arch=amd64 signed-by=/usr/share/keyrings/microsoft-prod.gpg] https://packages.microsoft.com/ubuntu/$(lsb_release -rs)/prod $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/mssql-release.list
sudo apt-get update
sudo ACCEPT_EULA=Y apt-get install -y msodbcsql18 unixodbc-dev
- name: Generate .env for CI
run: bash scripts/ci-generate-env.sh localhost
- run: docker compose down -v --remove-orphans
- run: docker compose up -d db mailcatcher
- name: Wait for SQL Server to be ready
run: |
for i in $(seq 1 30); do
docker compose exec db /opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P "${MSSQL_PASSWORD}" -Q "SELECT 1" -C > /dev/null 2>&1 && exit 0
echo "Waiting for SQL Server... ($i/30)"
sleep 2
done
echo "SQL Server did not become ready in time" && exit 1
- name: Create database
run: |
docker compose exec db /opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P "${MSSQL_PASSWORD}" -Q "IF NOT EXISTS (SELECT name FROM sys.databases WHERE name = '${MSSQL_DB}') CREATE DATABASE [${MSSQL_DB}]" -C
- name: Migrate DB
run: uv run bash scripts/prestart.sh
working-directory: backend
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/test-docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Generate .env for CI
run: bash scripts/ci-generate-env.sh db
- run: docker compose build
- run: docker compose down -v --remove-orphans
- run: docker compose up -d --wait backend frontend adminer
Expand Down
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,18 @@ The input variables, with their default values (some auto generated) are:
- `postgres_password`: (default: `"changethis"`) The password for the PostgreSQL database, stored in .env, you can generate one with the method above.
- `sentry_dsn`: (default: "") The DSN for Sentry, if you are using it, you can set it later in .env.

## Example Items CRUD (reference)

The **Items** feature is a full vertical slice (API, models, UI with table and dialogs, generated client, tests) kept as a **reference for new CRUDs**.

For product delivery, **the Items page is not shown in the app**: the sidebar has no Items entry, and `/items` **redirects to the dashboard**. The code stays in the tree for you to copy or read.

Main entry points:

- **Backend:** `backend/app/api/routes/items.py`, `Item` in `backend/app/models.py`, helpers in `backend/app/crud.py`, tests in `backend/tests/api/routes/test_items.py`.
- **Frontend:** `frontend/src/routes/_layout/items.tsx`, components under `frontend/src/components/Items/`, `ItemsService` in `frontend/src/client/`.
- **E2E:** `frontend/tests/items.spec.ts` covers the redirect for the hidden route.

## Backend Development

Backend docs: [backend/README.md](./backend/README.md).
Expand Down
10 changes: 10 additions & 0 deletions backend/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,16 @@ FROM python:3.10

ENV PYTHONUNBUFFERED=1

# Install Microsoft ODBC Driver 18 for SQL Server
RUN apt-get update && \
apt-get install -y --no-install-recommends curl gnupg apt-transport-https && \
curl -fsSL https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor -o /usr/share/keyrings/microsoft-prod.gpg && \
echo "deb [arch=amd64 signed-by=/usr/share/keyrings/microsoft-prod.gpg] https://packages.microsoft.com/debian/12/prod bookworm main" > /etc/apt/sources.list.d/mssql-release.list && \
apt-get update && \
ACCEPT_EULA=Y apt-get install -y --no-install-recommends msodbcsql18 unixodbc-dev && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*

# Install uv
# Ref: https://docs.astral.sh/uv/guides/integration/docker/#installing-uv
COPY --from=ghcr.io/astral-sh/uv:0.9.26 /uv /uvx /bin/
Expand Down
149 changes: 149 additions & 0 deletions backend/app/alembic/versions/0001_initial_schema.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
"""Initial schema for SQL Server

Revision ID: 0001
Revises:
Create Date: 2026-03-30 21:00:00.000000

"""
import sqlalchemy as sa
import sqlmodel.sql.sqltypes
from alembic import op

# revision identifiers, used by Alembic.
revision = "0001"
down_revision = None
branch_labels = None
depends_on = None


def upgrade():
# Create user table
op.create_table(
"user",
sa.Column("email", sqlmodel.sql.sqltypes.AutoString(length=255), nullable=False),
sa.Column("is_active", sa.Boolean(), nullable=False),
sa.Column("is_superuser", sa.Boolean(), nullable=False),
sa.Column("full_name", sqlmodel.sql.sqltypes.AutoString(length=255), nullable=True),
sa.Column(
"role",
sqlmodel.sql.sqltypes.AutoString(),
nullable=False,
server_default="comercial",
),
sa.Column("id", sa.Uuid(), nullable=False),
sa.Column("hashed_password", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
sa.Column("created_at", sa.DateTime(timezone=True), nullable=True),
sa.PrimaryKeyConstraint("id"),
)
op.create_index(op.f("ix_user_email"), "user", ["email"], unique=True)

# Create item table
op.create_table(
"item",
sa.Column("title", sqlmodel.sql.sqltypes.AutoString(length=255), nullable=False),
sa.Column("description", sqlmodel.sql.sqltypes.AutoString(length=255), nullable=True),
sa.Column("id", sa.Uuid(), nullable=False),
sa.Column("created_at", sa.DateTime(timezone=True), nullable=True),
sa.Column("owner_id", sa.Uuid(), nullable=False),
sa.ForeignKeyConstraint(["owner_id"], ["user.id"], ondelete="CASCADE"),
sa.PrimaryKeyConstraint("id"),
)

# Create company table
op.create_table(
"company",
sa.Column("cnpj", sqlmodel.sql.sqltypes.AutoString(length=20), nullable=False),
sa.Column("razao_social", sqlmodel.sql.sqltypes.AutoString(length=255), nullable=True),
sa.Column("representante_legal", sqlmodel.sql.sqltypes.AutoString(length=255), nullable=True),
sa.Column("data_abertura", sa.Date(), nullable=True),
sa.Column("nome_fantasia", sqlmodel.sql.sqltypes.AutoString(length=255), nullable=True),
sa.Column("porte", sqlmodel.sql.sqltypes.AutoString(length=100), nullable=True),
sa.Column("atividade_economica_principal", sqlmodel.sql.sqltypes.AutoString(length=255), nullable=True),
sa.Column("atividade_economica_secundaria", sqlmodel.sql.sqltypes.AutoString(length=255), nullable=True),
sa.Column("natureza_juridica", sqlmodel.sql.sqltypes.AutoString(length=255), nullable=True),
sa.Column("logradouro", sqlmodel.sql.sqltypes.AutoString(length=255), nullable=True),
sa.Column("numero", sqlmodel.sql.sqltypes.AutoString(length=20), nullable=True),
sa.Column("complemento", sqlmodel.sql.sqltypes.AutoString(length=255), nullable=True),
sa.Column("cep", sqlmodel.sql.sqltypes.AutoString(length=10), nullable=True),
sa.Column("bairro", sqlmodel.sql.sqltypes.AutoString(length=255), nullable=True),
sa.Column("municipio", sqlmodel.sql.sqltypes.AutoString(length=255), nullable=True),
sa.Column("uf", sqlmodel.sql.sqltypes.AutoString(length=2), nullable=True),
sa.Column("endereco_eletronico", sqlmodel.sql.sqltypes.AutoString(length=255), nullable=True),
sa.Column("telefone_comercial", sqlmodel.sql.sqltypes.AutoString(length=20), nullable=True),
sa.Column("situacao_cadastral", sqlmodel.sql.sqltypes.AutoString(length=100), nullable=True),
sa.Column("data_situacao_cadastral", sa.Date(), nullable=True),
sa.Column("cpf_representante_legal", sqlmodel.sql.sqltypes.AutoString(length=14), nullable=True),
sa.Column("identidade_representante_legal", sqlmodel.sql.sqltypes.AutoString(length=20), nullable=True),
sa.Column("logradouro_representante_legal", sqlmodel.sql.sqltypes.AutoString(length=255), nullable=True),
sa.Column("numero_representante_legal", sqlmodel.sql.sqltypes.AutoString(length=20), nullable=True),
sa.Column("complemento_representante_legal", sqlmodel.sql.sqltypes.AutoString(length=255), nullable=True),
sa.Column("cep_representante_legal", sqlmodel.sql.sqltypes.AutoString(length=10), nullable=True),
sa.Column("bairro_representante_legal", sqlmodel.sql.sqltypes.AutoString(length=255), nullable=True),
sa.Column("municipio_representante_legal", sqlmodel.sql.sqltypes.AutoString(length=255), nullable=True),
sa.Column("uf_representante_legal", sqlmodel.sql.sqltypes.AutoString(length=2), nullable=True),
sa.Column("endereco_eletronico_representante_legal", sqlmodel.sql.sqltypes.AutoString(length=255), nullable=True),
sa.Column("telefones_representante_legal", sqlmodel.sql.sqltypes.AutoString(length=40), nullable=True),
sa.Column("data_nascimento_representante_legal", sa.Date(), nullable=True),
sa.Column("banco_cc_cnpj", sqlmodel.sql.sqltypes.AutoString(length=100), nullable=True),
sa.Column("agencia_cc_cnpj", sqlmodel.sql.sqltypes.AutoString(length=20), nullable=True),
sa.Column("email", sqlmodel.sql.sqltypes.AutoString(length=255), nullable=True),
sa.Column(
"status",
sqlmodel.sql.sqltypes.AutoString(),
nullable=False,
server_default="completed",
),
sa.Column("id", sa.Uuid(), nullable=False),
sa.Column("created_at", sa.DateTime(timezone=True), nullable=True),
sa.PrimaryKeyConstraint("id"),
)
op.create_index(op.f("ix_company_cnpj"), "company", ["cnpj"], unique=True)

# Create companyinvite table
op.create_table(
"companyinvite",
sa.Column("email", sqlmodel.sql.sqltypes.AutoString(length=255), nullable=False),
sa.Column("id", sa.Uuid(), nullable=False),
sa.Column("company_id", sa.Uuid(), nullable=False),
sa.Column("token", sqlmodel.sql.sqltypes.AutoString(length=500), nullable=False),
sa.Column("expires_at", sa.DateTime(timezone=True), nullable=False),
sa.Column("used", sa.Boolean(), nullable=False, server_default=sa.text("0")),
sa.Column("created_at", sa.DateTime(timezone=True), nullable=True),
sa.ForeignKeyConstraint(["company_id"], ["company.id"], ondelete="CASCADE"),
sa.PrimaryKeyConstraint("id"),
)
op.create_index(op.f("ix_companyinvite_token"), "companyinvite", ["token"], unique=True)

# Create auditlog table
op.create_table(
"auditlog",
sa.Column("id", sa.Uuid(), nullable=False),
sa.Column(
"action",
sqlmodel.sql.sqltypes.AutoString(),
nullable=False,
),
sa.Column("target_user_id", sa.Uuid(), nullable=False),
sa.Column("performed_by_id", sa.Uuid(), nullable=False),
sa.Column(
"changes",
sqlmodel.sql.sqltypes.AutoString(length=2000),
nullable=False,
server_default="",
),
sa.Column("created_at", sa.DateTime(timezone=True), nullable=True),
sa.ForeignKeyConstraint(["target_user_id"], ["user.id"]),
sa.ForeignKeyConstraint(["performed_by_id"], ["user.id"]),
sa.PrimaryKeyConstraint("id"),
)


def downgrade():
op.drop_table("auditlog")
op.drop_index(op.f("ix_companyinvite_token"), table_name="companyinvite")
op.drop_table("companyinvite")
op.drop_index(op.f("ix_company_cnpj"), table_name="company")
op.drop_table("company")
op.drop_table("item")
op.drop_index(op.f("ix_user_email"), table_name="user")
op.drop_table("user")

This file was deleted.

This file was deleted.

Loading
Loading