Skip to content

Commit eda4829

Browse files
committed
feat: add tag, tagging and artifact index APIs
1 parent 64eeaec commit eda4829

8 files changed

Lines changed: 451 additions & 9 deletions

File tree

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
from typing import Annotated, List
2+
from fastapi import APIRouter, Body, HTTPException, Path
3+
from pydantic import BaseModel
4+
from whiskerrag_types.model import (
5+
PageQueryParams,
6+
PageResponse,
7+
Tenant,
8+
)
9+
from whiskerrag_types.model.artifact_index import ArtifactIndexCreate, ArtifactIndex
10+
from core.auth import Action, Resource, get_tenant_with_permissions
11+
from core.log import logger
12+
from core.plugin_manager import PluginManager
13+
from core.response import ResponseModel
14+
from whiskerrag_types.interface import DBPluginInterface
15+
16+
router = APIRouter(
17+
prefix="/api/v1/artifact",
18+
tags=["artifact"],
19+
responses={404: {"description": "Not found"}},
20+
)
21+
22+
23+
async def get_db_engine() -> DBPluginInterface:
24+
db_engine = PluginManager().dbPlugin
25+
if db_engine is None:
26+
raise HTTPException(status_code=500, detail="DB plugin is not initialized")
27+
await db_engine.ensure_initialized()
28+
return db_engine
29+
30+
31+
@router.post("/list", operation_id="get_artifact_list", response_model_by_alias=False)
32+
async def get_artifact_list(
33+
body: PageQueryParams[ArtifactIndex],
34+
tenant: Annotated[
35+
Tenant, get_tenant_with_permissions(Resource.ARTIFACT, [Action.READ])
36+
],
37+
) -> ResponseModel[PageResponse[ArtifactIndex]]:
38+
db_engine = await get_db_engine()
39+
artifact_list = await db_engine.get_artifact_list(body)
40+
return ResponseModel(data=artifact_list, success=True)
41+
42+
43+
@router.post(
44+
"/add_list", operation_id="add_artifact_list", response_model_by_alias=False
45+
)
46+
async def add_artifact_list(
47+
body: List[ArtifactIndexCreate],
48+
tenant: Annotated[
49+
Tenant, get_tenant_with_permissions(Resource.ARTIFACT, [Action.CREATE])
50+
],
51+
) -> ResponseModel[List[ArtifactIndex]]:
52+
db_engine = await get_db_engine()
53+
created_arts = await db_engine.add_artifact_list(body)
54+
return ResponseModel(data=created_arts, success=True)
55+
56+
57+
@router.get(
58+
"/{artifact_id}", operation_id="get_artifact_by_id", response_model_by_alias=False
59+
)
60+
async def get_artifact_by_id(
61+
tenant: Annotated[
62+
Tenant, get_tenant_with_permissions(Resource.ARTIFACT, [Action.READ])
63+
],
64+
artifact_id: str = Path(..., description="artifact id"),
65+
) -> ResponseModel[ArtifactIndex]:
66+
db_engine = await get_db_engine()
67+
artifact = await db_engine.get_artifact_by_id(artifact_id)
68+
if not artifact:
69+
logger.warning(
70+
"[get_artifact_by_id][artifact not exists], artifact_id=%s", artifact_id
71+
)
72+
raise HTTPException(status_code=404, detail="artifact not exists")
73+
return ResponseModel(data=artifact, success=True)
74+
75+
76+
@router.delete(
77+
"/{artifact_id}",
78+
operation_id="delete_artifact_by_id",
79+
response_model_by_alias=False,
80+
)
81+
async def delete_artifact_by_id(
82+
tenant: Annotated[
83+
Tenant, get_tenant_with_permissions(Resource.ARTIFACT, [Action.DELETE])
84+
],
85+
artifact_id: str = Path(..., description="artifact id"),
86+
) -> ResponseModel[None]:
87+
db_engine = await get_db_engine()
88+
deleted = await db_engine.delete_artifact_by_id(artifact_id)
89+
if not deleted:
90+
logger.warning(
91+
"[delete_artifact_by_id][artifact not exists], artifact_id=%s", artifact_id
92+
)
93+
raise HTTPException(status_code=404, detail="artifact not exists")
94+
return ResponseModel(
95+
success=True, message=f"Artifact {artifact_id} deleted successfully"
96+
)
97+
98+
99+
class ArtifactSpaceUpdate(BaseModel):
100+
artifact_id: str
101+
new_space_id: str
102+
103+
104+
@router.post(
105+
"/update_space",
106+
operation_id="update_artifact_space_id",
107+
response_model_by_alias=False,
108+
)
109+
async def update_artifact_space_id(
110+
tenant: Annotated[
111+
Tenant, get_tenant_with_permissions(Resource.ARTIFACT, [Action.UPDATE])
112+
],
113+
body: ArtifactSpaceUpdate = Body(..., description="要更新的 artifact 空间绑定"),
114+
) -> ResponseModel[ArtifactIndex]:
115+
db_engine = await get_db_engine()
116+
if not body.artifact_id:
117+
raise HTTPException(status_code=400, detail="artifact_id is required")
118+
119+
exist = await db_engine.get_artifact_by_id(body.artifact_id)
120+
if not exist:
121+
logger.error(
122+
"[update_artifact_space_id][artifact不存在], artifact_id=%s",
123+
body.artifact_id,
124+
)
125+
raise HTTPException(
126+
status_code=404, detail=f"artifact不存在,artifact_id={body.artifact_id}"
127+
)
128+
129+
updated_artifact = await db_engine.update_artifact_space_id(
130+
body.artifact_id, body.new_space_id
131+
)
132+
return ResponseModel(
133+
success=True, data=updated_artifact, message="Update artifact succeed"
134+
)

