feat: Implement license and tenant management APIs

- Added endpoints for license management including creation, retrieval, update, validation, and usage tracking.
- Developed service layer for business logic related to licenses.
- Introduced tenant management APIs for creating, updating, listing, and deleting tenants.
- Implemented user-tenant relationship management with endpoints for adding, removing, and updating user roles in tenants.
- Created DTOs for data transfer between layers and models for ORM mapping.
- Enhanced logging and error handling across services.
This commit is contained in:
2025-12-09 11:51:18 -06:00
parent 3992a9436b
commit 44eef08445
23 changed files with 25 additions and 25 deletions

View File

@@ -20,7 +20,7 @@ class TimestampMixin:
class TenantScopedMixin:
"""Mixin for tenant and company scoped entities"""
tenant_id: Mapped[int] = mapped_column(Integer, ForeignKey("a76.tenants.id"), nullable=False, index=True)
tenant_id: Mapped[int] = mapped_column(Integer, ForeignKey("core.tenants.id"), nullable=False, index=True)
company_id: Mapped[int] = mapped_column(Integer, ForeignKey("a76.company.id"), nullable=False, index=True)

View File

@@ -31,7 +31,7 @@ class Company(Base, TimestampMixin):
# Primary key
id: Mapped[int] = mapped_column(Integer, primary_key=True)
tenant_id: Mapped[int] = mapped_column(Integer, ForeignKey("a76.tenants.id"), nullable=False, index=True)
tenant_id: Mapped[int] = mapped_column(Integer, ForeignKey("core.tenants.id"), nullable=False, index=True)
# Información básica de la empresa
name: Mapped[Optional[str]] = mapped_column(String(255))

View File

@@ -8,7 +8,7 @@ from fastapi import APIRouter
from .customs_brokers.routes import router as customs_broker_router
# Importar routers de módulos
from .auth import router as auth_router
from ..core.auth import router as auth_router
from .classes import router as classes_router
from .clients_and_providers import router as client_and_provider_router
from .general_catalogs.company import router as company_router
@@ -17,7 +17,7 @@ from .transportation.drivers.routes import router as drivers_router
from .general_catalogs.exchange_rate.routes import router as exchange_rate_router
from .general_catalogs.identifiers.routes import router as identifiers_router
from .fraction_rule_octave.routes import router as fraction_rule_octave_router
from .licenses import router as licenses_router
from ..core.licenses import router as licenses_router
from .general_catalogs.packages.routes import router as package_router
from .general_catalogs.ports.routes import router as ports_router
from .parts import router as parts_router
@@ -38,10 +38,10 @@ from .general_catalogs.error_catalogs.routes import router as error_catalogs_rou
from .general_catalogs.doda.routes import router as doda_router
from .general_catalogs.prevalidators.routes import router as prevalidators_router
from .general_catalogs.electronic_notices.routes import router as electronic_notices_router
from .tenants import router as tenants_router
from ..core.tenants import router as tenants_router
from .transportation.trailers.routes import router as trailers_router
from .transportation.transporters.routes import router as transporters_router
from .user_tenant.routes import router as user_tenant_router
from ..core.user_tenant.routes import router as user_tenant_router
from .transportation.vehicles.routes import router as vehicles_router
# Router principal

View File

