添加日志记录和异常处理以增强错误管理
This commit is contained in:
@@ -1,6 +1,8 @@
|
|||||||
|
import logging
|
||||||
from fastapi import APIRouter, Depends, HTTPException, status
|
from fastapi import APIRouter, Depends, HTTPException, status
|
||||||
from psycopg import AsyncConnection
|
from psycopg import AsyncConnection
|
||||||
from sqlalchemy import text
|
from sqlalchemy import text
|
||||||
|
from sqlalchemy.exc import SQLAlchemyError
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
from app.auth.project_dependencies import (
|
from app.auth.project_dependencies import (
|
||||||
@@ -20,6 +22,7 @@ from app.domain.schemas.metadata import (
|
|||||||
from app.infra.repositories.metadata_repository import MetadataRepository
|
from app.infra.repositories.metadata_repository import MetadataRepository
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/meta/project", response_model=ProjectMetaResponse)
|
@router.get("/meta/project", response_model=ProjectMetaResponse)
|
||||||
@@ -61,7 +64,18 @@ async def list_user_projects(
|
|||||||
current_user=Depends(get_current_metadata_user),
|
current_user=Depends(get_current_metadata_user),
|
||||||
metadata_repo: MetadataRepository = Depends(get_metadata_repository),
|
metadata_repo: MetadataRepository = Depends(get_metadata_repository),
|
||||||
):
|
):
|
||||||
projects = await metadata_repo.list_projects_for_user(current_user.id)
|
try:
|
||||||
|
projects = await metadata_repo.list_projects_for_user(current_user.id)
|
||||||
|
except SQLAlchemyError as exc:
|
||||||
|
logger.error(
|
||||||
|
"Metadata DB error while listing projects for user %s",
|
||||||
|
current_user.id,
|
||||||
|
exc_info=True,
|
||||||
|
)
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||||
|
detail=f"Metadata database error: {exc}",
|
||||||
|
) from exc
|
||||||
return [
|
return [
|
||||||
ProjectSummaryResponse(
|
ProjectSummaryResponse(
|
||||||
project_id=project.project_id,
|
project_id=project.project_id,
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|
||||||
|
import logging
|
||||||
from fastapi import Depends, HTTPException, status
|
from fastapi import Depends, HTTPException, status
|
||||||
|
from sqlalchemy.exc import SQLAlchemyError
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
from app.auth.keycloak_dependencies import get_current_keycloak_sub
|
from app.auth.keycloak_dependencies import get_current_keycloak_sub
|
||||||
@@ -9,6 +11,8 @@ from app.core.config import settings
|
|||||||
from app.infra.db.metadata.database import get_metadata_session
|
from app.infra.db.metadata.database import get_metadata_session
|
||||||
from app.infra.repositories.metadata_repository import MetadataRepository
|
from app.infra.repositories.metadata_repository import MetadataRepository
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
async def get_metadata_repository(
|
async def get_metadata_repository(
|
||||||
session: AsyncSession = Depends(get_metadata_session),
|
session: AsyncSession = Depends(get_metadata_session),
|
||||||
@@ -20,7 +24,17 @@ async def get_current_metadata_user(
|
|||||||
keycloak_sub: UUID = Depends(get_current_keycloak_sub),
|
keycloak_sub: UUID = Depends(get_current_keycloak_sub),
|
||||||
metadata_repo: MetadataRepository = Depends(get_metadata_repository),
|
metadata_repo: MetadataRepository = Depends(get_metadata_repository),
|
||||||
):
|
):
|
||||||
user = await metadata_repo.get_user_by_keycloak_id(keycloak_sub)
|
try:
|
||||||
|
user = await metadata_repo.get_user_by_keycloak_id(keycloak_sub)
|
||||||
|
except SQLAlchemyError as exc:
|
||||||
|
logger.error(
|
||||||
|
"Metadata DB error while resolving current user",
|
||||||
|
exc_info=True,
|
||||||
|
)
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||||
|
detail=f"Metadata database error: {exc}",
|
||||||
|
) from exc
|
||||||
if not user or not user.is_active:
|
if not user or not user.is_active:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_403_FORBIDDEN, detail="Inactive user"
|
status_code=status.HTTP_403_FORBIDDEN, detail="Inactive user"
|
||||||
|
|||||||
@@ -2,8 +2,10 @@ from dataclasses import dataclass
|
|||||||
from typing import AsyncGenerator
|
from typing import AsyncGenerator
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|
||||||
|
import logging
|
||||||
from fastapi import Depends, Header, HTTPException, status
|
from fastapi import Depends, Header, HTTPException, status
|
||||||
from psycopg import AsyncConnection
|
from psycopg import AsyncConnection
|
||||||
|
from sqlalchemy.exc import SQLAlchemyError
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
from app.auth.keycloak_dependencies import get_current_keycloak_sub
|
from app.auth.keycloak_dependencies import get_current_keycloak_sub
|
||||||
@@ -12,11 +14,13 @@ from app.infra.db.dynamic_manager import project_connection_manager
|
|||||||
from app.infra.db.metadata.database import get_metadata_session
|
from app.infra.db.metadata.database import get_metadata_session
|
||||||
from app.infra.repositories.metadata_repository import MetadataRepository
|
from app.infra.repositories.metadata_repository import MetadataRepository
|
||||||
|
|
||||||
DB_ROLE_BIZ_PG = "biz_pg"
|
DB_ROLE_BIZ_DATA = "biz_data"
|
||||||
DB_ROLE_IOT_TS = "iot_ts"
|
DB_ROLE_IOT_DATA = "iot_data"
|
||||||
DB_TYPE_POSTGRES = "postgresql"
|
DB_TYPE_POSTGRES = "postgresql"
|
||||||
DB_TYPE_TIMESCALE = "timescaledb"
|
DB_TYPE_TIMESCALE = "timescaledb"
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class ProjectContext:
|
class ProjectContext:
|
||||||
@@ -43,31 +47,43 @@ async def get_project_context(
|
|||||||
status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid project id"
|
status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid project id"
|
||||||
) from exc
|
) from exc
|
||||||
|
|
||||||
project = await metadata_repo.get_project_by_id(project_uuid)
|
try:
|
||||||
if not project:
|
project = await metadata_repo.get_project_by_id(project_uuid)
|
||||||
raise HTTPException(
|
if not project:
|
||||||
status_code=status.HTTP_404_NOT_FOUND, detail="Project not found"
|
raise HTTPException(
|
||||||
)
|
status_code=status.HTTP_404_NOT_FOUND, detail="Project not found"
|
||||||
if project.status != "active":
|
)
|
||||||
raise HTTPException(
|
if project.status != "active":
|
||||||
status_code=status.HTTP_403_FORBIDDEN, detail="Project is not active"
|
raise HTTPException(
|
||||||
)
|
status_code=status.HTTP_403_FORBIDDEN, detail="Project is not active"
|
||||||
|
)
|
||||||
|
|
||||||
user = await metadata_repo.get_user_by_keycloak_id(keycloak_sub)
|
user = await metadata_repo.get_user_by_keycloak_id(keycloak_sub)
|
||||||
if not user:
|
if not user:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_403_FORBIDDEN, detail="User not registered"
|
status_code=status.HTTP_403_FORBIDDEN, detail="User not registered"
|
||||||
)
|
)
|
||||||
if not user.is_active:
|
if not user.is_active:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_403_FORBIDDEN, detail="Inactive user"
|
status_code=status.HTTP_403_FORBIDDEN, detail="Inactive user"
|
||||||
)
|
)
|
||||||
|
|
||||||
membership_role = await metadata_repo.get_membership_role(project_uuid, user.id)
|
membership_role = await metadata_repo.get_membership_role(
|
||||||
if not membership_role:
|
project_uuid, user.id
|
||||||
raise HTTPException(
|
|
||||||
status_code=status.HTTP_403_FORBIDDEN, detail="No access to project"
|
|
||||||
)
|
)
|
||||||
|
if not membership_role:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_403_FORBIDDEN, detail="No access to project"
|
||||||
|
)
|
||||||
|
except SQLAlchemyError as exc:
|
||||||
|
logger.error(
|
||||||
|
"Metadata DB error while resolving project context",
|
||||||
|
exc_info=True,
|
||||||
|
)
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||||
|
detail=f"Metadata database error: {exc}",
|
||||||
|
) from exc
|
||||||
|
|
||||||
return ProjectContext(
|
return ProjectContext(
|
||||||
project_id=project.id,
|
project_id=project.id,
|
||||||
@@ -80,9 +96,19 @@ async def get_project_pg_session(
|
|||||||
ctx: ProjectContext = Depends(get_project_context),
|
ctx: ProjectContext = Depends(get_project_context),
|
||||||
metadata_repo: MetadataRepository = Depends(get_metadata_repository),
|
metadata_repo: MetadataRepository = Depends(get_metadata_repository),
|
||||||
) -> AsyncGenerator[AsyncSession, None]:
|
) -> AsyncGenerator[AsyncSession, None]:
|
||||||
routing = await metadata_repo.get_project_db_routing(
|
try:
|
||||||
ctx.project_id, DB_ROLE_BIZ_PG
|
routing = await metadata_repo.get_project_db_routing(
|
||||||
)
|
ctx.project_id, DB_ROLE_BIZ_DATA
|
||||||
|
)
|
||||||
|
except ValueError as exc:
|
||||||
|
logger.error(
|
||||||
|
"Missing ENCRYPTION_KEY while resolving project PostgreSQL routing",
|
||||||
|
exc_info=True,
|
||||||
|
)
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||||
|
detail="ENCRYPTION_KEY is not configured for project PostgreSQL access",
|
||||||
|
) from exc
|
||||||
if not routing:
|
if not routing:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||||
@@ -98,7 +124,7 @@ async def get_project_pg_session(
|
|||||||
pool_max_size = routing.pool_max_size or settings.PROJECT_PG_POOL_SIZE
|
pool_max_size = routing.pool_max_size or settings.PROJECT_PG_POOL_SIZE
|
||||||
sessionmaker = await project_connection_manager.get_pg_sessionmaker(
|
sessionmaker = await project_connection_manager.get_pg_sessionmaker(
|
||||||
ctx.project_id,
|
ctx.project_id,
|
||||||
DB_ROLE_BIZ_PG,
|
DB_ROLE_BIZ_DATA,
|
||||||
routing.dsn,
|
routing.dsn,
|
||||||
pool_min_size,
|
pool_min_size,
|
||||||
pool_max_size,
|
pool_max_size,
|
||||||
@@ -111,9 +137,19 @@ async def get_project_pg_connection(
|
|||||||
ctx: ProjectContext = Depends(get_project_context),
|
ctx: ProjectContext = Depends(get_project_context),
|
||||||
metadata_repo: MetadataRepository = Depends(get_metadata_repository),
|
metadata_repo: MetadataRepository = Depends(get_metadata_repository),
|
||||||
) -> AsyncGenerator[AsyncConnection, None]:
|
) -> AsyncGenerator[AsyncConnection, None]:
|
||||||
routing = await metadata_repo.get_project_db_routing(
|
try:
|
||||||
ctx.project_id, DB_ROLE_BIZ_PG
|
routing = await metadata_repo.get_project_db_routing(
|
||||||
)
|
ctx.project_id, DB_ROLE_BIZ_DATA
|
||||||
|
)
|
||||||
|
except ValueError as exc:
|
||||||
|
logger.error(
|
||||||
|
"Missing ENCRYPTION_KEY while resolving project PostgreSQL routing",
|
||||||
|
exc_info=True,
|
||||||
|
)
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||||
|
detail="ENCRYPTION_KEY is not configured for project PostgreSQL access",
|
||||||
|
) from exc
|
||||||
if not routing:
|
if not routing:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||||
@@ -129,7 +165,7 @@ async def get_project_pg_connection(
|
|||||||
pool_max_size = routing.pool_max_size or settings.PROJECT_PG_POOL_SIZE
|
pool_max_size = routing.pool_max_size or settings.PROJECT_PG_POOL_SIZE
|
||||||
pool = await project_connection_manager.get_pg_pool(
|
pool = await project_connection_manager.get_pg_pool(
|
||||||
ctx.project_id,
|
ctx.project_id,
|
||||||
DB_ROLE_BIZ_PG,
|
DB_ROLE_BIZ_DATA,
|
||||||
routing.dsn,
|
routing.dsn,
|
||||||
pool_min_size,
|
pool_min_size,
|
||||||
pool_max_size,
|
pool_max_size,
|
||||||
@@ -142,9 +178,19 @@ async def get_project_timescale_connection(
|
|||||||
ctx: ProjectContext = Depends(get_project_context),
|
ctx: ProjectContext = Depends(get_project_context),
|
||||||
metadata_repo: MetadataRepository = Depends(get_metadata_repository),
|
metadata_repo: MetadataRepository = Depends(get_metadata_repository),
|
||||||
) -> AsyncGenerator[AsyncConnection, None]:
|
) -> AsyncGenerator[AsyncConnection, None]:
|
||||||
routing = await metadata_repo.get_project_db_routing(
|
try:
|
||||||
ctx.project_id, DB_ROLE_IOT_TS
|
routing = await metadata_repo.get_project_db_routing(
|
||||||
)
|
ctx.project_id, DB_ROLE_IOT_DATA
|
||||||
|
)
|
||||||
|
except ValueError as exc:
|
||||||
|
logger.error(
|
||||||
|
"Missing ENCRYPTION_KEY while resolving project TimescaleDB routing",
|
||||||
|
exc_info=True,
|
||||||
|
)
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||||
|
detail="ENCRYPTION_KEY is not configured for project TimescaleDB access",
|
||||||
|
) from exc
|
||||||
if not routing:
|
if not routing:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||||
@@ -160,7 +206,7 @@ async def get_project_timescale_connection(
|
|||||||
pool_max_size = routing.pool_max_size or settings.PROJECT_TS_POOL_MAX_SIZE
|
pool_max_size = routing.pool_max_size or settings.PROJECT_TS_POOL_MAX_SIZE
|
||||||
pool = await project_connection_manager.get_timescale_pool(
|
pool = await project_connection_manager.get_timescale_pool(
|
||||||
ctx.project_id,
|
ctx.project_id,
|
||||||
DB_ROLE_IOT_TS,
|
DB_ROLE_IOT_DATA,
|
||||||
routing.dsn,
|
routing.dsn,
|
||||||
pool_min_size,
|
pool_min_size,
|
||||||
pool_max_size,
|
pool_max_size,
|
||||||
|
|||||||
Reference in New Issue
Block a user