server/api/space/router.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ async def add_space(
4747

4848

4949
@router.delete(
50-
"/{space_id}",
50+
"/{space_id:path}",
5151
operation_id="delete_space",
5252
response_model_by_alias=False,
5353
)
@@ -66,7 +66,9 @@ async def delete_space(
6666
return ResponseModel(success=True, message=f"Space {space_id} deleted successfully")
6767

6868

69-
@router.put("/{space_id}", operation_id="update_space", response_model_by_alias=False)
69+
@router.put(
70+
"/{space_id:path}", operation_id="update_space", response_model_by_alias=False
71+
)
7072
async def update_space(
7173
space_id: str = Path(..., description="知识库唯一标识符"),
7274
body: SpaceCreate = Body(..., description="更新后的知识库信息"),

server/api/tag/router.py

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
from typing import Annotated, List, Optional
2+
3+
from fastapi import APIRouter, Body, HTTPException, Path
4+
from pydantic import BaseModel
5+
from whiskerrag_types.model import (
6+
PageQueryParams,
7+
PageResponse,
8+
Tag,
9+
TagCreate,
10+
Tenant,
11+
)
12+
13+
from core.auth import Action, Resource, get_tenant_with_permissions
14+
from core.log import logger
15+
from core.plugin_manager import PluginManager
16+
from core.response import ResponseModel
17+
from whiskerrag_types.interface import DBPluginInterface
18+
19+
router = APIRouter(
20+
prefix="/api/v1/tag", tags=["tag"], responses={404: {"description": "Not found"}}
21+
)
22+
23+
24+
async def get_db_engine() -> DBPluginInterface:
25+
db_engine = PluginManager().dbPlugin
26+
if db_engine is None:
27+
raise HTTPException(status_code=500, detail="DB plugin is not initialized")
28+
await db_engine.ensure_initialized()
29+
return db_engine
30+
31+
32+
@router.post(
33+
"/list",
34+
operation_id="get_tag_list",
35+
response_model_by_alias=False,
36+
)
37+
async def get_tag_list(
38+
body: PageQueryParams[Tag],
39+
tenant: Annotated[Tenant, get_tenant_with_permissions(Resource.TAG, [Action.READ])],
40+
) -> ResponseModel[PageResponse[Tag]]:
41+
"""
42+
分页获取标签列表
43+
"""
44+
db_engine = await get_db_engine()
45+
tag_list: PageResponse[Tag] = await db_engine.get_tag_list(tenant.tenant_id, body)
46+
return ResponseModel(data=tag_list, success=True)
47+
48+
49+
@router.post("/add_list", operation_id="add_tag_list", response_model_by_alias=False)
50+
async def add_tag_list(
51+
body: List[TagCreate],
52+
tenant: Annotated[
53+
Tenant, get_tenant_with_permissions(Resource.TAG, [Action.CREATE])
54+
],
55+
) -> ResponseModel[List[Tag]]:
56+
"""
57+
批量新增标签
58+
注意:根据接口定义,DB 插件方法签名为 add_tag_list(tag_list: List[TagCreate]),
59+
因此此处直接传入 body。若 TagCreate 需要携带 tenant_id,应由模型或插件内部处理。
60+
"""
61+
db_engine = await get_db_engine()
62+
created_tags = await db_engine.add_tag_list(tenant.tenant_id, body)
63+
return ResponseModel(data=created_tags, success=True)
64+
65+
66+
@router.get("/{tag_id}", operation_id="get_tag_by_id", response_model_by_alias=False)
67+
async def get_tag_by_id(
68+
tenant: Annotated[Tenant, get_tenant_with_permissions(Resource.TAG, [Action.READ])],
69+
tag_id: str = Path(..., description="tag id"),
70+
) -> ResponseModel[Tag]:
71+
"""
72+
获取标签详情
73+
"""
74+
db_engine = await get_db_engine()
75+
tag = await db_engine.get_tag_by_id(tenant.tenant_id, tag_id)
76+
if not tag:
77+
logger.warning("[get_tag_by_id][tag not exists], tag_id=%s", tag_id)
78+
raise HTTPException(status_code=404, detail="tag not exists")
79+
return ResponseModel(data=tag, success=True)
80+
81+
82+
@router.delete(
83+
"/{tag_id}",
84+
operation_id="delete_tag_by_id",
85+
response_model_by_alias=False,
86+
)
87+
async def delete_tag_by_id(
88+
tenant: Annotated[
89+
Tenant, get_tenant_with_permissions(Resource.TAG, [Action.DELETE])
90+
],
91+
tag_id: str = Path(..., description="tag id"),
92+
) -> ResponseModel[None]:
93+
db_engine = await get_db_engine()
94+
deleted = await db_engine.delete_tag_by_id(tenant.tenant_id, tag_id)
95+
if not deleted:
96+
logger.warning("[delete_tag_by_id][tag not exists], tag_id=%s", tag_id)
97+
raise HTTPException(status_code=404, detail="tag not exists")
98+
return ResponseModel(success=True, message=f"Tag {tag_id} deleted successfully")
99+
100+
101+
class TagUpdate(BaseModel):
102+
tag_id: str
103+
name: Optional[str] = None
104+
description: Optional[str] = None
105+
106+
107+
@router.post("/update", operation_id="update_tag", response_model_by_alias=False)
108+
async def update_tag(
109+
tenant: Annotated[
110+
Tenant, get_tenant_with_permissions(Resource.TAG, [Action.UPDATE])
111+
],
112+
body: TagUpdate = Body(..., description="要更新的标签字段"),
113+
) -> ResponseModel[Tag]:
114+
db_engine = await get_db_engine()
115+
116+
if not body.tag_id:
117+
raise HTTPException(status_code=400, detail="tag_id is required")
118+
119+
exist = await db_engine.get_tag_by_id(tenant.tenant_id, body.tag_id)
120+
if not exist:
121+
logger.error("[update_tag][标签不存在], tag_id=%s", body.tag_id)
122+
raise HTTPException(status_code=404, detail=f"标签不存在, tag_id={body.tag_id}")
123+
124+
updated_tag = await db_engine.update_tag_name_description(
125+
tenant.tenant_id, body.tag_id, body.name, body.description
126+
)
127+
return ResponseModel(success=True, data=updated_tag, message="Update tag succeed")

server/api/tagging/router.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
from typing import Annotated, List
2+
3+
from fastapi import APIRouter, Body, HTTPException, Path
4+
from whiskerrag_types.model import (
5+
PageQueryParams,
6+
PageResponse,
7+
Tagging,
8+
TaggingCreate,
9+
Tenant,
10+
)
11+
from core.auth import Action, Resource, get_tenant_with_permissions
12+
from core.log import logger
13+
from core.plugin_manager import PluginManager
14+
from core.response import ResponseModel
15+
from whiskerrag_types.interface import DBPluginInterface
16+
17+
router = APIRouter(
18+
prefix="/api/v1/tagging",
19+
tags=["tagging"],
20+
responses={404: {"description": "Not found"}},
21+
)
22+
23+
24+
async def get_db_engine() -> DBPluginInterface:
25+
db_engine = PluginManager().dbPlugin
26+
if db_engine is None:
27+
raise HTTPException(status_code=500, detail="DB plugin is not initialized")
28+
await db_engine.ensure_initialized()
29+
return db_engine
30+
31+
32+
@router.post("/list", operation_id="get_tagging_list", response_model_by_alias=False)
33+
async def get_tagging_list(
34+
body: PageQueryParams[Tagging],
35+
tenant: Annotated[
36+
Tenant, get_tenant_with_permissions(Resource.TAGGING, [Action.READ])
37+
],
38+
) -> ResponseModel[PageResponse[Tagging]]:
39+
db_engine = await get_db_engine()
40+
tagging_list = await db_engine.get_tagging_list(tenant.tenant_id, body)
41+
return ResponseModel(data=tagging_list, success=True)
42+
43+
44+
@router.post(
45+
"/add_list", operation_id="add_tagging_list", response_model_by_alias=False
46+
)
47+
async def add_tagging_list(
48+
body: List[TaggingCreate],
49+
tenant: Annotated[
50+
Tenant, get_tenant_with_permissions(Resource.TAGGING, [Action.CREATE])
51+
],
52+
) -> ResponseModel[List[Tagging]]:
53+
db_engine = await get_db_engine()
54+
created_tags = await db_engine.add_tagging_list(tenant.tenant_id, body)
55+
return ResponseModel(data=created_tags, success=True)
56+
57+
58+
@router.delete(
59+
"/{tagging_id}",
60+
operation_id="delete_tagging_by_id",
61+
response_model_by_alias=False,
62+
)
63+
async def delete_tagging_by_id(
64+
tenant: Annotated[
65+
Tenant, get_tenant_with_permissions(Resource.TAGGING, [Action.DELETE])
66+
],
67+
tagging_id: str = Path(..., description="tagging id"),
68+
) -> ResponseModel[None]:
69+
db_engine = await get_db_engine()
70+
deleted = await db_engine.delete_tagging_by_id(tenant.tenant_id, tagging_id)
71+
if not deleted:
72+
logger.warning(
73+
"[delete_tagging_by_id][tagging not exists], tagging_id=%s", tagging_id
74+
)
75+
raise HTTPException(status_code=404, detail="tagging not exists")
76+
return ResponseModel(
77+
success=True, message=f"Tagging {tagging_id} deleted successfully"
78+
)

0 commit comments

Comments
 (0)