@@ -4,8 +4,8 @@ Servicio de autenticación con Keycloak
import logging
from api.v1.modules.a76.tenants.service import TenantService
from api.v1.modules.a76.user_tenant.service import UserTenantService
from api.v1.modules.core.tenants.service import TenantService
from api.v1.modules.core.user_tenant.service import UserTenantService
from core.config import settings
from fastapi import HTTPException
from keycloak import KeycloakAdmin, KeycloakOpenID
@@ -266,7 +266,7 @@ class AuthService:
"""
try:
# Verificar que el tenant existe
from api.v1.modules.a76.tenants.service import TenantService
from api.v1.modules.core.tenants.service import TenantService
tenant_service = TenantService(self.db)
tenant = tenant_service.get_tenant_by_slug(register_data.tenant_slug)
@@ -319,7 +319,7 @@ class AuthService:
# Agregar el usuario al tenant en la base de datos
try:
from api.v1.modules.a76.user_tenant.service import UserTenantService
from api.v1.modules.core.user_tenant.service import UserTenantService
user_tenant_service = UserTenantService(self.db)
user_tenant_service.add_user_to_tenant(

View File

@@ -36,11 +36,11 @@ class License(Base, TimestampMixin):
"""
__tablename__ = "licenses"
__table_args__ = {"schema": "a76"}
__table_args__ = {"schema": "core"}
id = Column(Integer, primary_key=True, index=True)
tenant_id = Column(
Integer, ForeignKey("a76.tenants.id"), nullable=False, unique=True, index=True
Integer, ForeignKey("core.tenants.id"), nullable=False, unique=True, index=True
)
# Plan y características
@@ -74,11 +74,11 @@ class LicenseUsage(Base, TimestampMixin):
"""
__tablename__ = "license_usage"
__table_args__ = {"schema": "a76"}
__table_args__ = {"schema": "core"}
id = Column(Integer, primary_key=True, index=True)
tenant_id = Column(
Integer, ForeignKey("a76.tenants.id"), nullable=False, index=True
Integer, ForeignKey("core.tenants.id"), nullable=False, index=True
)
# Métricas de uso

View File

@@ -13,7 +13,7 @@ from sqlalchemy import Integer, String, Text
from sqlalchemy.orm import Mapped, relationship
if TYPE_CHECKING:
from api.v1.modules.a76.user_tenant.models import UserTenant
from api.v1.modules.core.user_tenant.models import UserTenant
class TenantType(enum.Enum):
@@ -30,7 +30,7 @@ class Tenant(Base, TimestampMixin):
"""
__tablename__ = "tenants"
__table_args__ = {"schema": "a76", "extend_existing": True}
__table_args__ = {"schema": "core", "extend_existing": True}
id = Column(Integer, primary_key=True, index=True)
name = Column(String(255), nullable=False, index=True)

View File

@@ -10,7 +10,7 @@ from sqlalchemy import Boolean, ForeignKeyConstraint, String, UniqueConstraint
from sqlalchemy.orm import Mapped, mapped_column, relationship
if TYPE_CHECKING:
from api.v1.modules.a76.tenants.models import Tenant
from api.v1.modules.core.tenants.models import Tenant
class UserTenant(Base, TenantScopedMixin, TimestampMixin):
@@ -26,7 +26,7 @@ class UserTenant(Base, TenantScopedMixin, TimestampMixin):
UniqueConstraint(
"keycloak_user_id", "tenant_id", "company_id", name="uq_user_tenant"
),
{"schema": "a76", "extend_existing": True},
{"schema": "core", "extend_existing": True},
)
# Primary Key

View File

