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:
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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}"
|
||||
|
||||
|
||||
@@ -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;
|
||||
CREATE SCHEMA IF NOT EXISTS a30;
|
||||
EOSQL
|
||||
|
||||
echo "✓ Extensiones y esquemas creados correctamente"
|
||||
|
||||
Reference in New Issue
Block a user