Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 39 additions & 4 deletions api/v1/sys/api_key.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

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

from backend.common.pagination import DependsPagination, PageData
from backend.common.response.response_code import CustomResponse
from backend.common.response.response_schema import ResponseModel, ResponseSchemaModel, response_base
from backend.common.security.jwt import DependsJwtAuth
Expand All @@ -21,19 +22,26 @@

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


@router.get('', summary='分页获取所有 API Key', dependencies=[DependsJwtAuth])
@router.get(
'',
summary='分页获取所有 API Key',
dependencies=[
DependsJwtAuth,
DependsPagination,
],
)
async def get_api_keys_paginated(
db: CurrentSession,
request: Request,
name: Annotated[str | None, Query(description='API Key 名称')] = None,
status: Annotated[int | None, Query(description='状态')] = None,
) -> ResponseSchemaModel[list[GetApiKeyDetail]]:
) -> ResponseSchemaModel[PageData[GetApiKeyDetail]]:
data = await api_key_service.get_list(
db=db, user_id=request.user.id, is_superuser=request.user.is_superuser, name=name, status=status
)
Expand Down Expand Up @@ -81,6 +89,28 @@ async def update_api_key(
return response_base.success()


@router.put(
'/{pk}/status',
summary='切换 API Key 状态',
dependencies=[
Depends(RequestPermission('sys:apikey:edit')),
DependsRBAC,
],
)
async def update_api_key_status(
db: CurrentSessionTransaction,
request: Request,
pk: Annotated[int, Path(description='API Key ID')],
) -> ResponseModel:
await api_key_service.update_status(
db=db,
user_id=request.user.id,
is_superuser=request.user.is_superuser,
pk=pk,
)
return response_base.success()


@router.delete(
'',
summary='批量删除 API Key',
Expand All @@ -94,5 +124,10 @@ async def delete_api_keys(
request: Request,
obj: DeleteApiKeyParam,
) -> ResponseModel:
await api_key_service.delete(db=db, user_id=request.user.id, pks=obj.pks)
await api_key_service.delete(
db=db,
user_id=request.user.id,
is_superuser=request.user.is_superuser,
pks=obj.pks,
)
return response_base.success()
21 changes: 13 additions & 8 deletions crud/crud_api_key.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
from datetime import timedelta

from sqlalchemy import Select
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy_crud_plus import CRUDPlus

from backend.plugin.api_key.model import ApiKey
from backend.plugin.api_key.schema.api_key import CreateApiKeyParam, UpdateApiKeyParam
from backend.utils.timezone import timezone


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

new_api_key = self.model(**dict_obj)
db.add(new_api_key)
Expand All @@ -95,8 +90,18 @@ async def update(self, db: AsyncSession, pk: int, obj: UpdateApiKeyParam) -> int
:param obj: 更新 API Key 参数
:return:
"""
expire_time = timezone.now() + timedelta(days=obj.expire_days) if obj.expire_days is not None else None
return await self.update_model(db, pk, obj, expire_time=expire_time)
return await self.update_model(db, pk, obj.model_dump(exclude_unset=True))

async def set_status(self, db: AsyncSession, pk: int, status: int) -> int:
"""
设置 API Key 状态

:param db: 数据库会话
:param pk: API Key ID
:param status: 状态
:return:
"""
return await self.update_model(db, pk, {'status': status})

async def delete(self, db: AsyncSession, pks: list[int]) -> int:
"""
Expand Down
4 changes: 2 additions & 2 deletions schema/api_key.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,13 @@ class ApiKeySchemaBase(SchemaBase):
class CreateApiKeyParam(ApiKeySchemaBase):
"""创建 API Key 参数"""

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


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

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


class DeleteApiKeyParam(SchemaBase):
Expand Down
20 changes: 18 additions & 2 deletions service/api_key_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,8 @@ async def get_list(
page_data = await paging_data(db, api_key_select)

for item in page_data['items']:
if hasattr(item, 'key'):
item.key = mask_key(item.key)
if item.get('key') is not None:
item['key'] = mask_key(item.get('key'))

return page_data

Expand Down Expand Up @@ -104,6 +104,22 @@ async def update(
raise errors.AuthorizationError
return await api_key_dao.update(db, pk, obj)

async def update_status(self, *, db: AsyncSession, user_id: int, is_superuser: bool, pk: int) -> int:
"""
切换 API Key 状态

:param db: 数据库会话
:param user_id: 用户 ID
:param is_superuser: 用户超级管理员权限
:param pk: API Key ID
:return:
"""
api_key = await self._get(db=db, user_id=user_id, is_superuser=is_superuser, pk=pk)
if not is_superuser and user_id != api_key.user_id:
raise errors.AuthorizationError
next_status = 0 if api_key.status == 1 else 1
return await api_key_dao.set_status(db, pk, next_status)

async def delete(self, *, db: AsyncSession, user_id: int, is_superuser: bool, pks: list[int]) -> int:
"""
批量删除 API Key
Expand Down
163 changes: 163 additions & 0 deletions sql/mysql/init.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
INSERT INTO sys_menu (title, name, path, sort, icon, type, component, perms, status, display, cache, link, remark, parent_id, created_time, updated_time)
SELECT
'api_key.menu',
'PluginApiKey',
'/plugins/api-key',
11,
'mdi:key-outline',
1,
'/plugins/api_key/views/index',
NULL,
1,
1,
1,
'',
'API Key 管理',
(SELECT id FROM (SELECT id FROM sys_menu WHERE name = 'System' LIMIT 1) AS tmp_parent),
CURRENT_TIMESTAMP,
CURRENT_TIMESTAMP
FROM DUAL
WHERE NOT EXISTS (
SELECT 1 FROM sys_menu WHERE name = 'PluginApiKey'
);

UPDATE sys_menu
SET
title = 'api_key.menu',
path = '/plugins/api-key',
sort = 11,
icon = 'mdi:key-outline',
type = 1,
component = '/plugins/api_key/views/index',
perms = NULL,
status = 1,
display = 1,
cache = 1,
link = '',
remark = 'API Key 管理',
parent_id = (SELECT id FROM (SELECT id FROM sys_menu WHERE name = 'System' LIMIT 1) AS tmp_parent),
updated_time = CURRENT_TIMESTAMP
WHERE name = 'PluginApiKey';

INSERT INTO sys_menu (title, name, path, sort, icon, type, component, perms, status, display, cache, link, remark, parent_id, created_time, updated_time)
SELECT
'新增',
'AddApiKey',
NULL,
0,
NULL,
2,
NULL,
'sys:apikey:add',
1,
0,
1,
'',
NULL,
(SELECT id FROM (SELECT id FROM sys_menu WHERE name = 'PluginApiKey' LIMIT 1) AS tmp_parent),
CURRENT_TIMESTAMP,
CURRENT_TIMESTAMP
FROM DUAL
WHERE NOT EXISTS (
SELECT 1 FROM sys_menu WHERE name = 'AddApiKey'
);

UPDATE sys_menu
SET
title = '新增',
path = NULL,
sort = 0,
icon = NULL,
type = 2,
component = NULL,
perms = 'sys:apikey:add',
status = 1,
display = 0,
cache = 1,
link = '',
remark = NULL,
parent_id = (SELECT id FROM (SELECT id FROM sys_menu WHERE name = 'PluginApiKey' LIMIT 1) AS tmp_parent),
updated_time = CURRENT_TIMESTAMP
WHERE name = 'AddApiKey';

INSERT INTO sys_menu (title, name, path, sort, icon, type, component, perms, status, display, cache, link, remark, parent_id, created_time, updated_time)
SELECT
'修改',
'EditApiKey',
NULL,
0,
NULL,
2,
NULL,
'sys:apikey:edit',
1,
0,
1,
'',
NULL,
(SELECT id FROM (SELECT id FROM sys_menu WHERE name = 'PluginApiKey' LIMIT 1) AS tmp_parent),
CURRENT_TIMESTAMP,
CURRENT_TIMESTAMP
FROM DUAL
WHERE NOT EXISTS (
SELECT 1 FROM sys_menu WHERE name = 'EditApiKey'
);

UPDATE sys_menu
SET
title = '修改',
path = NULL,
sort = 0,
icon = NULL,
type = 2,
component = NULL,
perms = 'sys:apikey:edit',
status = 1,
display = 0,
cache = 1,
link = '',
remark = NULL,
parent_id = (SELECT id FROM (SELECT id FROM sys_menu WHERE name = 'PluginApiKey' LIMIT 1) AS tmp_parent),
updated_time = CURRENT_TIMESTAMP
WHERE name = 'EditApiKey';

INSERT INTO sys_menu (title, name, path, sort, icon, type, component, perms, status, display, cache, link, remark, parent_id, created_time, updated_time)
SELECT
'删除',
'DeleteApiKey',
NULL,
0,
NULL,
2,
NULL,
'sys:apikey:del',
1,
0,
1,
'',
NULL,
(SELECT id FROM (SELECT id FROM sys_menu WHERE name = 'PluginApiKey' LIMIT 1) AS tmp_parent),
CURRENT_TIMESTAMP,
CURRENT_TIMESTAMP
FROM DUAL
WHERE NOT EXISTS (
SELECT 1 FROM sys_menu WHERE name = 'DeleteApiKey'
);

UPDATE sys_menu
SET
title = '删除',
path = NULL,
sort = 0,
icon = NULL,
type = 2,
component = NULL,
perms = 'sys:apikey:del',
status = 1,
display = 0,
cache = 1,
link = '',
remark = NULL,
parent_id = (SELECT id FROM (SELECT id FROM sys_menu WHERE name = 'PluginApiKey' LIMIT 1) AS tmp_parent),
updated_time = CURRENT_TIMESTAMP
WHERE name = 'DeleteApiKey';
Loading