@@ -502,17 +502,17 @@ fi
# Insertar o actualizar tenant
echo "Insertando tenant en PostgreSQL..."
docker exec -e PGPASSWORD="${POSTGRES_PASSWORD}" anexo76-postgres-a76 psql -h localhost -p 5432 -U "${POSTGRES_USER}" -d "${POSTGRES_DB}" -c "INSERT INTO a76.tenants (name, slug, type, keycloak_realm, contact_email, is_active, created_at, updated_at) VALUES ('${TENANT_NAME}', '${TENANT_SLUG}', 'SHARED'::tenanttype, '${KEYCLOAK_REALM}', '${DEMO_EMAIL}', true, now(), now()) ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name, contact_email = EXCLUDED.contact_email, updated_at = CURRENT_TIMESTAMP;" 2>&1
docker exec -e PGPASSWORD="${POSTGRES_PASSWORD}" anexo76-postgres-a76 psql -h localhost -p 5432 -U "${POSTGRES_USER}" -d "${POSTGRES_DB}" -c "INSERT INTO core.tenants (name, slug, type, keycloak_realm, contact_email, is_active, created_at, updated_at) VALUES ('${TENANT_NAME}', '${TENANT_SLUG}', 'SHARED'::tenanttype, '${KEYCLOAK_REALM}', '${DEMO_EMAIL}', true, now(), now()) ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name, contact_email = EXCLUDED.contact_email, updated_at = CURRENT_TIMESTAMP;" 2>&1
# Obtener el ID del tenant con mejor manejo de errores
echo "Obteniendo ID del tenant..."
TENANT_ID_RESULT=$(docker exec -e PGPASSWORD="${POSTGRES_PASSWORD}" anexo76-postgres-a76 psql -h localhost -p 5432 -U "${POSTGRES_USER}" -d "${POSTGRES_DB}" -t -c "SELECT id FROM a76.tenants WHERE slug = '${TENANT_SLUG}';" 2>&1)
TENANT_ID_RESULT=$(docker exec -e PGPASSWORD="${POSTGRES_PASSWORD}" anexo76-postgres-a76 psql -h localhost -p 5432 -U "${POSTGRES_USER}" -d "${POSTGRES_DB}" -t -c "SELECT id FROM core.tenants WHERE slug = '${TENANT_SLUG}';" 2>&1)
if [ $? -ne 0 ]; then
echo -e "${RED}✗ Error al consultar el tenant:${NC}"
echo "$TENANT_ID_RESULT"
echo -e "${YELLOW}Verificando si la tabla existe...${NC}"
docker exec -e PGPASSWORD="${POSTGRES_PASSWORD}" anexo76-postgres-a76 psql -h localhost -p 5432 -U "${POSTGRES_USER}" -d "${POSTGRES_DB}" -c "\dt a76.tenants;"
docker exec -e PGPASSWORD="${POSTGRES_PASSWORD}" anexo76-postgres-a76 psql -h localhost -p 5432 -U "${POSTGRES_USER}" -d "${POSTGRES_DB}" -c "\dt core.tenants;"
exit 1
fi
@@ -522,7 +522,7 @@ if [ -z "$TENANT_ID" ]; then
echo -e "${RED}✗ Error: No se pudo obtener el ID del tenant${NC}"
echo "Resultado de la consulta: '$TENANT_ID_RESULT'"
echo -e "${YELLOW}Intentando ver todos los tenants...${NC}"
docker exec -e PGPASSWORD="${POSTGRES_PASSWORD}" anexo76-postgres-a76 psql -h localhost -p 5432 -U "${POSTGRES_USER}" -d "${POSTGRES_DB}" -c "SELECT id, slug FROM a76.tenants LIMIT 10;"
docker exec -e PGPASSWORD="${POSTGRES_PASSWORD}" anexo76-postgres-a76 psql -h localhost -p 5432 -U "${POSTGRES_USER}" -d "${POSTGRES_DB}" -c "SELECT id, slug FROM core.tenants LIMIT 10;"
exit 1
fi
@@ -561,7 +561,7 @@ echo -e "${GREEN}✓ Atributo tenant_id asignado al usuario${NC}"
# Agregar relación usuario-tenant en la base de datos
echo -e "\n${YELLOW}Creando relación usuario-tenant en la base de datos...${NC}"
docker exec -e PGPASSWORD="${POSTGRES_PASSWORD}" anexo76-postgres-a76 psql -h localhost -p 5432 -U "${POSTGRES_USER}" -d "${POSTGRES_DB}" -c "INSERT INTO a76.user_tenants (keycloak_user_id, tenant_id, company_id, role, is_active, created_at, updated_at) VALUES ('${USER_ID}', ${TENANT_ID}, 1, 'admin', true, now(), now()) ON CONFLICT (keycloak_user_id, tenant_id, company_id) DO UPDATE SET is_active = true, updated_at = CURRENT_TIMESTAMP;" 2>&1
docker exec -e PGPASSWORD="${POSTGRES_PASSWORD}" anexo76-postgres-a76 psql -h localhost -p 5432 -U "${POSTGRES_USER}" -d "${POSTGRES_DB}" -c "INSERT INTO core.user_tenants (keycloak_user_id, tenant_id, company_id, role, is_active, created_at, updated_at) VALUES ('${USER_ID}', ${TENANT_ID}, 1, 'admin', true, now(), now()) ON CONFLICT (keycloak_user_id, tenant_id, company_id) DO UPDATE SET is_active = true, updated_at = CURRENT_TIMESTAMP;" 2>&1
echo -e "${GREEN}✓ Relación usuario-tenant creada en la base de datos${NC}"

View File

@@ -18,11 +18,11 @@ echo "Creando extensiones y esquemas..."
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE EXTENSION IF NOT EXISTS "pg_trgm";
CREATE SCHEMA IF NOT EXISTS core;
CREATE SCHEMA IF NOT EXISTS a76;
CREATE SCHEMA IF NOT EXISTS a22;
CREATE SCHEMA IF NOT EXISTS a24;
CREATE SCHEMA IF NOT EXISTS a30;
DROP SCHEMA IF EXISTS a31;
EOSQL
echo "✓ Extensiones y esquemas creados correctamente"