Merge branch 'development' into feature/api-sittar-actualizacion
This commit is contained in:
@@ -9,7 +9,12 @@ from sqlalchemy.orm import Session
|
||||
|
||||
from api.v1.modules.a76.general_catalogs.company.models import Company
|
||||
from api.v1.modules.a76.invoices.models import InvoiceHeader
|
||||
from core.database import rls_company_var, rls_tenant_var
|
||||
from core.database import (
|
||||
RLS_COMPANY_KEY,
|
||||
RLS_TENANT_KEY,
|
||||
rls_company_var,
|
||||
rls_tenant_var,
|
||||
)
|
||||
|
||||
from .services.service import AuditService
|
||||
from .utils.serialization import serialize_for_json
|
||||
@@ -56,13 +61,17 @@ def _resolve_audit_company_tenant(session: Session, target) -> tuple:
|
||||
if company_id is not None:
|
||||
resolution_source = "company_self_id"
|
||||
if company_id is None:
|
||||
company_id = rls_company_var.get()
|
||||
company_id = session.info.get(RLS_COMPANY_KEY)
|
||||
if company_id is None:
|
||||
company_id = rls_company_var.get()
|
||||
if company_id is not None:
|
||||
resolution_source = "rls_context"
|
||||
|
||||
tenant_id = getattr(target, "tenant_id", None)
|
||||
if tenant_id is None:
|
||||
tenant_id = rls_tenant_var.get()
|
||||
tenant_id = session.info.get(RLS_TENANT_KEY)
|
||||
if tenant_id is None:
|
||||
tenant_id = rls_tenant_var.get()
|
||||
if tenant_id is not None and resolution_source == "target":
|
||||
resolution_source = "rls_context"
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ from typing import Dict, Any, Optional
|
||||
|
||||
from core.config import settings
|
||||
from core.database import get_core_db
|
||||
from core.exceptions import BaseAPIException
|
||||
from core.security import collect_user_role_names, get_current_user, validate_access_to_resource
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, Path
|
||||
from sqlalchemy import func, or_, and_
|
||||
@@ -69,6 +70,8 @@ def get_creation_data(
|
||||
return InvoiceCatalogService.get_creation_data(db, tenant_id, company_id)
|
||||
except HTTPException:
|
||||
raise
|
||||
except BaseAPIException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.exception("get_creation_data failed: %s", e)
|
||||
raise HTTPException(status_code=500, detail=f"Error al cargar datos de creación: {str(e)}")
|
||||
@@ -97,6 +100,8 @@ def get_edition_data(
|
||||
return data
|
||||
except HTTPException:
|
||||
raise
|
||||
except BaseAPIException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.exception("get_edition_data failed: %s", e)
|
||||
raise HTTPException(status_code=500, detail=f"Error al cargar datos de edición: {str(e)}")
|
||||
@@ -191,6 +196,8 @@ def create_invoice(
|
||||
return services.InvoiceService.create(db, data, tenant_id, company_id)
|
||||
except HTTPException:
|
||||
raise
|
||||
except BaseAPIException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.exception("create_invoice failed: %s", e)
|
||||
raise HTTPException(status_code=500, detail=f"Error al guardar factura: {str(e)}")
|
||||
@@ -337,6 +344,8 @@ def list_invoices(
|
||||
"page": page,
|
||||
"page_size": page_size
|
||||
}
|
||||
except BaseAPIException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.exception("list_invoices failed: %s", e)
|
||||
raise HTTPException(status_code=500, detail=f"Internal server error in invoices list: {str(e)}")
|
||||
|
||||
@@ -3,7 +3,7 @@ from typing import Any
|
||||
from celery import Task
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from core.database import reset_rls_context_tokens, rls_company_var, rls_tenant_var
|
||||
from core.database import rls_company_var, rls_tenant_var
|
||||
|
||||
from .service import TaskTrackerService
|
||||
|
||||
@@ -29,8 +29,10 @@ def track_and_dispatch(
|
||||
if company_id is not None:
|
||||
headers["rls_company_id"] = str(int(company_id))
|
||||
|
||||
token_t = rls_tenant_var.set(int(tenant_id))
|
||||
token_c = rls_company_var.set(int(company_id) if company_id is not None else None)
|
||||
prev_tenant = rls_tenant_var.get()
|
||||
prev_company = rls_company_var.get()
|
||||
rls_tenant_var.set(int(tenant_id))
|
||||
rls_company_var.set(int(company_id) if company_id is not None else None)
|
||||
try:
|
||||
celery_task = task.apply_async(
|
||||
args=args or [],
|
||||
@@ -39,7 +41,8 @@ def track_and_dispatch(
|
||||
headers=headers,
|
||||
)
|
||||
finally:
|
||||
reset_rls_context_tokens(token_t, token_c)
|
||||
rls_tenant_var.set(prev_tenant)
|
||||
rls_company_var.set(prev_company)
|
||||
|
||||
tracker = TaskTrackerService(db)
|
||||
tracker.register_dispatch(
|
||||
|
||||
@@ -154,12 +154,13 @@ def get_core_db(request: Request = None) -> Generator[Session, None, None]:
|
||||
escriben ``db: Session = Depends(get_core_db)`` siguen funcionando sin
|
||||
cambios porque ``Request`` se resuelve en la capa de dependencia.
|
||||
|
||||
Replica el mismo ``(tenant_id, company_id)`` en ContextVars para código que
|
||||
comparte la transacción sin la misma instancia de sesión (p. ej. listeners).
|
||||
No se escriben las ContextVars de RLS aquí: las dependencias síncronas con
|
||||
``yield`` se ejecutan vía ``contextmanager_in_threadpool`` (hilo worker) y
|
||||
mezclar ``ContextVar.set`` / ``reset`` entre ese hilo y el bucle asyncio
|
||||
provoca ``ValueError: ... was created in a different Context``. El aislamiento
|
||||
RLS se aplica con ``session.info`` (véase ``after_begin`` y audit listeners).
|
||||
"""
|
||||
tenant_id, company_id = _extract_rls_context(request)
|
||||
token_t = rls_tenant_var.set(tenant_id)
|
||||
token_c = rls_company_var.set(company_id)
|
||||
db = CoreSessionLocal()
|
||||
db.info[RLS_TENANT_KEY] = tenant_id
|
||||
db.info[RLS_COMPANY_KEY] = company_id
|
||||
@@ -167,14 +168,15 @@ def get_core_db(request: Request = None) -> Generator[Session, None, None]:
|
||||
yield db
|
||||
finally:
|
||||
db.close()
|
||||
reset_rls_context_tokens(token_t, token_c)
|
||||
|
||||
|
||||
async def get_async_core_db(request: Request = None) -> AsyncGenerator[AsyncSession, None]:
|
||||
"""Dependency async para obtener sesión con contexto RLS."""
|
||||
tenant_id, company_id = _extract_rls_context(request)
|
||||
token_t = rls_tenant_var.set(tenant_id)
|
||||
token_c = rls_company_var.set(company_id)
|
||||
prev_tenant = rls_tenant_var.get()
|
||||
prev_company = rls_company_var.get()
|
||||
rls_tenant_var.set(tenant_id)
|
||||
rls_company_var.set(company_id)
|
||||
try:
|
||||
async with AsyncCoreSessionLocal() as session:
|
||||
session.info[RLS_TENANT_KEY] = tenant_id
|
||||
@@ -184,7 +186,8 @@ async def get_async_core_db(request: Request = None) -> AsyncGenerator[AsyncSess
|
||||
finally:
|
||||
await session.close()
|
||||
finally:
|
||||
reset_rls_context_tokens(token_t, token_c)
|
||||
rls_tenant_var.set(prev_tenant)
|
||||
rls_company_var.set(prev_company)
|
||||
|
||||
|
||||
@contextmanager
|
||||
|
||||
@@ -178,12 +178,12 @@ class LicenseValidationMiddleware(BaseHTTPMiddleware):
|
||||
}
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Hub verify-license request failed: {str(e)}")
|
||||
logger.exception("Hub verify-license request failed: %s", e)
|
||||
return JSONResponse(
|
||||
status_code=500,
|
||||
content={
|
||||
"error": "VALIDATION_ERROR",
|
||||
"message": "Error interno al validar licencia con el Hub.",
|
||||
"message": "Error interno al contactar el servicio de licencias.",
|
||||
"status_code": 500,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -346,6 +346,8 @@ services:
|
||||
- NODE_ENV=${NODE_ENV:-production}
|
||||
- VITE_API_URL=${VITE_API_URL:-https://anexo76-dev.aduanasoft.com/api}
|
||||
- INTERNAL_API_URL=${INTERNAL_API_URL:-http://backend:8000/api/}
|
||||
- INTERNAL_HUB_URL=https://workspace.aduanasoft.com
|
||||
- HUB_URL=https://workspace.aduanasoft.com
|
||||
- VITE_KEYCLOAK_URL=${VITE_KEYCLOAK_URL:-https://anexo76-dev.aduanasoft.com/kcauth/}
|
||||
- VITE_KEYCLOAK_REALM=${VITE_KEYCLOAK_REALM:-master}
|
||||
- VITE_KEYCLOAK_CLIENT_ID=${VITE_KEYCLOAK_CLIENT_ID:-anexo76-frontend}
|
||||
|
||||
Reference in New Issue
Block a user