feat(server): add project RBAC and guarded workflows

This commit is contained in:
2026-07-30 16:45:09 +08:00
parent 3fbb17bb30
commit ae1a657554
29 changed files with 1431 additions and 412 deletions
+11 -63
View File
@@ -1,9 +1,13 @@
import json
from fastapi import APIRouter, Request, HTTPException, Query, Path, Body, Depends
from fastapi import APIRouter, Request, HTTPException, Query, Path, 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.auth.permissions import (
ENVIRONMENT_MANAGE,
require_permission,
)
from app.domain.schemas.metadata import ProjectMetaResponse
import app.services.project_info as project_info
from app.infra.db.postgresql.database import get_database_instance as get_pg_db
@@ -18,7 +22,6 @@ from app.services.tjnetwork import (
open_project,
close_project,
copy_project,
import_inp,
export_inp,
read_inp,
dump_inp,
@@ -89,7 +92,8 @@ async def have_project_endpoint(
@router.post("/createproject/", summary="创建新项目", description="创建一个新的供水管网项目。如果项目已存在,可能会覆盖或报错(取决于底层实现)。")
async def create_project_endpoint(
network: str = Query(..., description="管网名称(或数据库名称)")
network: str = Query(..., description="管网名称(或数据库名称)"),
_=Depends(require_permission(ENVIRONMENT_MANAGE)),
):
"""
创建新项目
@@ -101,7 +105,8 @@ async def create_project_endpoint(
@router.post("/deleteproject/", summary="删除项目", description="永久删除指定的供水管网项目。此操作不可恢复。")
async def delete_project_endpoint(
network: str = Query(..., description="管网名称(或数据库名称)")
network: str = Query(..., description="管网名称(或数据库名称)"),
_=Depends(require_permission(ENVIRONMENT_MANAGE)),
):
"""
删除项目
@@ -172,7 +177,8 @@ async def close_project_endpoint(
@router.post("/copyproject/", summary="复制项目", description="将现有项目复制为新项目。")
async def copy_project_endpoint(
source: str = Query(..., description="管网名称(或数据库名称)"),
target: str = Query(..., description="管网名称(或数据库名称)")
target: str = Query(..., description="管网名称(或数据库名称)"),
_=Depends(require_permission(ENVIRONMENT_MANAGE)),
):
"""
复制项目
@@ -183,24 +189,6 @@ async def copy_project_endpoint(
copy_project(source, target)
return True
@router.post("/importinp/", summary="导入 INP 文件内容", description="将 INP 格式的文本内容导入到指定项目中。")
async def import_inp_endpoint(
req: Request,
network: str = Query(..., description="管网名称(或数据库名称)")
):
"""
导入 INP 文件内容
- **network**: 管网名称(或数据库名称)
- **req**: 请求体,需包含 `{"inp": "..."}` 结构
"""
jo_root = await req.json()
inp_text = jo_root["inp"]
ps = {"inp": inp_text}
ret = import_inp(network, ChangeSet(ps))
print(ret)
return ret
@router.get("/exportinp/", response_model=None, summary="导出项目为 ChangeSet", description="导出项目的变更集 (ChangeSet),包含顶点、SCADA 元素、DMA、SA、VD 等信息。")
async def export_inp_endpoint(
network: str = Query(..., description="管网名称(或数据库名称)"),
@@ -331,26 +319,6 @@ def unlock_project_endpoint(
return False
# inp file operations
@router.post("/uploadinp/", status_code=status.HTTP_200_OK, summary="上传 INP 文件", description="上传 INP 文件到服务器数据目录。")
async def fastapi_upload_inp(
afile: bytes = Body(..., description="文件二进制内容"),
name: str = Query(..., description="保存的文件名")
):
"""
上传 INP 文件
- **afile**: 文件内容
- **name**: 文件名
"""
if not os.path.exists(inpDir):
os.makedirs(inpDir, exist_ok=True)
filePath = inpDir + str(name)
with open(filePath, "wb") as f:
f.write(afile)
return True
@router.get("/downloadinp/", status_code=status.HTTP_200_OK, summary="下载 INP 文件", description="从服务器数据目录下载指定的 INP 文件。")
async def fastapi_download_inp(
name: str = Query(..., description="文件名"),
@@ -502,26 +470,6 @@ def unlock_project_endpoint(
return False
# inp file operations
@router.post("/uploadinp/", status_code=status.HTTP_200_OK, summary="上传 INP 文件", description="上传 INP 文件到服务器数据目录。")
async def fastapi_upload_inp(
afile: bytes = Body(..., description="文件二进制内容"),
name: str = Query(..., description="保存的文件名")
):
"""
上传 INP 文件
- **afile**: 文件内容
- **name**: 文件名
"""
if not os.path.exists(inpDir):
os.makedirs(inpDir, exist_ok=True)
filePath = inpDir + str(name)
with open(filePath, "wb") as f:
f.write(afile)
return True
@router.get("/downloadinp/", status_code=status.HTTP_200_OK, summary="下载 INP 文件", description="从服务器数据目录下载指定的 INP 文件。")
async def fastapi_download_inp(
name: str = Query(..., description="文件名"),