Skip to content

Commit 2d91534

Browse files
authored
Sync runtime and sql scripts to prepare for plugin UI (#3)
* feat(api-key): sync plugin api and sql with management ui * fix(api-key): align plugin sql layout with spec
1 parent 35c0d03 commit 2d91534

8 files changed

Lines changed: 724 additions & 16 deletions

File tree

api/v1/sys/api_key.py

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from fastapi import APIRouter, Depends, Path, Query, Request
44

5+
from backend.common.pagination import DependsPagination, PageData
56
from backend.common.response.response_code import CustomResponse
67
from backend.common.response.response_schema import ResponseModel, ResponseSchemaModel, response_base
78
from backend.common.security.jwt import DependsJwtAuth
@@ -21,19 +22,26 @@
2122

2223
@router.get('/{pk}', summary='获取 API Key 详情', dependencies=[DependsJwtAuth])
2324
async def get_api_key(
24-
db: CurrentSession, request: Request, pk: Annotated[int, Path(description='通知公告 ID')]
25+
db: CurrentSession, request: Request, pk: Annotated[int, Path(description='API Key ID')]
2526
) -> ResponseSchemaModel[GetApiKeyDetail]:
2627
data = await api_key_service.get(db=db, user_id=request.user.id, is_superuser=request.user.is_superuser, pk=pk)
2728
return response_base.success(data=data)
2829

2930

30-
@router.get('', summary='分页获取所有 API Key', dependencies=[DependsJwtAuth])
31+
@router.get(
32+
'',
33+
summary='分页获取所有 API Key',
34+
dependencies=[
35+
DependsJwtAuth,
36+
DependsPagination,
37+
],
38+
)
3139
async def get_api_keys_paginated(
3240
db: CurrentSession,
3341
request: Request,
3442
name: Annotated[str | None, Query(description='API Key 名称')] = None,
3543
status: Annotated[int | None, Query(description='状态')] = None,
36-
) -> ResponseSchemaModel[list[GetApiKeyDetail]]:
44+
) -> ResponseSchemaModel[PageData[GetApiKeyDetail]]:
3745
data = await api_key_service.get_list(
3846
db=db, user_id=request.user.id, is_superuser=request.user.is_superuser, name=name, status=status
3947
)
@@ -81,6 +89,28 @@ async def update_api_key(
8189
return response_base.success()
8290

8391

92+
@router.put(
93+
'/{pk}/status',
94+
summary='切换 API Key 状态',
95+
dependencies=[
96+
Depends(RequestPermission('sys:apikey:edit')),
97+
DependsRBAC,
98+
],
99+
)
100+
async def update_api_key_status(
101+
db: CurrentSessionTransaction,
102+
request: Request,
103+
pk: Annotated[int, Path(description='API Key ID')],
104+
) -> ResponseModel:
105+
await api_key_service.update_status(
106+
db=db,
107+
user_id=request.user.id,
108+
is_superuser=request.user.is_superuser,
109+
pk=pk,
110+
)
111+
return response_base.success()
112+
113+
84114
@router.delete(
85115
'',
86116
summary='批量删除 API Key',
@@ -94,5 +124,10 @@ async def delete_api_keys(
94124
request: Request,
95125
obj: DeleteApiKeyParam,
96126
) -> ResponseModel:
97-
await api_key_service.delete(db=db, user_id=request.user.id, pks=obj.pks)
127+
await api_key_service.delete(
128+
db=db,
129+
user_id=request.user.id,
130+
is_superuser=request.user.is_superuser,
131+
pks=obj.pks,
132+
)
98133
return response_base.success()

crud/crud_api_key.py

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
1-
from datetime import timedelta
2-
31
from sqlalchemy import Select
42
from sqlalchemy.ext.asyncio import AsyncSession
53
from sqlalchemy_crud_plus import CRUDPlus
64

75
from backend.plugin.api_key.model import ApiKey
86
from backend.plugin.api_key.schema.api_key import CreateApiKeyParam, UpdateApiKeyParam
9-
from backend.utils.timezone import timezone
107

118

129
class CRUDApiKey(CRUDPlus[ApiKey]):
@@ -74,11 +71,9 @@ async def create(self, db: AsyncSession, user_id: int, key: str, obj: CreateApiK
7471
:param obj: 创建 API Key 参数
7572
:return:
7673
"""
77-
dict_obj = obj.model_dump(exclude={'expire_days'})
74+
dict_obj = obj.model_dump()
7875
dict_obj['user_id'] = user_id
7976
dict_obj['key'] = key
80-
if obj.expire_days is not None:
81-
dict_obj['expire_time'] = timezone.now() + timedelta(days=obj.expire_days)
8277

8378
new_api_key = self.model(**dict_obj)
8479
db.add(new_api_key)
@@ -95,8 +90,18 @@ async def update(self, db: AsyncSession, pk: int, obj: UpdateApiKeyParam) -> int
9590
:param obj: 更新 API Key 参数
9691
:return:
9792
"""
98-
expire_time = timezone.now() + timedelta(days=obj.expire_days) if obj.expire_days is not None else None
99-
return await self.update_model(db, pk, obj, expire_time=expire_time)
93+
return await self.update_model(db, pk, obj.model_dump(exclude_unset=True))
94+
95+
async def set_status(self, db: AsyncSession, pk: int, status: int) -> int:
96+
"""
97+
设置 API Key 状态
98+
99+
:param db: 数据库会话
100+
:param pk: API Key ID
101+
:param status: 状态
102+
:return:
103+
"""
104+
return await self.update_model(db, pk, {'status': status})
100105

101106
async def delete(self, db: AsyncSession, pks: list[int]) -> int:
102107
"""

schema/api_key.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,13 @@ class ApiKeySchemaBase(SchemaBase):
1717
class CreateApiKeyParam(ApiKeySchemaBase):
1818
"""创建 API Key 参数"""
1919

20-
expire_days: int | None = Field(None, ge=1, le=365, description='过期天数(空表示永不过期,最大 365 天)')
20+
expire_time: datetime | None = Field(None, description='过期时间(空表示永不过期)')
2121

2222

2323
class UpdateApiKeyParam(ApiKeySchemaBase):
2424
"""更新 API Key 参数"""
2525

26-
expire_days: int | None = Field(None, ge=1, le=365, description='过期天数(空表示永不过期,最大 365 天)')
26+
expire_time: datetime | None = Field(None, description='过期时间(空表示永不过期)')
2727

2828

2929
class DeleteApiKeyParam(SchemaBase):

service/api_key_service.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,8 @@ async def get_list(
6868
page_data = await paging_data(db, api_key_select)
6969

7070
for item in page_data['items']:
71-
if hasattr(item, 'key'):
72-
item.key = mask_key(item.key)
71+
if item.get('key') is not None:
72+
item['key'] = mask_key(item.get('key'))
7373

7474
return page_data
7575

@@ -104,6 +104,22 @@ async def update(
104104
raise errors.AuthorizationError
105105
return await api_key_dao.update(db, pk, obj)
106106

107+
async def update_status(self, *, db: AsyncSession, user_id: int, is_superuser: bool, pk: int) -> int:
108+
"""
109+
切换 API Key 状态
110+
111+
:param db: 数据库会话
112+
:param user_id: 用户 ID
113+
:param is_superuser: 用户超级管理员权限
114+
:param pk: API Key ID
115+
:return:
116+
"""
117+
api_key = await self._get(db=db, user_id=user_id, is_superuser=is_superuser, pk=pk)
118+
if not is_superuser and user_id != api_key.user_id:
119+
raise errors.AuthorizationError
120+
next_status = 0 if api_key.status == 1 else 1
121+
return await api_key_dao.set_status(db, pk, next_status)
122+
107123
async def delete(self, *, db: AsyncSession, user_id: int, is_superuser: bool, pks: list[int]) -> int:
108124
"""
109125
批量删除 API Key

sql/mysql/init.sql

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
INSERT INTO sys_menu (title, name, path, sort, icon, type, component, perms, status, display, cache, link, remark, parent_id, created_time, updated_time)
2+
SELECT
3+
'api_key.menu',
4+
'PluginApiKey',
5+
'/plugins/api-key',
6+
11,
7+
'mdi:key-outline',
8+
1,
9+
'/plugins/api_key/views/index',
10+
NULL,
11+
1,
12+
1,
13+
1,
14+
'',
15+
'API Key 管理',
16+
(SELECT id FROM (SELECT id FROM sys_menu WHERE name = 'System' LIMIT 1) AS tmp_parent),
17+
CURRENT_TIMESTAMP,
18+
CURRENT_TIMESTAMP
19+
FROM DUAL
20+
WHERE NOT EXISTS (
21+
SELECT 1 FROM sys_menu WHERE name = 'PluginApiKey'
22+
);
23+
24+
UPDATE sys_menu
25+
SET
26+
title = 'api_key.menu',
27+
path = '/plugins/api-key',
28+
sort = 11,
29+
icon = 'mdi:key-outline',
30+
type = 1,
31+
component = '/plugins/api_key/views/index',
32+
perms = NULL,
33+
status = 1,
34+
display = 1,
35+
cache = 1,
36+
link = '',
37+
remark = 'API Key 管理',
38+
parent_id = (SELECT id FROM (SELECT id FROM sys_menu WHERE name = 'System' LIMIT 1) AS tmp_parent),
39+
updated_time = CURRENT_TIMESTAMP
40+
WHERE name = 'PluginApiKey';
41+
42+
INSERT INTO sys_menu (title, name, path, sort, icon, type, component, perms, status, display, cache, link, remark, parent_id, created_time, updated_time)
43+
SELECT
44+
'新增',
45+
'AddApiKey',
46+
NULL,
47+
0,
48+
NULL,
49+
2,
50+
NULL,
51+
'sys:apikey:add',
52+
1,
53+
0,
54+
1,
55+
'',
56+
NULL,
57+
(SELECT id FROM (SELECT id FROM sys_menu WHERE name = 'PluginApiKey' LIMIT 1) AS tmp_parent),
58+
CURRENT_TIMESTAMP,
59+
CURRENT_TIMESTAMP
60+
FROM DUAL
61+
WHERE NOT EXISTS (
62+
SELECT 1 FROM sys_menu WHERE name = 'AddApiKey'
63+
);
64+
65+
UPDATE sys_menu
66+
SET
67+
title = '新增',
68+
path = NULL,
69+
sort = 0,
70+
icon = NULL,
71+
type = 2,
72+
component = NULL,
73+
perms = 'sys:apikey:add',
74+
status = 1,
75+
display = 0,
76+
cache = 1,
77+
link = '',
78+
remark = NULL,
79+
parent_id = (SELECT id FROM (SELECT id FROM sys_menu WHERE name = 'PluginApiKey' LIMIT 1) AS tmp_parent),
80+
updated_time = CURRENT_TIMESTAMP
81+
WHERE name = 'AddApiKey';
82+
83+
INSERT INTO sys_menu (title, name, path, sort, icon, type, component, perms, status, display, cache, link, remark, parent_id, created_time, updated_time)
84+
SELECT
85+
'修改',
86+
'EditApiKey',
87+
NULL,
88+
0,
89+
NULL,
90+
2,
91+
NULL,
92+
'sys:apikey:edit',
93+
1,
94+
0,
95+
1,
96+
'',
97+
NULL,
98+
(SELECT id FROM (SELECT id FROM sys_menu WHERE name = 'PluginApiKey' LIMIT 1) AS tmp_parent),
99+
CURRENT_TIMESTAMP,
100+
CURRENT_TIMESTAMP
101+
FROM DUAL
102+
WHERE NOT EXISTS (
103+
SELECT 1 FROM sys_menu WHERE name = 'EditApiKey'
104+
);
105+
106+
UPDATE sys_menu
107+
SET
108+
title = '修改',
109+
path = NULL,
110+
sort = 0,
111+
icon = NULL,
112+
type = 2,
113+
component = NULL,
114+
perms = 'sys:apikey:edit',
115+
status = 1,
116+
display = 0,
117+
cache = 1,
118+
link = '',
119+
remark = NULL,
120+
parent_id = (SELECT id FROM (SELECT id FROM sys_menu WHERE name = 'PluginApiKey' LIMIT 1) AS tmp_parent),
121+
updated_time = CURRENT_TIMESTAMP
122+
WHERE name = 'EditApiKey';
123+
124+
INSERT INTO sys_menu (title, name, path, sort, icon, type, component, perms, status, display, cache, link, remark, parent_id, created_time, updated_time)
125+
SELECT
126+
'删除',
127+
'DeleteApiKey',
128+
NULL,
129+
0,
130+
NULL,
131+
2,
132+
NULL,
133+
'sys:apikey:del',
134+
1,
135+
0,
136+
1,
137+
'',
138+
NULL,
139+
(SELECT id FROM (SELECT id FROM sys_menu WHERE name = 'PluginApiKey' LIMIT 1) AS tmp_parent),
140+
CURRENT_TIMESTAMP,
141+
CURRENT_TIMESTAMP
142+
FROM DUAL
143+
WHERE NOT EXISTS (
144+
SELECT 1 FROM sys_menu WHERE name = 'DeleteApiKey'
145+
);
146+
147+
UPDATE sys_menu
148+
SET
149+
title = '删除',
150+
path = NULL,
151+
sort = 0,
152+
icon = NULL,
153+
type = 2,
154+
component = NULL,
155+
perms = 'sys:apikey:del',
156+
status = 1,
157+
display = 0,
158+
cache = 1,
159+
link = '',
160+
remark = NULL,
161+
parent_id = (SELECT id FROM (SELECT id FROM sys_menu WHERE name = 'PluginApiKey' LIMIT 1) AS tmp_parent),
162+
updated_time = CURRENT_TIMESTAMP
163+
WHERE name = 'DeleteApiKey';

0 commit comments

Comments
 (0)