fix(metadata): sync project metadata endpoints

This commit is contained in:
2026-06-11 11:15:59 +08:00
parent 78af7ecfb3
commit f61be3685f
8 changed files with 357 additions and 12 deletions
+21 -3
View File
@@ -1,5 +1,6 @@
import logging
from fastapi import APIRouter, Depends, HTTPException, status, Query, Path
import psycopg
from psycopg import AsyncConnection
from sqlalchemy import text
from sqlalchemy.exc import SQLAlchemyError
@@ -58,6 +59,7 @@ async def get_project_metadata(
code=project.code,
description=project.description,
gs_workspace=project.gs_workspace,
map_extent=project.map_extent,
status=project.status,
project_role=ctx.project_role,
geoserver=geoserver_payload,
@@ -110,7 +112,23 @@ async def project_db_health(
检查PostgreSQL和TimescaleDB数据库的连接状态
"""
await pg_session.execute(text("SELECT 1"))
async with ts_conn.cursor() as cur:
await cur.execute("SELECT 1")
try:
await pg_session.execute(text("SELECT 1"))
except SQLAlchemyError as exc:
logger.error("Project PostgreSQL health check failed", exc_info=True)
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail=f"Project PostgreSQL health check failed: {exc}",
) from exc
try:
async with ts_conn.cursor() as cur:
await cur.execute("SELECT 1")
except psycopg.Error as exc:
logger.error("Project TimescaleDB health check failed", exc_info=True)
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail=f"Project TimescaleDB health check failed: {exc}",
) from exc
return {"postgres": "ok", "timescale": "ok"}
+40 -1
View File
@@ -1,7 +1,10 @@
import json
from fastapi import APIRouter, Request, HTTPException, Query, Path, Body
from fastapi import APIRouter, Request, HTTPException, Query, Path, Body, Depends
from fastapi.responses import PlainTextResponse
from typing import Any, Dict, List
from app.infra.db.metadb.repositories.metadata_repository import MetadataRepository
from app.auth.project_dependencies import get_metadata_repository
from app.domain.schemas.metadata import ProjectMetaResponse, GeoServerConfigResponse
import app.services.project_info as project_info
from app.infra.db.postgresql.database import get_database_instance as get_pg_db
from app.infra.db.timescaledb.database import get_database_instance as get_ts_db
@@ -39,6 +42,42 @@ inpDir = "data/" # Assuming data directory exists or is defined somewhere.
router = APIRouter()
lockedPrjs: Dict[str, str] = {}
@router.get("/project_info/", summary="获取项目信息", description="从数据库获取项目的详细信息,包括地图范围等。", response_model=ProjectMetaResponse)
async def get_project_info_endpoint(
network: str = Query(..., description="管网名称(或项目代码)"),
metadata_repo: MetadataRepository = Depends(get_metadata_repository),
):
"""
获取项目信息
- **network**: 管网名称(或项目代码)
"""
project_detail = await metadata_repo.get_project_detail_by_code(network)
if not project_detail:
raise HTTPException(status_code=404, detail=f"Project {network} not found")
geoserver_payload = None
if project_detail.geoserver:
geoserver_payload = GeoServerConfigResponse(
gs_base_url=project_detail.geoserver.gs_base_url,
gs_admin_user=project_detail.geoserver.gs_admin_user,
gs_datastore_name=project_detail.geoserver.gs_datastore_name,
default_extent=project_detail.geoserver.default_extent,
srid=project_detail.geoserver.srid,
)
return ProjectMetaResponse(
project_id=project_detail.project_id,
name=project_detail.name,
code=project_detail.code,
description=project_detail.description,
gs_workspace=project_detail.gs_workspace,
map_extent=project_detail.map_extent,
status=project_detail.status,
project_role="viewer",
geoserver=geoserver_payload,
)
@router.get("/listprojects/", summary="获取项目列表", description="获取服务器上所有可用的供水管网项目名称列表。")
async def list_projects_endpoint() -> list[str]:
"""
+7 -6
View File
@@ -5,10 +5,10 @@ from pydantic import BaseModel
class GeoServerConfigResponse(BaseModel):
gs_base_url: Optional[str]
gs_admin_user: Optional[str]
gs_base_url: Optional[str] = None
gs_admin_user: Optional[str] = None
gs_datastore_name: str
default_extent: Optional[dict]
default_extent: Optional[dict] = None
srid: int
@@ -16,18 +16,19 @@ class ProjectMetaResponse(BaseModel):
project_id: UUID
name: str
code: str
description: Optional[str]
description: Optional[str] = None
gs_workspace: str
map_extent: Optional[dict] = None
status: str
project_role: str
geoserver: Optional[GeoServerConfigResponse]
geoserver: Optional[GeoServerConfigResponse] = None
class ProjectSummaryResponse(BaseModel):
project_id: UUID
name: str
code: str
description: Optional[str]
description: Optional[str] = None
gs_workspace: str
status: str
project_role: str
+1
View File
@@ -42,6 +42,7 @@ class Project(Base):
code: Mapped[str] = mapped_column(String(50), unique=True)
description: Mapped[str | None] = mapped_column(Text, nullable=True)
gs_workspace: Mapped[str] = mapped_column(String(100), unique=True)
map_extent: Mapped[dict | None] = mapped_column(JSONB, nullable=True)
status: Mapped[str] = mapped_column(String(20), default="active")
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=datetime.utcnow
@@ -61,10 +61,23 @@ class ProjectSummary:
code: str
description: Optional[str]
gs_workspace: str
map_extent: Optional[dict]
status: str
project_role: str
@dataclass(frozen=True)
class ProjectDetail:
project_id: UUID
name: str
code: str
description: Optional[str]
gs_workspace: str
map_extent: Optional[dict]
status: str
geoserver: Optional[ProjectGeoServerInfo]
class MetadataRepository:
"""元数据访问层(system_hub"""
@@ -89,6 +102,30 @@ class MetadataRepository:
)
return result.scalar_one_or_none()
async def get_project_by_code(self, code: str) -> Optional[models.Project]:
result = await self.session.execute(
select(models.Project).where(models.Project.code == code)
)
return result.scalar_one_or_none()
async def get_project_detail_by_code(self, code: str) -> Optional[ProjectDetail]:
project = await self.get_project_by_code(code)
if not project:
return None
geoserver = await self.get_geoserver_config(project.id)
return ProjectDetail(
project_id=project.id,
name=project.name,
code=project.code,
description=project.description,
gs_workspace=project.gs_workspace,
map_extent=project.map_extent,
status=project.status,
geoserver=geoserver,
)
async def get_membership_role(
self, project_id: UUID, user_id: UUID
) -> Optional[str]:
@@ -179,6 +216,7 @@ class MetadataRepository:
code=project.code,
description=project.description,
gs_workspace=project.gs_workspace,
map_extent=project.map_extent,
status=project.status,
project_role=role,
)
@@ -196,6 +234,7 @@ class MetadataRepository:
code=project.code,
description=project.description,
gs_workspace=project.gs_workspace,
map_extent=project.map_extent,
status=project.status,
project_role="owner",
)