Skip to content

Commit ae9bc0e

Browse files
authored
Add tenant plugin (#1)
* Add tenant plugin * Add release ci * Fix package id index * Fix and improve the issue * Fix known issues and improvements * Optimize permission logic * Fix lint * Fix destroy SQL scripts
1 parent e833e0f commit ae9bc0e

31 files changed

Lines changed: 1390 additions & 0 deletions

.github/workflows/release.yml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
name: Plugin Release
2+
3+
on:
4+
push:
5+
tags:
6+
- "v*"
7+
8+
jobs:
9+
update-submodule:
10+
runs-on: ubuntu-latest
11+
steps:
12+
- name: Update plugin submodule
13+
uses: fastapi-practices/plugin-release@v1
14+
with:
15+
push-to: fastapi-practices/plugins
16+
env:
17+
GH_TOKEN: ${{ secrets.GH_TOKEN }}

__init__.py

Whitespace-only changes.

api/__init__.py

Whitespace-only changes.

api/router.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
from fastapi import APIRouter
2+
3+
from backend.core.conf import settings
4+
from backend.plugin.tenant.api.v1.package import router as package_router
5+
from backend.plugin.tenant.api.v1.tenant import router as tenant_router
6+
7+
v1 = APIRouter(prefix=settings.FASTAPI_API_V1_PATH)
8+
9+
v1.include_router(tenant_router, prefix='/tenants', tags=['租户管理'])
10+
v1.include_router(package_router, prefix='/tenant/packages', tags=['租户套餐管理'])

api/v1/__init__.py

Whitespace-only changes.

api/v1/package.py

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
from typing import Annotated
2+
3+
from fastapi import APIRouter, Depends, Path, Query
4+
5+
from backend.app.admin.schema.menu import GetMenuTree
6+
from backend.common.pagination import DependsPagination, PageData
7+
from backend.common.response.response_schema import ResponseModel, ResponseSchemaModel, response_base
8+
from backend.common.security.jwt import DependsJwtAuth
9+
from backend.common.security.permission import RequestPermission
10+
from backend.common.security.rbac import DependsRBAC
11+
from backend.database.db import CurrentSession, CurrentSessionTransaction
12+
from backend.plugin.tenant.schema.package import (
13+
CreateTenantPackageParam,
14+
GetTenantPackageDetail,
15+
UpdateTenantPackageParam,
16+
)
17+
from backend.plugin.tenant.service.package_service import tenant_package_service
18+
19+
router = APIRouter()
20+
21+
22+
@router.get('/{pk}', summary='获取套餐详情', dependencies=[DependsJwtAuth])
23+
async def get_tenant_package(
24+
db: CurrentSession, pk: Annotated[int, Path(description='套餐 ID')]
25+
) -> ResponseSchemaModel[GetTenantPackageDetail]:
26+
package = await tenant_package_service.get(db=db, pk=pk)
27+
return response_base.success(data=package)
28+
29+
30+
@router.get('/{pk}/menus', summary='获取套餐菜单树', dependencies=[DependsJwtAuth])
31+
async def get_tenant_package_menus(
32+
db: CurrentSession, pk: Annotated[int, Path(description='套餐 ID')]
33+
) -> ResponseSchemaModel[list[GetMenuTree] | None]:
34+
menus = await tenant_package_service.get_menu_tree(db=db, pk=pk)
35+
return response_base.success(data=menus)
36+
37+
38+
@router.get(
39+
'',
40+
summary='分页获取所有套餐',
41+
dependencies=[
42+
DependsJwtAuth,
43+
DependsPagination,
44+
],
45+
)
46+
async def get_tenant_packages_paginated(
47+
db: CurrentSession,
48+
name: Annotated[str | None, Query(description='套餐名称')] = None,
49+
status: Annotated[int | None, Query(description='状态')] = None,
50+
) -> ResponseSchemaModel[PageData[GetTenantPackageDetail]]:
51+
page_data = await tenant_package_service.get_list(db=db, name=name, status=status)
52+
return response_base.success(data=page_data)
53+
54+
55+
@router.post(
56+
'',
57+
summary='创建套餐',
58+
dependencies=[
59+
Depends(RequestPermission('tenant:package:add')),
60+
DependsRBAC,
61+
],
62+
)
63+
async def create_tenant_package(db: CurrentSessionTransaction, obj: CreateTenantPackageParam) -> ResponseModel:
64+
await tenant_package_service.create(db=db, obj=obj)
65+
return response_base.success()
66+
67+
68+
@router.put(
69+
'/{pk}',
70+
summary='更新套餐',
71+
dependencies=[
72+
Depends(RequestPermission('tenant:package:edit')),
73+
DependsRBAC,
74+
],
75+
)
76+
async def update_tenant_package(
77+
db: CurrentSessionTransaction, pk: Annotated[int, Path(description='套餐 ID')], obj: UpdateTenantPackageParam
78+
) -> ResponseModel:
79+
count = await tenant_package_service.update(db=db, pk=pk, obj=obj)
80+
if count > 0:
81+
return response_base.success()
82+
return response_base.fail()
83+
84+
85+
@router.delete(
86+
'/{pk}',
87+
summary='删除套餐',
88+
dependencies=[
89+
Depends(RequestPermission('tenant:package:del')),
90+
DependsRBAC,
91+
],
92+
)
93+
async def delete_tenant_package(
94+
db: CurrentSessionTransaction, pk: Annotated[int, Path(description='套餐 ID')]
95+
) -> ResponseModel:
96+
count = await tenant_package_service.delete(db=db, pk=pk)
97+
if count > 0:
98+
return response_base.success()
99+
return response_base.fail()

api/v1/tenant.py

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
from typing import Annotated
2+
3+
from fastapi import APIRouter, Depends, Path, Query
4+
5+
from backend.common.pagination import DependsPagination, PageData
6+
from backend.common.response.response_schema import ResponseModel, ResponseSchemaModel, response_base
7+
from backend.common.security.jwt import DependsJwtAuth
8+
from backend.common.security.permission import RequestPermission
9+
from backend.common.security.rbac import DependsRBAC
10+
from backend.core.conf import settings
11+
from backend.database.db import CurrentSession, CurrentSessionTransaction
12+
from backend.plugin.tenant.schema.tenant import (
13+
CreateTenantParam,
14+
DeleteTenantParam,
15+
GetTenantDetail,
16+
UpdateTenantAdminPwdParam,
17+
UpdateTenantParam,
18+
)
19+
from backend.plugin.tenant.service.tenant_service import tenant_service
20+
21+
router = APIRouter()
22+
23+
24+
@router.get('/enabled', summary='获取租户开启状态')
25+
async def get_tenant_enabled_status() -> ResponseSchemaModel[bool]:
26+
return response_base.success(data=settings.TENANT_ENABLED)
27+
28+
29+
@router.get('/id', summary='获取租户 ID')
30+
async def get_tenant_id(
31+
db: CurrentSession,
32+
domain: Annotated[str, Query(description='租户域名')],
33+
) -> ResponseSchemaModel[int | None]:
34+
tenant_id = await tenant_service.get_id_by_domain(db=db, domain=domain)
35+
return response_base.success(data=tenant_id)
36+
37+
38+
@router.get('/{pk}', summary='获取租户详情', dependencies=[DependsJwtAuth])
39+
async def get_tenant(
40+
db: CurrentSession, pk: Annotated[int, Path(description='租户 ID')]
41+
) -> ResponseSchemaModel[GetTenantDetail]:
42+
tenant = await tenant_service.get(db=db, pk=pk)
43+
return response_base.success(data=tenant)
44+
45+
46+
@router.get(
47+
'',
48+
summary='分页获取所有租户',
49+
dependencies=[
50+
DependsJwtAuth,
51+
DependsPagination,
52+
],
53+
)
54+
async def get_tenants_paginated(
55+
db: CurrentSession,
56+
name: Annotated[str | None, Query(description='租户名称')] = None,
57+
code: Annotated[str | None, Query(description='租户编码')] = None,
58+
domain: Annotated[str | None, Query(description='租户域名')] = None,
59+
package_id: Annotated[int | None, Query(description='套餐 ID')] = None,
60+
status: Annotated[int | None, Query(description='状态')] = None,
61+
) -> ResponseSchemaModel[PageData[GetTenantDetail]]:
62+
page_data = await tenant_service.get_list(
63+
db=db,
64+
name=name,
65+
code=code,
66+
domain=domain,
67+
package_id=package_id,
68+
status=status,
69+
)
70+
return response_base.success(data=page_data)
71+
72+
73+
@router.post(
74+
'',
75+
summary='创建租户',
76+
dependencies=[
77+
Depends(RequestPermission('tenant:management:add')),
78+
DependsRBAC,
79+
],
80+
)
81+
async def create_tenant(db: CurrentSessionTransaction, obj: CreateTenantParam) -> ResponseModel:
82+
await tenant_service.create(db=db, obj=obj)
83+
return response_base.success()
84+
85+
86+
@router.put(
87+
'/{pk}',
88+
summary='更新租户',
89+
dependencies=[
90+
Depends(RequestPermission('tenant:management:edit')),
91+
DependsRBAC,
92+
],
93+
)
94+
async def update_tenant(
95+
db: CurrentSessionTransaction, pk: Annotated[int, Path(description='租户 ID')], obj: UpdateTenantParam
96+
) -> ResponseModel:
97+
count = await tenant_service.update(db=db, pk=pk, obj=obj)
98+
if count > 0:
99+
return response_base.success()
100+
return response_base.fail()
101+
102+
103+
@router.put(
104+
'/{pk}/admin/password',
105+
summary='修改租户管理员密码',
106+
dependencies=[
107+
Depends(RequestPermission('tenant:management:pwd')),
108+
DependsRBAC,
109+
],
110+
)
111+
async def update_tenant_admin_password(
112+
db: CurrentSessionTransaction,
113+
pk: Annotated[int, Path(description='租户 ID')],
114+
obj: UpdateTenantAdminPwdParam,
115+
) -> ResponseModel:
116+
await tenant_service.update_admin_password(db=db, pk=pk, password=obj.password)
117+
return response_base.success()
118+
119+
120+
@router.delete(
121+
'',
122+
summary='批量删除租户',
123+
dependencies=[
124+
Depends(RequestPermission('tenant:management:del')),
125+
DependsRBAC,
126+
],
127+
)
128+
async def delete_tenants(db: CurrentSessionTransaction, obj: DeleteTenantParam) -> ResponseModel:
129+
count = await tenant_service.delete(db=db, obj=obj)
130+
if count > 0:
131+
return response_base.success()
132+
return response_base.fail()

crud/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)