Skip to content

Commit

Permalink
Add RBAC authorisation and some tools or optimisations (#41)
Browse files Browse the repository at this point in the history
* WIP: add rbac authorization

* Perform pre-commit fixes

* add rbac route whitelist

* add init test data user role associations

* Restore database table id naming to fix generic crud base

* Add database section value uniqueness settings

* Update the test directory to tests

* Update route_name file name to health_check

* Split user auth and user action interfaces

* Fix conflict between merge and current branch

* Add pymysql dependencies

* Fix RBAC authentication method

* Add the select serialisation tool

* Fix missing return messages due to global exception handler slicing

* Update the user interface with associated relationships

* Add items to be completed

* Perform pre-commit fixes

* Add pre-made routers

* Paging data return structure optimisation

* Split user auth and user interface tests

* Fix user register test data structure error

* Fix duplicate named test classes
  • Loading branch information
wu-clan authored May 17, 2023
1 parent cee2c58 commit e1edcad
Show file tree
Hide file tree
Showing 62 changed files with 1,149 additions and 127 deletions.
13 changes: 10 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# FastAPI Best Architecture

This is a base project of the FastAPI framework.
This is a base project of the FastAPI framework, in production

It‘s purpose is to allow you to develop your project directly with it
as your base project
Expand Down Expand Up @@ -29,19 +29,24 @@ git clone https://github.com/wu-clan/fastapi_best_architecture.git
### 1:Tradition

1. Install dependencies

```shell
pip install -r requirements.txt
```

2. Create a database `fba`, choose utf8mb4 encode
3. Install and start Redis
4. create a `.env` file in the `backend/app/` directory

```shell
cd backend/app/
touch .env
```
5. Copy .env.example to .env and view `backend/app/core/conf.py`, update database configuration information

5. Copy `.env.example` to `.env` and view `backend/app/core/conf.py`, update database configuration information
6. Perform a database migration [alembic](https://alembic.sqlalchemy.org/en/latest/tutorial.html)

```shell
cd backend/app/
Expand All @@ -51,7 +56,8 @@ git clone https://github.com/wu-clan/fastapi_best_architecture.git
# Perform the migration
alembic upgrade head
```
7. Execute the backend/app/main.py file startup service

7. Execute the `backend/app/main.py` file startup service
8. Browser access: http://127.0.0.1:8000/v1/docs

---
Expand All @@ -63,6 +69,7 @@ git clone https://github.com/wu-clan/fastapi_best_architecture.git
```shell
docker-compose up -d --build
```

2. Wait for the command to finish automatically

3. Browser access: http://127.0.0.1:8000/v1/docs
Expand Down
90 changes: 90 additions & 0 deletions README_zh.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# FastAPI 最佳架构

这是 FastAPI 框架的一个基础项目,在制作中

它的目的是让你直接用它作为你的基础项目来开发你的项目

支持 python3.10 及以上版本

## 技术栈

- [x] FastAPI
- [x] Pydantic
- [x] SQLAlchemy
- [x] Alembic
- [x] MySQL
- [x] Redis
- [x] APScheduler
- [x] Docker

## 克隆

```shell
git clone https://github.com/wu-clan/fastapi_best_architecture.git
```

## 使用:

### 1:传统

1. 安装依赖项
```shell
pip install -r requirements.txt
```

2. 创建一个数据库`fba`,选择 utf8mb4 编码
3. 安装并启动 Redis
4. 在`backend/app/`目录下创建一个`.env`文件
```shell
cd backend/app/
touch .env
```
5. 复制 `.env.example``.env` 并查看`backend/app/core/conf.py`,更新数据库配置信息
6. 进行数据库迁移[alembic](https://alembic.sqlalchemy.org/en/latest/tutorial.html)
```shell
cd backend/app/
# 生成迁移文件
alembic revision --autogenerate
# 执行迁移
alembic upgrade head
```
7. 执行 `backend/app/main.py` 文件启动服务
8. 浏览器访问:http://127.0.0.1:8000/v1/docs

---

### 2:Docker

1. 在 `docker-compose.yml` 文件所在的目录中运行一键启动命令

```shell
docker-compose up -d -build
```

2. 等待命令自动完成

3. 浏览器访问:http://127.0.0.1:8000/v1/docs

## 初始化测试数据

执行 `backend/app/init_test_data.py` 文件

## 测试

通过 pytest 进行测试

**提示**: 在测试开始前,请先执行初始化测试数据,同时,需要启动 fastapi 服务。

1. 首先,进入app目录

```shell
cd backend/app/
```

2. 执行测试命令

```shell
pytest -vs --disable-warnings
```
18 changes: 14 additions & 4 deletions backend/app/api/routers.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,23 @@
from fastapi import APIRouter

from backend.app.api.v1.auth import router as auth_router
from backend.app.api.v1.user import router as user_router
from backend.app.api.v1.casbin import router as casbin_router
from backend.app.api.v1.dept import router as dept_router
from backend.app.api.v1.role import router as role_router
from backend.app.api.v1.menu import router as menu_router
from backend.app.api.v1.api import router as api_router
from backend.app.api.v1.task_demo import router as task_demo_router
from backend.app.api.v1.sys_config import router as sys_config_router
from backend.app.api.v1.config import router as config_router

v1 = APIRouter(prefix='/v1')

v1.include_router(auth_router)

v1.include_router(user_router, prefix='/users', tags=['用户管理'])
v1.include_router(casbin_router, prefix='/casbin', tags=['权限管理'])
v1.include_router(dept_router, prefix='/depts', tags=['部门管理'])
v1.include_router(role_router, prefix='/roles', tags=['角色管理'])
v1.include_router(menu_router, prefix='/menus', tags=['菜单管理'])
v1.include_router(api_router, prefix='/apis', tags=['API管理'])
v1.include_router(config_router, prefix='/configs', tags=['系统配置'])
v1.include_router(task_demo_router, prefix='/tasks', tags=['任务管理'])

v1.include_router(sys_config_router, prefix='/configs', tags=['系统配置'])
7 changes: 7 additions & 0 deletions backend/app/api/v1/api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from fastapi import APIRouter

router = APIRouter()

# TODO: 添加 api 相关接口
6 changes: 3 additions & 3 deletions backend/app/api/v1/auth/__init__.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from fastapi import APIRouter
from backend.app.api.v1.auth.user import router as user_router
from backend.app.api.v1.auth.auth import router as auth_router

router = APIRouter(prefix='/auth', tags=['用户管理'])
router = APIRouter(prefix='/auth', tags=['认证'])

router.include_router(user_router, prefix='/users')
router.include_router(auth_router, prefix='/users')
32 changes: 32 additions & 0 deletions backend/app/api/v1/auth/auth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from fastapi import APIRouter, Depends
from fastapi.security import OAuth2PasswordRequestForm

from backend.app.common.jwt import DependsUser
from backend.app.common.response.response_schema import response_base
from backend.app.schemas.token import Token
from backend.app.schemas.user import Auth
from backend.app.services.user_service import UserService

router = APIRouter()


@router.post('/swagger_login', summary='swagger 表单登录', description='form 格式登录,仅用于 swagger 文档调试接口')
async def swagger_user_login(form_data: OAuth2PasswordRequestForm = Depends()) -> Token:
token, user = await UserService.swagger_login(form_data)
return Token(access_token=token, user=user)


@router.post('/login', summary='用户登录', description='json 格式登录, 仅支持在第三方api工具调试接口, 例如: postman')
async def user_login(obj: Auth):
token, user = await UserService.login(obj)
# TODO: token 存储
data = Token(access_token=token, user=user)
return response_base.response_200(data=data)


@router.post('/logout', summary='用户登出', dependencies=[DependsUser])
async def user_logout():
# TODO: 加入 token 黑名单
return response_base.response_200()
7 changes: 7 additions & 0 deletions backend/app/api/v1/casbin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from fastapi import APIRouter

router = APIRouter()

# TODO: 添加 casbin 相关接口
22 changes: 16 additions & 6 deletions backend/app/api/v1/sys_config.py → backend/app/api/v1/config.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from fastapi import APIRouter
from fastapi import APIRouter, Request
from fastapi.routing import APIRoute

from backend.app.api.jwt import DependsSuperUser
from backend.app.common.response.response_schema import ResponseModel
from backend.app.common.casbin_rbac import DependsRBAC
from backend.app.common.response.response_schema import response_base
from backend.app.core.conf import settings

router = APIRouter()


@router.get('', summary='获取系统配置', dependencies=[DependsSuperUser])
async def get_sys_config() -> ResponseModel:
return ResponseModel(
@router.get('', summary='获取系统配置', dependencies=[DependsRBAC])
async def get_sys_config():
return response_base.success(
data={
'title': settings.TITLE,
'version': settings.VERSION,
Expand Down Expand Up @@ -49,3 +50,12 @@ async def get_sys_config() -> ResponseModel:
'middleware_access': settings.MIDDLEWARE_ACCESS,
}
)


@router.get('/routers', summary='获取所有路由', dependencies=[DependsRBAC])
async def get_all_route(request: Request):
data = []
for route in request.app.routes:
if isinstance(route, APIRoute):
data.append({'path': route.path, 'name': route.name, 'summary': route.summary, 'methods': route.methods})
return response_base.success(data={'route_list': data})
7 changes: 7 additions & 0 deletions backend/app/api/v1/dept.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from fastapi import APIRouter

router = APIRouter()

# TODO: 添加 dept 相关接口
7 changes: 7 additions & 0 deletions backend/app/api/v1/menu.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from fastapi import APIRouter

router = APIRouter()

# TODO: 添加 menu 相关接口
7 changes: 7 additions & 0 deletions backend/app/api/v1/role.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from fastapi import APIRouter

router = APIRouter()

# TODO: 添加 role 相关接口
38 changes: 14 additions & 24 deletions backend/app/api/v1/auth/user.py → backend/app/api/v1/user.py
Original file line number Diff line number Diff line change
@@ -1,31 +1,18 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from fastapi import APIRouter, Depends
from fastapi.security import OAuth2PasswordRequestForm
from fastapi import APIRouter

from backend.app.api.jwt import CurrentUser, DependsUser, DependsSuperUser
from backend.app.api.service.user_service import UserService
from backend.app.common.pagination import Page
from backend.app.common.jwt import DependsUser, CurrentUser, DependsSuperUser
from backend.app.common.pagination import paging_data, PageDepends
from backend.app.common.response.response_schema import response_base
from backend.app.schemas.token import Token
from backend.app.schemas.user import CreateUser, GetUserInfo, ResetPassword, UpdateUser, Avatar, Auth
from backend.app.database.db_mysql import CurrentSession
from backend.app.schemas.user import CreateUser, GetUserInfo, ResetPassword, UpdateUser, Avatar
from backend.app.services.user_service import UserService
from backend.app.utils.serializers import select_to_json

router = APIRouter()


@router.post('/swagger_login', summary='swagger 表单登录', description='form 格式登录,仅用于 swagger 文档调试接口')
async def swagger_user_login(form_data: OAuth2PasswordRequestForm = Depends()) -> Token:
token, user = await UserService.swagger_login(form_data)
return Token(access_token=token, user=user)


@router.post('/login', summary='用户登录', description='json 格式登录, 仅支持在第三方api工具调试接口, 例如: postman')
async def user_login(obj: Auth):
token, user = await UserService.login(obj)
data = Token(access_token=token, user=user)
return response_base.response_200(data=data)


@router.post('/register', summary='用户注册')
async def user_register(obj: CreateUser):
await UserService.register(obj)
Expand All @@ -41,7 +28,8 @@ async def password_reset(obj: ResetPassword):
@router.get('/{username}', summary='查看用户信息', dependencies=[DependsUser])
async def userinfo(username: str):
current_user = await UserService.get_userinfo(username)
return response_base.response_200(data=current_user, exclude={'password'})
data = GetUserInfo(**select_to_json(current_user))
return response_base.response_200(data=data, exclude={'password'})


@router.put('/{username}', summary='更新用户信息')
Expand All @@ -60,9 +48,11 @@ async def update_avatar(username: str, avatar: Avatar, current_user: CurrentUser
return response_base.fail()


@router.get('', summary='获取所有用户', dependencies=[DependsUser])
async def get_all_users() -> Page[GetUserInfo]:
return await UserService.get_user_list()
@router.get('', summary='获取所有用户', dependencies=[DependsUser, PageDepends])
async def get_all_users(db: CurrentSession):
user_list = await UserService.get_user_list()
page_data = await paging_data(db, user_list, GetUserInfo)
return response_base.response_200(data=page_data)


@router.post('/{pk}/super', summary='修改用户超级权限', dependencies=[DependsSuperUser])
Expand Down
Loading

0 comments on commit e1edcad

Please sign in to comment.