Compare commits

..
17 Commits
Author SHA1 Message Date
Wu Clan 1f958fc42a Bump fastapi to remove warning for python 3.14 (#872) 2025-10-21 15:45:05 +08:00
Wu Clan e6e53296a8 Optimize sqlalchemy types to simplify compatibility (#870)
* Optimize sqlalchemy types to simplify compatibility

* Update TimeZone type comment
2025-10-20 17:41:00 +08:00
Wu Clan f533de8abd Bump rtoml and uvicorn to support python 3.14 (#871) 2025-10-20 12:42:28 +08:00
Wu Clan adc6427aab Update the changelog for v1.10.1 (#869) 2025-10-18 18:29:30 +08:00
Wu Clan d034b78b48 Bump dependencies to the latest version (#868) 2025-10-18 18:14:43 +08:00
Wu Clan 6deda906de Update the release changelog workflow (#867) 2025-10-18 18:08:29 +08:00
Wu Clan a063105de8 Fix the volume in the docker compose script (#865)
* Fix the volume in the docker compose script

* Fix the db comment
2025-10-18 15:57:29 +08:00
Wu Clan 02b4215742 Fix tasks related to deleting db logs (#863) 2025-10-18 15:49:55 +08:00
Wu Clan 56b6e2113f Update the changelog for v1.10.0 (#864) 2025-10-18 15:44:17 +08:00
Wu Clan 4fcc78eb94 Update the changelog for v1.9.0 (#862) 2025-10-18 15:41:28 +08:00
Wu Clan b28a4d7673 Update the primary database to postgresql (#829)
* Update the primary database to postgresql

* Delete the prints

* Fix lint

* Update models in code generation

* Delete print

* Fix the salt column type
2025-10-17 23:03:17 +08:00
Wu Clan 27383e4ff5 Fix ctx in validation exception handler (#861)
* Fix ctx in validation exception handler

* Fix lint
2025-10-17 16:21:28 +08:00
Wu Clan bf16f8916d Update the changelog for v1.9.0 (#856) 2025-10-17 13:05:31 +08:00
Wu Clan 75a62ac6aa Fix dict plugin pgsql init script missing (#857)
* Fix dict plugin pgsql init script missing

* Fix lint
2025-10-17 13:05:10 +08:00
Wu Clan bbc4aaa020 Fix the api ninja in code generation (#858) 2025-10-17 13:04:54 +08:00
Wu Clan 89e5d02fe2 Update unique judgment logic for dict data (#859) 2025-10-17 13:04:35 +08:00
Wu Clan a93973af06 Fix the import table cli command (#860) 2025-10-17 13:02:28 +08:00
45 changed files with 1192 additions and 1032 deletions
+2
View File
@@ -18,4 +18,6 @@ jobs:
with: with:
file: CHANGELOG.md file: CHANGELOG.md
pull_request: true pull_request: true
commit_summary_template: 'Update the changelog for %s'
args: -c
github_token: ${{ secrets.GH_TOKEN }} github_token: ${{ secrets.GH_TOKEN }}
+187
View File
@@ -1,3 +1,67 @@
<a id="v1.10.1"></a>
# [v1.10.1](https://github.com/fastapi-practices/fastapi_best_architecture/releases/tag/v1.10.1) - 2025-10-18
## What's Changed
* Update the changelog for v1.10.0 by [@wu-clan](https://github.com/wu-clan) in [#864](https://github.com/fastapi-practices/fastapi_best_architecture/pull/864)
* Fix tasks related to deleting db logs by [@wu-clan](https://github.com/wu-clan) in [#863](https://github.com/fastapi-practices/fastapi_best_architecture/pull/863)
* Fix the volume in the docker compose script by [@wu-clan](https://github.com/wu-clan) in [#865](https://github.com/fastapi-practices/fastapi_best_architecture/pull/865)
* Update the release changelog workflow by [@wu-clan](https://github.com/wu-clan) in [#867](https://github.com/fastapi-practices/fastapi_best_architecture/pull/867)
* Bump dependencies to the latest version by [@wu-clan](https://github.com/wu-clan) in [#868](https://github.com/fastapi-practices/fastapi_best_architecture/pull/868)
**Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.10.0...v1.10.1
## Contributors
<a href="https://github.com/wu-clan"><img src="https://wsrv.nl/?url=https%3A%2F%2Fgithub.com%2Fwu-clan.png&w=128&h=128&fit=cover&mask=circle" width="64" height="64" alt="@wu-clan"></a>
[Changes][v1.10.1]
<a id="v1.10.0"></a>
# [v1.10.0](https://github.com/fastapi-practices/fastapi_best_architecture/releases/tag/v1.10.0) - 2025-10-17
## What's Changed
* Fix the import table cli command by [@wu-clan](https://github.com/wu-clan) in [#860](https://github.com/fastapi-practices/fastapi_best_architecture/pull/860)
* Update unique judgment logic for dict data by [@wu-clan](https://github.com/wu-clan) in [#859](https://github.com/fastapi-practices/fastapi_best_architecture/pull/859)
* Fix the api ninja in code generation by [@wu-clan](https://github.com/wu-clan) in [#858](https://github.com/fastapi-practices/fastapi_best_architecture/pull/858)
* Fix dict plugin pgsql init script missing by [@wu-clan](https://github.com/wu-clan) in [#857](https://github.com/fastapi-practices/fastapi_best_architecture/pull/857)
* Update the changelog for v1.9.0 by [@wu-clan](https://github.com/wu-clan) in [#856](https://github.com/fastapi-practices/fastapi_best_architecture/pull/856)
* Fix ctx in validation exception handler by [@wu-clan](https://github.com/wu-clan) in [#861](https://github.com/fastapi-practices/fastapi_best_architecture/pull/861)
* Update the primary database to postgresql by [@wu-clan](https://github.com/wu-clan) in [#829](https://github.com/fastapi-practices/fastapi_best_architecture/pull/829)
**Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.9.0...v1.10.0
## Contributors
<a href="https://github.com/wu-clan"><img src="https://wsrv.nl/?url=https%3A%2F%2Fgithub.com%2Fwu-clan.png&w=128&h=128&fit=cover&mask=circle" width="64" height="64" alt="@wu-clan"></a>
[Changes][v1.10.0]
<a id="v1.9.0"></a>
# [v1.9.0](https://github.com/fastapi-practices/fastapi_best_architecture/releases/tag/v1.9.0) - 2025-10-16
## What's Changed
* Update changelog for v1.8.3 by [@wu-clan](https://github.com/wu-clan) in [#849](https://github.com/fastapi-practices/fastapi_best_architecture/pull/849)
* Bump fastapi and pydantic to the latest by [@wu-clan](https://github.com/wu-clan) in [#851](https://github.com/fastapi-practices/fastapi_best_architecture/pull/851)
* Update superuser verify to dependency injection by [@wu-clan](https://github.com/wu-clan) in [#852](https://github.com/fastapi-practices/fastapi_best_architecture/pull/852)
* Fix the superuser verify missing auth by [@wu-clan](https://github.com/wu-clan) in [#854](https://github.com/fastapi-practices/fastapi_best_architecture/pull/854)
* Update request state usage to context variable by [@wu-clan](https://github.com/wu-clan) in [#853](https://github.com/fastapi-practices/fastapi_best_architecture/pull/853)
* Fix safely of access ctx in exception handlers by [@wu-clan](https://github.com/wu-clan) in [#855](https://github.com/fastapi-practices/fastapi_best_architecture/pull/855)
* Refactor the service layer db session call method by [@wu-clan](https://github.com/wu-clan) in [#850](https://github.com/fastapi-practices/fastapi_best_architecture/pull/850)
**Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.8.3...v1.9.0
## Contributors
<a href="https://github.com/wu-clan"><img src="https://wsrv.nl/?url=https%3A%2F%2Fgithub.com%2Fwu-clan.png&w=128&h=128&fit=cover&mask=circle" width="64" height="64" alt="@wu-clan"></a>
[Changes][v1.9.0]
<a id="v1.8.3"></a> <a id="v1.8.3"></a>
# [v1.8.3](https://github.com/fastapi-practices/fastapi_best_architecture/releases/tag/v1.8.3) - 2025-10-13 # [v1.8.3](https://github.com/fastapi-practices/fastapi_best_architecture/releases/tag/v1.8.3) - 2025-10-13
@@ -13,6 +77,10 @@
**Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.8.2...v1.8.3 **Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.8.2...v1.8.3
## Contributors
<a href="https://github.com/wu-clan"><img src="https://wsrv.nl/?url=https%3A%2F%2Fgithub.com%2Fwu-clan.png&w=128&h=128&fit=cover&mask=circle" width="64" height="64" alt="@wu-clan"></a>
[Changes][v1.8.3] [Changes][v1.8.3]
@@ -48,6 +116,12 @@
**Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.8.1...v1.8.2 **Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.8.1...v1.8.2
## Contributors
<a href="https://github.com/MortyZhaoy"><img src="https://wsrv.nl/?url=https%3A%2F%2Fgithub.com%2FMortyZhaoy.png&w=128&h=128&fit=cover&mask=circle" width="64" height="64" alt="@MortyZhaoy"></a>
<a href="https://github.com/byte-voyager"><img src="https://wsrv.nl/?url=https%3A%2F%2Fgithub.com%2Fbyte-voyager.png&w=128&h=128&fit=cover&mask=circle" width="64" height="64" alt="@byte-voyager"></a>
<a href="https://github.com/wu-clan"><img src="https://wsrv.nl/?url=https%3A%2F%2Fgithub.com%2Fwu-clan.png&w=128&h=128&fit=cover&mask=circle" width="64" height="64" alt="@wu-clan"></a>
[Changes][v1.8.2] [Changes][v1.8.2]
@@ -92,6 +166,15 @@
**Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.8.0...v1.8.1 **Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.8.0...v1.8.1
## Contributors
<a href="https://github.com/IAseven"><img src="https://wsrv.nl/?url=https%3A%2F%2Fgithub.com%2FIAseven.png&w=128&h=128&fit=cover&mask=circle" width="64" height="64" alt="@IAseven"></a>
<a href="https://github.com/siyue-wang"><img src="https://wsrv.nl/?url=https%3A%2F%2Fgithub.com%2Fsiyue-wang.png&w=128&h=128&fit=cover&mask=circle" width="64" height="64" alt="@siyue-wang"></a>
<a href="https://github.com/wu-clan"><img src="https://wsrv.nl/?url=https%3A%2F%2Fgithub.com%2Fwu-clan.png&w=128&h=128&fit=cover&mask=circle" width="64" height="64" alt="@wu-clan"></a>
<a href="https://github.com/wuyao4"><img src="https://wsrv.nl/?url=https%3A%2F%2Fgithub.com%2Fwuyao4.png&w=128&h=128&fit=cover&mask=circle" width="64" height="64" alt="@wuyao4"></a>
<a href="https://github.com/yanlingsishao"><img src="https://wsrv.nl/?url=https%3A%2F%2Fgithub.com%2Fyanlingsishao.png&w=128&h=128&fit=cover&mask=circle" width="64" height="64" alt="@yanlingsishao"></a>
<a href="https://github.com/yzbf-lin"><img src="https://wsrv.nl/?url=https%3A%2F%2Fgithub.com%2Fyzbf-lin.png&w=128&h=128&fit=cover&mask=circle" width="64" height="64" alt="@yzbf-lin"></a>
[Changes][v1.8.1] [Changes][v1.8.1]
@@ -136,6 +219,12 @@
**Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.7.0...v1.8.0 **Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.7.0...v1.8.0
## Contributors
<a href="https://github.com/IAseven"><img src="https://wsrv.nl/?url=https%3A%2F%2Fgithub.com%2FIAseven.png&w=128&h=128&fit=cover&mask=circle" width="64" height="64" alt="@IAseven"></a>
<a href="https://github.com/downdawn"><img src="https://wsrv.nl/?url=https%3A%2F%2Fgithub.com%2Fdowndawn.png&w=128&h=128&fit=cover&mask=circle" width="64" height="64" alt="@downdawn"></a>
<a href="https://github.com/wu-clan"><img src="https://wsrv.nl/?url=https%3A%2F%2Fgithub.com%2Fwu-clan.png&w=128&h=128&fit=cover&mask=circle" width="64" height="64" alt="@wu-clan"></a>
[Changes][v1.8.0] [Changes][v1.8.0]
@@ -171,6 +260,10 @@
**Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.6.0...v1.7.0 **Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.6.0...v1.7.0
## Contributors
<a href="https://github.com/wu-clan"><img src="https://wsrv.nl/?url=https%3A%2F%2Fgithub.com%2Fwu-clan.png&w=128&h=128&fit=cover&mask=circle" width="64" height="64" alt="@wu-clan"></a>
[Changes][v1.7.0] [Changes][v1.7.0]
@@ -194,6 +287,10 @@
**Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.5.2...v1.6.0 **Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.5.2...v1.6.0
## Contributors
<a href="https://github.com/wu-clan"><img src="https://wsrv.nl/?url=https%3A%2F%2Fgithub.com%2Fwu-clan.png&w=128&h=128&fit=cover&mask=circle" width="64" height="64" alt="@wu-clan"></a>
[Changes][v1.6.0] [Changes][v1.6.0]
@@ -220,6 +317,11 @@
**Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.5.1...v1.5.2 **Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.5.1...v1.5.2
## Contributors
<a href="https://github.com/downdawn"><img src="https://wsrv.nl/?url=https%3A%2F%2Fgithub.com%2Fdowndawn.png&w=128&h=128&fit=cover&mask=circle" width="64" height="64" alt="@downdawn"></a>
<a href="https://github.com/wu-clan"><img src="https://wsrv.nl/?url=https%3A%2F%2Fgithub.com%2Fwu-clan.png&w=128&h=128&fit=cover&mask=circle" width="64" height="64" alt="@wu-clan"></a>
[Changes][v1.5.2] [Changes][v1.5.2]
@@ -236,6 +338,11 @@
**Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.5.0...v1.5.1 **Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.5.0...v1.5.1
## Contributors
<a href="https://github.com/downdawn"><img src="https://wsrv.nl/?url=https%3A%2F%2Fgithub.com%2Fdowndawn.png&w=128&h=128&fit=cover&mask=circle" width="64" height="64" alt="@downdawn"></a>
<a href="https://github.com/wu-clan"><img src="https://wsrv.nl/?url=https%3A%2F%2Fgithub.com%2Fwu-clan.png&w=128&h=128&fit=cover&mask=circle" width="64" height="64" alt="@wu-clan"></a>
[Changes][v1.5.1] [Changes][v1.5.1]
@@ -259,6 +366,10 @@
**Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.4.3...v1.5.0 **Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.4.3...v1.5.0
## Contributors
<a href="https://github.com/wu-clan"><img src="https://wsrv.nl/?url=https%3A%2F%2Fgithub.com%2Fwu-clan.png&w=128&h=128&fit=cover&mask=circle" width="64" height="64" alt="@wu-clan"></a>
[Changes][v1.5.0] [Changes][v1.5.0]
@@ -280,6 +391,10 @@
**Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.4.2...v1.4.3 **Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.4.2...v1.4.3
## Contributors
<a href="https://github.com/wu-clan"><img src="https://wsrv.nl/?url=https%3A%2F%2Fgithub.com%2Fwu-clan.png&w=128&h=128&fit=cover&mask=circle" width="64" height="64" alt="@wu-clan"></a>
[Changes][v1.4.3] [Changes][v1.4.3]
@@ -301,6 +416,11 @@
**Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.4.1...v1.4.2 **Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.4.1...v1.4.2
## Contributors
<a href="https://github.com/huyuwei1996"><img src="https://wsrv.nl/?url=https%3A%2F%2Fgithub.com%2Fhuyuwei1996.png&w=128&h=128&fit=cover&mask=circle" width="64" height="64" alt="@huyuwei1996"></a>
<a href="https://github.com/wu-clan"><img src="https://wsrv.nl/?url=https%3A%2F%2Fgithub.com%2Fwu-clan.png&w=128&h=128&fit=cover&mask=circle" width="64" height="64" alt="@wu-clan"></a>
[Changes][v1.4.2] [Changes][v1.4.2]
@@ -318,6 +438,10 @@
**Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.4.0...v1.4.1 **Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.4.0...v1.4.1
## Contributors
<a href="https://github.com/wu-clan"><img src="https://wsrv.nl/?url=https%3A%2F%2Fgithub.com%2Fwu-clan.png&w=128&h=128&fit=cover&mask=circle" width="64" height="64" alt="@wu-clan"></a>
[Changes][v1.4.1] [Changes][v1.4.1]
@@ -339,6 +463,10 @@
**Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.3.0...v1.4.0 **Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.3.0...v1.4.0
## Contributors
<a href="https://github.com/wu-clan"><img src="https://wsrv.nl/?url=https%3A%2F%2Fgithub.com%2Fwu-clan.png&w=128&h=128&fit=cover&mask=circle" width="64" height="64" alt="@wu-clan"></a>
[Changes][v1.4.0] [Changes][v1.4.0]
@@ -356,6 +484,10 @@
**Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.2.0...v1.3.0 **Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.2.0...v1.3.0
## Contributors
<a href="https://github.com/wu-clan"><img src="https://wsrv.nl/?url=https%3A%2F%2Fgithub.com%2Fwu-clan.png&w=128&h=128&fit=cover&mask=circle" width="64" height="64" alt="@wu-clan"></a>
[Changes][v1.3.0] [Changes][v1.3.0]
@@ -373,6 +505,10 @@
**Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.1.2...v1.2.0 **Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.1.2...v1.2.0
## Contributors
<a href="https://github.com/wu-clan"><img src="https://wsrv.nl/?url=https%3A%2F%2Fgithub.com%2Fwu-clan.png&w=128&h=128&fit=cover&mask=circle" width="64" height="64" alt="@wu-clan"></a>
[Changes][v1.2.0] [Changes][v1.2.0]
@@ -390,6 +526,10 @@
**Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.1.1...v1.1.2 **Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.1.1...v1.1.2
## Contributors
<a href="https://github.com/wu-clan"><img src="https://wsrv.nl/?url=https%3A%2F%2Fgithub.com%2Fwu-clan.png&w=128&h=128&fit=cover&mask=circle" width="64" height="64" alt="@wu-clan"></a>
[Changes][v1.1.2] [Changes][v1.1.2]
@@ -404,6 +544,10 @@
**Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.1.0...v1.1.1 **Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.1.0...v1.1.1
## Contributors
<a href="https://github.com/wu-clan"><img src="https://wsrv.nl/?url=https%3A%2F%2Fgithub.com%2Fwu-clan.png&w=128&h=128&fit=cover&mask=circle" width="64" height="64" alt="@wu-clan"></a>
[Changes][v1.1.1] [Changes][v1.1.1]
@@ -423,6 +567,11 @@
**Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.0.5...v1.1.0 **Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.0.5...v1.1.0
## Contributors
<a href="https://github.com/huyuwei1996"><img src="https://wsrv.nl/?url=https%3A%2F%2Fgithub.com%2Fhuyuwei1996.png&w=128&h=128&fit=cover&mask=circle" width="64" height="64" alt="@huyuwei1996"></a>
<a href="https://github.com/wu-clan"><img src="https://wsrv.nl/?url=https%3A%2F%2Fgithub.com%2Fwu-clan.png&w=128&h=128&fit=cover&mask=circle" width="64" height="64" alt="@wu-clan"></a>
[Changes][v1.1.0] [Changes][v1.1.0]
@@ -447,6 +596,10 @@
**Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.0.4...v1.0.5 **Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.0.4...v1.0.5
## Contributors
<a href="https://github.com/wu-clan"><img src="https://wsrv.nl/?url=https%3A%2F%2Fgithub.com%2Fwu-clan.png&w=128&h=128&fit=cover&mask=circle" width="64" height="64" alt="@wu-clan"></a>
[Changes][v1.0.5] [Changes][v1.0.5]
@@ -475,6 +628,12 @@
**Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.0.3...v1.0.4 **Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.0.3...v1.0.4
## Contributors
<a href="https://github.com/PoetryL"><img src="https://wsrv.nl/?url=https%3A%2F%2Fgithub.com%2FPoetryL.png&w=128&h=128&fit=cover&mask=circle" width="64" height="64" alt="@PoetryL"></a>
<a href="https://github.com/ThankCat"><img src="https://wsrv.nl/?url=https%3A%2F%2Fgithub.com%2FThankCat.png&w=128&h=128&fit=cover&mask=circle" width="64" height="64" alt="@ThankCat"></a>
<a href="https://github.com/wu-clan"><img src="https://wsrv.nl/?url=https%3A%2F%2Fgithub.com%2Fwu-clan.png&w=128&h=128&fit=cover&mask=circle" width="64" height="64" alt="@wu-clan"></a>
[Changes][v1.0.4] [Changes][v1.0.4]
@@ -493,6 +652,11 @@
**Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.0.2...v1.0.3 **Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.0.2...v1.0.3
## Contributors
<a href="https://github.com/huyuwei1996"><img src="https://wsrv.nl/?url=https%3A%2F%2Fgithub.com%2Fhuyuwei1996.png&w=128&h=128&fit=cover&mask=circle" width="64" height="64" alt="@huyuwei1996"></a>
<a href="https://github.com/wu-clan"><img src="https://wsrv.nl/?url=https%3A%2F%2Fgithub.com%2Fwu-clan.png&w=128&h=128&fit=cover&mask=circle" width="64" height="64" alt="@wu-clan"></a>
[Changes][v1.0.3] [Changes][v1.0.3]
@@ -508,6 +672,10 @@
**Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.0.1...v1.0.2 **Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.0.1...v1.0.2
## Contributors
<a href="https://github.com/wu-clan"><img src="https://wsrv.nl/?url=https%3A%2F%2Fgithub.com%2Fwu-clan.png&w=128&h=128&fit=cover&mask=circle" width="64" height="64" alt="@wu-clan"></a>
[Changes][v1.0.2] [Changes][v1.0.2]
@@ -527,6 +695,11 @@
**Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.0.0...v1.0.1 **Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.0.0...v1.0.1
## Contributors
<a href="https://github.com/huyuwei1996"><img src="https://wsrv.nl/?url=https%3A%2F%2Fgithub.com%2Fhuyuwei1996.png&w=128&h=128&fit=cover&mask=circle" width="64" height="64" alt="@huyuwei1996"></a>
<a href="https://github.com/wu-clan"><img src="https://wsrv.nl/?url=https%3A%2F%2Fgithub.com%2Fwu-clan.png&w=128&h=128&fit=cover&mask=circle" width="64" height="64" alt="@wu-clan"></a>
[Changes][v1.0.1] [Changes][v1.0.1]
@@ -852,9 +1025,23 @@
**Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/commits/v1.0.0 **Full Changelog**: https://github.com/fastapi-practices/fastapi_best_architecture/commits/v1.0.0
## Contributors
<a href="https://github.com/Meepoljdx"><img src="https://wsrv.nl/?url=https%3A%2F%2Fgithub.com%2FMeepoljdx.png&w=128&h=128&fit=cover&mask=circle" width="64" height="64" alt="@Meepoljdx"></a>
<a href="https://github.com/dependabot"><img src="https://wsrv.nl/?url=https%3A%2F%2Fgithub.com%2Fdependabot.png&w=128&h=128&fit=cover&mask=circle" width="64" height="64" alt="@dependabot"></a>
<a href="https://github.com/dividduang"><img src="https://wsrv.nl/?url=https%3A%2F%2Fgithub.com%2Fdividduang.png&w=128&h=128&fit=cover&mask=circle" width="64" height="64" alt="@dividduang"></a>
<a href="https://github.com/downdawn"><img src="https://wsrv.nl/?url=https%3A%2F%2Fgithub.com%2Fdowndawn.png&w=128&h=128&fit=cover&mask=circle" width="64" height="64" alt="@downdawn"></a>
<a href="https://github.com/obrua"><img src="https://wsrv.nl/?url=https%3A%2F%2Fgithub.com%2Fobrua.png&w=128&h=128&fit=cover&mask=circle" width="64" height="64" alt="@obrua"></a>
<a href="https://github.com/qhp13654398483"><img src="https://wsrv.nl/?url=https%3A%2F%2Fgithub.com%2Fqhp13654398483.png&w=128&h=128&fit=cover&mask=circle" width="64" height="64" alt="@qhp13654398483"></a>
<a href="https://github.com/wu-clan"><img src="https://wsrv.nl/?url=https%3A%2F%2Fgithub.com%2Fwu-clan.png&w=128&h=128&fit=cover&mask=circle" width="64" height="64" alt="@wu-clan"></a>
<a href="https://github.com/yshan2028"><img src="https://wsrv.nl/?url=https%3A%2F%2Fgithub.com%2Fyshan2028.png&w=128&h=128&fit=cover&mask=circle" width="64" height="64" alt="@yshan2028"></a>
[Changes][v1.0.0] [Changes][v1.0.0]
[v1.10.1]: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.10.0...v1.10.1
[v1.10.0]: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.9.0...v1.10.0
[v1.9.0]: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.8.3...v1.9.0
[v1.8.3]: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.8.2...v1.8.3 [v1.8.3]: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.8.2...v1.8.3
[v1.8.2]: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.8.1...v1.8.2 [v1.8.2]: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.8.1...v1.8.2
[v1.8.1]: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.8.0...v1.8.1 [v1.8.1]: https://github.com/fastapi-practices/fastapi_best_architecture/compare/v1.8.0...v1.8.1
+3 -3
View File
@@ -1,10 +1,10 @@
# Env # Env
ENVIRONMENT='dev' ENVIRONMENT='dev'
# Database # Database
DATABASE_TYPE='mysql' DATABASE_TYPE='postgresql'
DATABASE_HOST='127.0.0.1' DATABASE_HOST='127.0.0.1'
DATABASE_PORT=3306 DATABASE_PORT=5432
DATABASE_USER='root' DATABASE_USER='postgres'
DATABASE_PASSWORD='123456' DATABASE_PASSWORD='123456'
# Redis # Redis
REDIS_HOST='127.0.0.1' REDIS_HOST='127.0.0.1'
+1 -1
View File
@@ -1,6 +1,6 @@
from backend.common.i18n import i18n from backend.common.i18n import i18n
__version__ = '1.8.2' __version__ = '1.10.2'
# 初始化 i18n # 初始化 i18n
-1
View File
@@ -12,7 +12,6 @@ def get_app_models() -> list[type]:
apps = [d for d in list_dirs if os.path.isdir(os.path.join(app_path, d)) and d != '__pycache__'] apps = [d for d in list_dirs if os.path.isdir(os.path.join(app_path, d)) and d != '__pycache__']
objs = [] objs = []
for app in apps: for app in apps:
module_path = f'backend.app.{app}.model' module_path = f'backend.app.{app}.model'
obj = get_model_objects(module_path) obj = get_model_objects(module_path)
+3 -3
View File
@@ -20,7 +20,7 @@ class CRUDDept(CRUDPlus[Dept]):
:param dept_id: 部门 ID :param dept_id: 部门 ID
:return: :return:
""" """
return await self.select_model_by_column(db, id=dept_id, del_flag=0) return await self.select_model_by_column(db, id=dept_id, del_flag=False)
async def get_by_name(self, db: AsyncSession, name: str) -> Dept | None: async def get_by_name(self, db: AsyncSession, name: str) -> Dept | None:
""" """
@@ -30,7 +30,7 @@ class CRUDDept(CRUDPlus[Dept]):
:param name: 部门名称 :param name: 部门名称
:return: :return:
""" """
return await self.select_model_by_column(db, name=name, del_flag=0) return await self.select_model_by_column(db, name=name, del_flag=False)
async def get_all( async def get_all(
self, self,
@@ -52,7 +52,7 @@ class CRUDDept(CRUDPlus[Dept]):
:param status: 部门状态 :param status: 部门状态
:return: :return:
""" """
filters = {'del_flag': 0} filters = {'del_flag': False}
if name is not None: if name is not None:
filters['name__like'] = f'%{name}%' filters['name__like'] = f'%{name}%'
+6 -5
View File
@@ -2,7 +2,8 @@ from __future__ import annotations
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from sqlalchemy import String import sqlalchemy as sa
from sqlalchemy.orm import Mapped, mapped_column, relationship from sqlalchemy.orm import Mapped, mapped_column, relationship
from backend.app.admin.model.m2m import sys_data_scope_rule from backend.app.admin.model.m2m import sys_data_scope_rule
@@ -18,14 +19,14 @@ class DataRule(Base):
__tablename__ = 'sys_data_rule' __tablename__ = 'sys_data_rule'
id: Mapped[id_key] = mapped_column(init=False) id: Mapped[id_key] = mapped_column(init=False)
name: Mapped[str] = mapped_column(String(500), unique=True, comment='名称') name: Mapped[str] = mapped_column(sa.String(500), unique=True, comment='名称')
model: Mapped[str] = mapped_column(String(50), comment='SQLA 模型名,对应 DATA_PERMISSION_MODELS 键名') model: Mapped[str] = mapped_column(sa.String(50), comment='SQLA 模型名,对应 DATA_PERMISSION_MODELS 键名')
column: Mapped[str] = mapped_column(String(20), comment='模型字段名') column: Mapped[str] = mapped_column(sa.String(20), comment='模型字段名')
operator: Mapped[int] = mapped_column(comment='运算符(0and、1or') operator: Mapped[int] = mapped_column(comment='运算符(0and、1or')
expression: Mapped[int] = mapped_column( expression: Mapped[int] = mapped_column(
comment='表达式(0==、1!=、2>、3>=、4<、5<=、6in、7not_in', comment='表达式(0==、1!=、2>、3>=、4<、5<=、6in、7not_in',
) )
value: Mapped[str] = mapped_column(String(255), comment='规则值') value: Mapped[str] = mapped_column(sa.String(255), comment='规则值')
# 数据范围规则多对多 # 数据范围规则多对多
scopes: Mapped[list[DataScope]] = relationship(init=False, secondary=sys_data_scope_rule, back_populates='rules') scopes: Mapped[list[DataScope]] = relationship(init=False, secondary=sys_data_scope_rule, back_populates='rules')
+3 -2
View File
@@ -2,7 +2,8 @@ from __future__ import annotations
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from sqlalchemy import String import sqlalchemy as sa
from sqlalchemy.orm import Mapped, mapped_column, relationship from sqlalchemy.orm import Mapped, mapped_column, relationship
from backend.app.admin.model.m2m import sys_data_scope_rule, sys_role_data_scope from backend.app.admin.model.m2m import sys_data_scope_rule, sys_role_data_scope
@@ -18,7 +19,7 @@ class DataScope(Base):
__tablename__ = 'sys_data_scope' __tablename__ = 'sys_data_scope'
id: Mapped[id_key] = mapped_column(init=False) id: Mapped[id_key] = mapped_column(init=False)
name: Mapped[str] = mapped_column(String(50), unique=True, comment='名称') name: Mapped[str] = mapped_column(sa.String(50), unique=True, comment='名称')
status: Mapped[int] = mapped_column(default=1, comment='状态(0停用 1正常)') status: Mapped[int] = mapped_column(default=1, comment='状态(0停用 1正常)')
# 数据范围规则多对多 # 数据范围规则多对多
+8 -16
View File
@@ -2,8 +2,8 @@ from __future__ import annotations
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from sqlalchemy import BigInteger, Boolean, ForeignKey, String import sqlalchemy as sa
from sqlalchemy.dialects.postgresql import INTEGER
from sqlalchemy.orm import Mapped, mapped_column, relationship from sqlalchemy.orm import Mapped, mapped_column, relationship
from backend.common.model import Base, id_key from backend.common.model import Base, id_key
@@ -18,25 +18,17 @@ class Dept(Base):
__tablename__ = 'sys_dept' __tablename__ = 'sys_dept'
id: Mapped[id_key] = mapped_column(init=False) id: Mapped[id_key] = mapped_column(init=False)
name: Mapped[str] = mapped_column(String(50), comment='部门名称') name: Mapped[str] = mapped_column(sa.String(50), comment='部门名称')
sort: Mapped[int] = mapped_column(default=0, comment='排序') sort: Mapped[int] = mapped_column(default=0, comment='排序')
leader: Mapped[str | None] = mapped_column(String(20), default=None, comment='负责人') leader: Mapped[str | None] = mapped_column(sa.String(20), default=None, comment='负责人')
phone: Mapped[str | None] = mapped_column(String(11), default=None, comment='手机') phone: Mapped[str | None] = mapped_column(sa.String(11), default=None, comment='手机')
email: Mapped[str | None] = mapped_column(String(50), default=None, comment='邮箱') email: Mapped[str | None] = mapped_column(sa.String(50), default=None, comment='邮箱')
status: Mapped[int] = mapped_column(default=1, comment='部门状态(0停用 1正常)') status: Mapped[int] = mapped_column(default=1, comment='部门状态(0停用 1正常)')
del_flag: Mapped[bool] = mapped_column( del_flag: Mapped[bool] = mapped_column(default=False, comment='删除标志(0删除 1存在)')
Boolean().with_variant(INTEGER, 'postgresql'),
default=False,
comment='删除标志(0删除 1存在)',
)
# 父级部门一对多 # 父级部门一对多
parent_id: Mapped[int | None] = mapped_column( parent_id: Mapped[int | None] = mapped_column(
BigInteger, sa.BigInteger, sa.ForeignKey('sys_dept.id', ondelete='SET NULL'), default=None, index=True, comment='父部门ID'
ForeignKey('sys_dept.id', ondelete='SET NULL'),
default=None,
index=True,
comment='父部门ID',
) )
parent: Mapped[Dept | None] = relationship(init=False, back_populates='children', remote_side=[id]) parent: Mapped[Dept | None] = relationship(init=False, back_populates='children', remote_side=[id])
children: Mapped[list[Dept] | None] = relationship(init=False, back_populates='parent') children: Mapped[list[Dept] | None] = relationship(init=False, back_populates='parent')
+14 -15
View File
@@ -1,11 +1,10 @@
from datetime import datetime from datetime import datetime
from sqlalchemy import String import sqlalchemy as sa
from sqlalchemy.dialects.mysql import LONGTEXT
from sqlalchemy.dialects.postgresql import TEXT
from sqlalchemy.orm import Mapped, mapped_column from sqlalchemy.orm import Mapped, mapped_column
from backend.common.model import DataClassBase, TimeZone, id_key from backend.common.model import DataClassBase, TimeZone, UniversalText, id_key
from backend.utils.timezone import timezone from backend.utils.timezone import timezone
@@ -15,18 +14,18 @@ class LoginLog(DataClassBase):
__tablename__ = 'sys_login_log' __tablename__ = 'sys_login_log'
id: Mapped[id_key] = mapped_column(init=False) id: Mapped[id_key] = mapped_column(init=False)
user_uuid: Mapped[str] = mapped_column(String(50), comment='用户UUID') user_uuid: Mapped[str] = mapped_column(sa.String(50), comment='用户UUID')
username: Mapped[str] = mapped_column(String(20), comment='用户名') username: Mapped[str] = mapped_column(sa.String(20), comment='用户名')
status: Mapped[int] = mapped_column(insert_default=0, comment='登录状态(0失败 1成功)') status: Mapped[int] = mapped_column(insert_default=0, comment='登录状态(0失败 1成功)')
ip: Mapped[str] = mapped_column(String(50), comment='登录IP地址') ip: Mapped[str] = mapped_column(sa.String(50), comment='登录IP地址')
country: Mapped[str | None] = mapped_column(String(50), comment='国家') country: Mapped[str | None] = mapped_column(sa.String(50), comment='国家')
region: Mapped[str | None] = mapped_column(String(50), comment='地区') region: Mapped[str | None] = mapped_column(sa.String(50), comment='地区')
city: Mapped[str | None] = mapped_column(String(50), comment='城市') city: Mapped[str | None] = mapped_column(sa.String(50), comment='城市')
user_agent: Mapped[str] = mapped_column(String(255), comment='请求头') user_agent: Mapped[str] = mapped_column(sa.String(255), comment='请求头')
os: Mapped[str | None] = mapped_column(String(50), comment='操作系统') os: Mapped[str | None] = mapped_column(sa.String(50), comment='操作系统')
browser: Mapped[str | None] = mapped_column(String(50), comment='浏览器') browser: Mapped[str | None] = mapped_column(sa.String(50), comment='浏览器')
device: Mapped[str | None] = mapped_column(String(50), comment='设备') device: Mapped[str | None] = mapped_column(sa.String(50), comment='设备')
msg: Mapped[str] = mapped_column(LONGTEXT().with_variant(TEXT, 'postgresql'), comment='提示消息') msg: Mapped[str] = mapped_column(UniversalText, comment='提示消息')
login_time: Mapped[datetime] = mapped_column(TimeZone, comment='登录时间') login_time: Mapped[datetime] = mapped_column(TimeZone, comment='登录时间')
created_time: Mapped[datetime] = mapped_column( created_time: Mapped[datetime] = mapped_column(
TimeZone, TimeZone,
+33 -23
View File
@@ -1,52 +1,62 @@
from sqlalchemy import BigInteger, Column, ForeignKey, Table import sqlalchemy as sa
from backend.common.model import MappedBase from backend.common.model import MappedBase
sys_user_role = Table( sys_user_role = sa.Table(
'sys_user_role', 'sys_user_role',
MappedBase.metadata, MappedBase.metadata,
Column('id', BigInteger, primary_key=True, unique=True, index=True, autoincrement=True, comment='主键ID'), sa.Column('id', sa.BigInteger, primary_key=True, unique=True, index=True, autoincrement=True, comment='主键ID'),
Column('user_id', BigInteger, ForeignKey('sys_user.id', ondelete='CASCADE'), primary_key=True, comment='用户ID'), sa.Column(
Column('role_id', BigInteger, ForeignKey('sys_role.id', ondelete='CASCADE'), primary_key=True, comment='角色ID'), 'user_id', sa.BigInteger, sa.ForeignKey('sys_user.id', ondelete='CASCADE'), primary_key=True, comment='用户ID'
),
sa.Column(
'role_id', sa.BigInteger, sa.ForeignKey('sys_role.id', ondelete='CASCADE'), primary_key=True, comment='角色ID'
),
) )
sys_role_menu = Table( sys_role_menu = sa.Table(
'sys_role_menu', 'sys_role_menu',
MappedBase.metadata, MappedBase.metadata,
Column('id', BigInteger, primary_key=True, unique=True, index=True, autoincrement=True, comment='主键ID'), sa.Column('id', sa.BigInteger, primary_key=True, unique=True, index=True, autoincrement=True, comment='主键ID'),
Column('role_id', BigInteger, ForeignKey('sys_role.id', ondelete='CASCADE'), primary_key=True, comment='角色ID'), sa.Column(
Column('menu_id', BigInteger, ForeignKey('sys_menu.id', ondelete='CASCADE'), primary_key=True, comment='菜单ID'), 'role_id', sa.BigInteger, sa.ForeignKey('sys_role.id', ondelete='CASCADE'), primary_key=True, comment='角色ID'
),
sa.Column(
'menu_id', sa.BigInteger, sa.ForeignKey('sys_menu.id', ondelete='CASCADE'), primary_key=True, comment='菜单ID'
),
) )
sys_role_data_scope = Table( sys_role_data_scope = sa.Table(
'sys_role_data_scope', 'sys_role_data_scope',
MappedBase.metadata, MappedBase.metadata,
Column('id', BigInteger, primary_key=True, unique=True, index=True, autoincrement=True, comment='主键 ID'), sa.Column('id', sa.BigInteger, primary_key=True, unique=True, index=True, autoincrement=True, comment='主键 ID'),
Column('role_id', BigInteger, ForeignKey('sys_role.id', ondelete='CASCADE'), primary_key=True, comment='角色 ID'), sa.Column(
Column( 'role_id', sa.BigInteger, sa.ForeignKey('sys_role.id', ondelete='CASCADE'), primary_key=True, comment='角色 ID'
),
sa.Column(
'data_scope_id', 'data_scope_id',
BigInteger, sa.BigInteger,
ForeignKey('sys_data_scope.id', ondelete='CASCADE'), sa.ForeignKey('sys_data_scope.id', ondelete='CASCADE'),
primary_key=True, primary_key=True,
comment='数据范围 ID', comment='数据范围 ID',
), ),
) )
sys_data_scope_rule = Table( sys_data_scope_rule = sa.Table(
'sys_data_scope_rule', 'sys_data_scope_rule',
MappedBase.metadata, MappedBase.metadata,
Column('id', BigInteger, primary_key=True, unique=True, index=True, autoincrement=True, comment='主键ID'), sa.Column('id', sa.BigInteger, primary_key=True, unique=True, index=True, autoincrement=True, comment='主键ID'),
Column( sa.Column(
'data_scope_id', 'data_scope_id',
BigInteger, sa.BigInteger,
ForeignKey('sys_data_scope.id', ondelete='CASCADE'), sa.ForeignKey('sys_data_scope.id', ondelete='CASCADE'),
primary_key=True, primary_key=True,
comment='数据范围 ID', comment='数据范围 ID',
), ),
Column( sa.Column(
'data_rule_id', 'data_rule_id',
BigInteger, sa.BigInteger,
ForeignKey('sys_data_rule.id', ondelete='CASCADE'), sa.ForeignKey('sys_data_rule.id', ondelete='CASCADE'),
primary_key=True, primary_key=True,
comment='数据规则 ID', comment='数据规则 ID',
), ),
+12 -25
View File
@@ -2,13 +2,12 @@ from __future__ import annotations
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from sqlalchemy import BigInteger, ForeignKey, String import sqlalchemy as sa
from sqlalchemy.dialects.mysql import LONGTEXT
from sqlalchemy.dialects.postgresql import TEXT
from sqlalchemy.orm import Mapped, mapped_column, relationship from sqlalchemy.orm import Mapped, mapped_column, relationship
from backend.app.admin.model.m2m import sys_role_menu from backend.app.admin.model.m2m import sys_role_menu
from backend.common.model import Base, id_key from backend.common.model import Base, UniversalText, id_key
if TYPE_CHECKING: if TYPE_CHECKING:
from backend.app.admin.model import Role from backend.app.admin.model import Role
@@ -20,35 +19,23 @@ class Menu(Base):
__tablename__ = 'sys_menu' __tablename__ = 'sys_menu'
id: Mapped[id_key] = mapped_column(init=False) id: Mapped[id_key] = mapped_column(init=False)
title: Mapped[str] = mapped_column(String(50), comment='菜单标题') title: Mapped[str] = mapped_column(sa.String(50), comment='菜单标题')
name: Mapped[str] = mapped_column(String(50), comment='菜单名称') name: Mapped[str] = mapped_column(sa.String(50), comment='菜单名称')
path: Mapped[str | None] = mapped_column(String(200), comment='路由地址') path: Mapped[str | None] = mapped_column(sa.String(200), comment='路由地址')
sort: Mapped[int] = mapped_column(default=0, comment='排序') sort: Mapped[int] = mapped_column(default=0, comment='排序')
icon: Mapped[str | None] = mapped_column(String(100), default=None, comment='菜单图标') icon: Mapped[str | None] = mapped_column(sa.String(100), default=None, comment='菜单图标')
type: Mapped[int] = mapped_column(default=0, comment='菜单类型(0目录 1菜单 2按钮 3内嵌 4外链)') type: Mapped[int] = mapped_column(default=0, comment='菜单类型(0目录 1菜单 2按钮 3内嵌 4外链)')
component: Mapped[str | None] = mapped_column(String(255), default=None, comment='组件路径') component: Mapped[str | None] = mapped_column(sa.String(255), default=None, comment='组件路径')
perms: Mapped[str | None] = mapped_column(String(100), default=None, comment='权限标识') perms: Mapped[str | None] = mapped_column(sa.String(100), default=None, comment='权限标识')
status: Mapped[int] = mapped_column(default=1, comment='菜单状态(0停用 1正常)') status: Mapped[int] = mapped_column(default=1, comment='菜单状态(0停用 1正常)')
display: Mapped[int] = mapped_column(default=1, comment='是否显示(0否 1是)') display: Mapped[int] = mapped_column(default=1, comment='是否显示(0否 1是)')
cache: Mapped[int] = mapped_column(default=1, comment='是否缓存(0否 1是)') cache: Mapped[int] = mapped_column(default=1, comment='是否缓存(0否 1是)')
link: Mapped[str | None] = mapped_column( link: Mapped[str | None] = mapped_column(UniversalText, default=None, comment='外链地址')
LONGTEXT().with_variant(TEXT, 'postgresql'), remark: Mapped[str | None] = mapped_column(UniversalText, default=None, comment='备注')
default=None,
comment='外链地址',
)
remark: Mapped[str | None] = mapped_column(
LONGTEXT().with_variant(TEXT, 'postgresql'),
default=None,
comment='备注',
)
# 父级菜单一对多 # 父级菜单一对多
parent_id: Mapped[int | None] = mapped_column( parent_id: Mapped[int | None] = mapped_column(
BigInteger, sa.BigInteger, sa.ForeignKey('sys_menu.id', ondelete='SET NULL'), default=None, index=True, comment='父菜单ID'
ForeignKey('sys_menu.id', ondelete='SET NULL'),
default=None,
index=True,
comment='父菜单ID',
) )
parent: Mapped[Menu | None] = relationship(init=False, back_populates='children', remote_side=[id]) parent: Mapped[Menu | None] = relationship(init=False, back_populates='children', remote_side=[id])
children: Mapped[list[Menu] | None] = relationship(init=False, back_populates='parent') children: Mapped[list[Menu] | None] = relationship(init=False, back_populates='parent')
+20 -24
View File
@@ -1,11 +1,10 @@
from datetime import datetime from datetime import datetime
from sqlalchemy import String import sqlalchemy as sa
from sqlalchemy.dialects.mysql import JSON, LONGTEXT
from sqlalchemy.dialects.postgresql import TEXT
from sqlalchemy.orm import Mapped, mapped_column from sqlalchemy.orm import Mapped, mapped_column
from backend.common.model import DataClassBase, TimeZone, id_key from backend.common.model import DataClassBase, TimeZone, UniversalText, id_key
from backend.utils.timezone import timezone from backend.utils.timezone import timezone
@@ -15,28 +14,25 @@ class OperaLog(DataClassBase):
__tablename__ = 'sys_opera_log' __tablename__ = 'sys_opera_log'
id: Mapped[id_key] = mapped_column(init=False) id: Mapped[id_key] = mapped_column(init=False)
trace_id: Mapped[str] = mapped_column(String(32), comment='请求跟踪 ID') trace_id: Mapped[str] = mapped_column(sa.String(32), comment='请求跟踪 ID')
username: Mapped[str | None] = mapped_column(String(20), comment='用户名') username: Mapped[str | None] = mapped_column(sa.String(20), comment='用户名')
method: Mapped[str] = mapped_column(String(20), comment='请求类型') method: Mapped[str] = mapped_column(sa.String(20), comment='请求类型')
title: Mapped[str] = mapped_column(String(255), comment='操作模块') title: Mapped[str] = mapped_column(sa.String(255), comment='操作模块')
path: Mapped[str] = mapped_column(String(500), comment='请求路径') path: Mapped[str] = mapped_column(sa.String(500), comment='请求路径')
ip: Mapped[str] = mapped_column(String(50), comment='IP地址') ip: Mapped[str] = mapped_column(sa.String(50), comment='IP地址')
country: Mapped[str | None] = mapped_column(String(50), comment='国家') country: Mapped[str | None] = mapped_column(sa.String(50), comment='国家')
region: Mapped[str | None] = mapped_column(String(50), comment='地区') region: Mapped[str | None] = mapped_column(sa.String(50), comment='地区')
city: Mapped[str | None] = mapped_column(String(50), comment='城市') city: Mapped[str | None] = mapped_column(sa.String(50), comment='城市')
user_agent: Mapped[str] = mapped_column(LONGTEXT().with_variant(TEXT, 'postgresql'), comment='请求头') user_agent: Mapped[str] = mapped_column(sa.String(255), comment='请求头')
os: Mapped[str | None] = mapped_column(String(50), comment='操作系统') os: Mapped[str | None] = mapped_column(sa.String(50), comment='操作系统')
browser: Mapped[str | None] = mapped_column(String(50), comment='浏览器') browser: Mapped[str | None] = mapped_column(sa.String(50), comment='浏览器')
device: Mapped[str | None] = mapped_column(String(50), comment='设备') device: Mapped[str | None] = mapped_column(sa.String(50), comment='设备')
args: Mapped[str | None] = mapped_column(JSON(), comment='请求参数') args: Mapped[str | None] = mapped_column(sa.JSON(), comment='请求参数')
status: Mapped[int] = mapped_column(comment='操作状态(0异常 1正常)') status: Mapped[int] = mapped_column(comment='操作状态(0异常 1正常)')
code: Mapped[str] = mapped_column(String(20), insert_default='200', comment='操作状态码') code: Mapped[str] = mapped_column(sa.String(20), insert_default='200', comment='操作状态码')
msg: Mapped[str | None] = mapped_column(LONGTEXT().with_variant(TEXT, 'postgresql'), comment='提示消息') msg: Mapped[str | None] = mapped_column(UniversalText, comment='提示消息')
cost_time: Mapped[float] = mapped_column(insert_default=0.0, comment='请求耗时(ms') cost_time: Mapped[float] = mapped_column(insert_default=0.0, comment='请求耗时(ms')
opera_time: Mapped[datetime] = mapped_column(TimeZone, comment='操作时间') opera_time: Mapped[datetime] = mapped_column(TimeZone, comment='操作时间')
created_time: Mapped[datetime] = mapped_column( created_time: Mapped[datetime] = mapped_column(
TimeZone, TimeZone, init=False, default_factory=timezone.now, comment='创建时间'
init=False,
default_factory=timezone.now,
comment='创建时间',
) )
+6 -15
View File
@@ -2,13 +2,12 @@ from __future__ import annotations
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from sqlalchemy import Boolean, String import sqlalchemy as sa
from sqlalchemy.dialects.mysql import LONGTEXT
from sqlalchemy.dialects.postgresql import INTEGER, TEXT
from sqlalchemy.orm import Mapped, mapped_column, relationship from sqlalchemy.orm import Mapped, mapped_column, relationship
from backend.app.admin.model.m2m import sys_role_data_scope, sys_role_menu, sys_user_role from backend.app.admin.model.m2m import sys_role_data_scope, sys_role_menu, sys_user_role
from backend.common.model import Base, id_key from backend.common.model import Base, UniversalText, id_key
if TYPE_CHECKING: if TYPE_CHECKING:
from backend.app.admin.model import DataScope, Menu, User from backend.app.admin.model import DataScope, Menu, User
@@ -20,18 +19,10 @@ class Role(Base):
__tablename__ = 'sys_role' __tablename__ = 'sys_role'
id: Mapped[id_key] = mapped_column(init=False) id: Mapped[id_key] = mapped_column(init=False)
name: Mapped[str] = mapped_column(String(20), unique=True, comment='角色名称') name: Mapped[str] = mapped_column(sa.String(20), unique=True, comment='角色名称')
status: Mapped[int] = mapped_column(default=1, comment='角色状态(0停用 1正常)') status: Mapped[int] = mapped_column(default=1, comment='角色状态(0停用 1正常)')
is_filter_scopes: Mapped[bool] = mapped_column( is_filter_scopes: Mapped[bool] = mapped_column(default=True, comment='过滤数据权限(0否 1是)')
Boolean().with_variant(INTEGER, 'postgresql'), remark: Mapped[str | None] = mapped_column(UniversalText, default=None, comment='备注')
default=True,
comment='过滤数据权限(0否 1是)',
)
remark: Mapped[str | None] = mapped_column(
LONGTEXT().with_variant(TEXT, 'postgresql'),
default=None,
comment='备注',
)
# 角色用户多对多 # 角色用户多对多
users: Mapped[list[User]] = relationship(init=False, secondary=sys_user_role, back_populates='roles') users: Mapped[list[User]] = relationship(init=False, secondary=sys_user_role, back_populates='roles')
+15 -32
View File
@@ -3,8 +3,8 @@ from __future__ import annotations
from datetime import datetime from datetime import datetime
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from sqlalchemy import VARBINARY, Boolean, ForeignKey, String import sqlalchemy as sa
from sqlalchemy.dialects.postgresql import BYTEA, INTEGER
from sqlalchemy.orm import Mapped, mapped_column, relationship from sqlalchemy.orm import Mapped, mapped_column, relationship
from backend.app.admin.model.m2m import sys_user_role from backend.app.admin.model.m2m import sys_user_role
@@ -22,43 +22,26 @@ class User(Base):
__tablename__ = 'sys_user' __tablename__ = 'sys_user'
id: Mapped[id_key] = mapped_column(init=False) id: Mapped[id_key] = mapped_column(init=False)
uuid: Mapped[str] = mapped_column(String(50), init=False, default_factory=uuid4_str, unique=True) uuid: Mapped[str] = mapped_column(sa.String(50), init=False, default_factory=uuid4_str, unique=True)
username: Mapped[str] = mapped_column(String(20), unique=True, index=True, comment='用户名') username: Mapped[str] = mapped_column(sa.String(20), unique=True, index=True, comment='用户名')
nickname: Mapped[str] = mapped_column(String(20), comment='昵称') nickname: Mapped[str] = mapped_column(sa.String(20), comment='昵称')
password: Mapped[str | None] = mapped_column(String(255), comment='密码') password: Mapped[str | None] = mapped_column(sa.String(255), comment='密码')
salt: Mapped[bytes | None] = mapped_column(VARBINARY(255).with_variant(BYTEA(255), 'postgresql'), comment='加密盐') salt: Mapped[bytes | None] = mapped_column(sa.LargeBinary(255), comment='加密盐')
email: Mapped[str | None] = mapped_column(String(50), default=None, unique=True, index=True, comment='邮箱') email: Mapped[str | None] = mapped_column(sa.String(50), default=None, unique=True, index=True, comment='邮箱')
phone: Mapped[str | None] = mapped_column(String(11), default=None, comment='手机号') phone: Mapped[str | None] = mapped_column(sa.String(11), default=None, comment='手机号')
avatar: Mapped[str | None] = mapped_column(String(255), default=None, comment='头像') avatar: Mapped[str | None] = mapped_column(sa.String(255), default=None, comment='头像')
status: Mapped[int] = mapped_column(default=1, index=True, comment='用户账号状态(0停用 1正常)') status: Mapped[int] = mapped_column(default=1, index=True, comment='用户账号状态(0停用 1正常)')
is_superuser: Mapped[bool] = mapped_column( is_superuser: Mapped[bool] = mapped_column(default=False, comment='超级权限(0否 1是)')
Boolean().with_variant(INTEGER, 'postgresql'), is_staff: Mapped[bool] = mapped_column(default=False, comment='后台管理登陆(0否 1是)')
default=False, is_multi_login: Mapped[bool] = mapped_column(default=False, comment='是否重复登陆(0否 1是)')
comment='超级权限(0否 1是)',
)
is_staff: Mapped[bool] = mapped_column(
Boolean().with_variant(INTEGER, 'postgresql'),
default=False,
comment='后台管理登陆(0否 1是)',
)
is_multi_login: Mapped[bool] = mapped_column(
Boolean().with_variant(INTEGER, 'postgresql'),
default=False,
comment='是否重复登陆(0否 1是)',
)
join_time: Mapped[datetime] = mapped_column(TimeZone, init=False, default_factory=timezone.now, comment='注册时间') join_time: Mapped[datetime] = mapped_column(TimeZone, init=False, default_factory=timezone.now, comment='注册时间')
last_login_time: Mapped[datetime | None] = mapped_column( last_login_time: Mapped[datetime | None] = mapped_column(
TimeZone, TimeZone, init=False, onupdate=timezone.now, comment='上次登录'
init=False,
onupdate=timezone.now,
comment='上次登录',
) )
# 部门用户一对多 # 部门用户一对多
dept_id: Mapped[int | None] = mapped_column( dept_id: Mapped[int | None] = mapped_column(
ForeignKey('sys_dept.id', ondelete='SET NULL'), sa.BigInteger, sa.ForeignKey('sys_dept.id', ondelete='SET NULL'), default=None, comment='部门关联ID'
default=None,
comment='部门关联ID',
) )
dept: Mapped[Dept | None] = relationship(init=False, back_populates='users') dept: Mapped[Dept | None] = relationship(init=False, back_populates='users')
+16 -33
View File
@@ -2,18 +2,13 @@ import asyncio
from datetime import datetime from datetime import datetime
from sqlalchemy import ( import sqlalchemy as sa
JSON,
Boolean, from sqlalchemy import event
String,
event,
)
from sqlalchemy.dialects.mysql import LONGTEXT
from sqlalchemy.dialects.postgresql import INTEGER, TEXT
from sqlalchemy.orm import Mapped, mapped_column from sqlalchemy.orm import Mapped, mapped_column
from backend.common.exception import errors from backend.common.exception import errors
from backend.common.model import Base, TimeZone, id_key from backend.common.model import Base, TimeZone, UniversalText, id_key
from backend.core.conf import settings from backend.core.conf import settings
from backend.database.redis import redis_client from backend.database.redis import redis_client
from backend.utils.timezone import timezone from backend.utils.timezone import timezone
@@ -25,37 +20,25 @@ class TaskScheduler(Base):
__tablename__ = 'task_scheduler' __tablename__ = 'task_scheduler'
id: Mapped[id_key] = mapped_column(init=False) id: Mapped[id_key] = mapped_column(init=False)
name: Mapped[str] = mapped_column(String(50), unique=True, comment='任务名称') name: Mapped[str] = mapped_column(sa.String(50), unique=True, comment='任务名称')
task: Mapped[str] = mapped_column(String(255), comment='要运行的 Celery 任务') task: Mapped[str] = mapped_column(sa.String(255), comment='要运行的 Celery 任务')
args: Mapped[str | None] = mapped_column(JSON(), comment='任务可接收的位置参数') args: Mapped[str | None] = mapped_column(sa.JSON(), comment='任务可接收的位置参数')
kwargs: Mapped[str | None] = mapped_column(JSON(), comment='任务可接收的关键字参数') kwargs: Mapped[str | None] = mapped_column(sa.JSON(), comment='任务可接收的关键字参数')
queue: Mapped[str | None] = mapped_column(String(255), comment='CELERY_TASK_QUEUES 中定义的队列') queue: Mapped[str | None] = mapped_column(sa.String(255), comment='CELERY_TASK_QUEUES 中定义的队列')
exchange: Mapped[str | None] = mapped_column(String(255), comment='低级别 AMQP 路由的交换机') exchange: Mapped[str | None] = mapped_column(sa.String(255), comment='低级别 AMQP 路由的交换机')
routing_key: Mapped[str | None] = mapped_column(String(255), comment='低级别 AMQP 路由的路由密钥') routing_key: Mapped[str | None] = mapped_column(sa.String(255), comment='低级别 AMQP 路由的路由密钥')
start_time: Mapped[datetime | None] = mapped_column(TimeZone, comment='任务开始触发的时间') start_time: Mapped[datetime | None] = mapped_column(TimeZone, comment='任务开始触发的时间')
expire_time: Mapped[datetime | None] = mapped_column(TimeZone, comment='任务不再触发的截止时间') expire_time: Mapped[datetime | None] = mapped_column(TimeZone, comment='任务不再触发的截止时间')
expire_seconds: Mapped[int | None] = mapped_column(comment='任务不再触发的秒数时间差') expire_seconds: Mapped[int | None] = mapped_column(comment='任务不再触发的秒数时间差')
type: Mapped[int] = mapped_column(comment='调度类型(0间隔 1定时)') type: Mapped[int] = mapped_column(comment='调度类型(0间隔 1定时)')
interval_every: Mapped[int | None] = mapped_column(comment='任务再次运行前的间隔周期数') interval_every: Mapped[int | None] = mapped_column(comment='任务再次运行前的间隔周期数')
interval_period: Mapped[str | None] = mapped_column(String(255), comment='任务运行之间的周期类型') interval_period: Mapped[str | None] = mapped_column(sa.String(255), comment='任务运行之间的周期类型')
crontab: Mapped[str | None] = mapped_column(String(50), default='* * * * *', comment='任务运行的 Crontab 计划') crontab: Mapped[str | None] = mapped_column(sa.String(50), default='* * * * *', comment='任务运行的 Crontab 计划')
one_off: Mapped[bool] = mapped_column( one_off: Mapped[bool] = mapped_column(default=False, comment='是否仅运行一次')
Boolean().with_variant(INTEGER, 'postgresql'), enabled: Mapped[bool] = mapped_column(default=True, comment='是否启用任务')
default=False,
comment='是否仅运行一次',
)
enabled: Mapped[bool] = mapped_column(
Boolean().with_variant(INTEGER, 'postgresql'),
default=True,
comment='是否启用任务',
)
total_run_count: Mapped[int] = mapped_column(default=0, comment='任务触发的总次数') total_run_count: Mapped[int] = mapped_column(default=0, comment='任务触发的总次数')
last_run_time: Mapped[datetime | None] = mapped_column(TimeZone, default=None, comment='任务最后触发的时间') last_run_time: Mapped[datetime | None] = mapped_column(TimeZone, default=None, comment='任务最后触发的时间')
remark: Mapped[str | None] = mapped_column( remark: Mapped[str | None] = mapped_column(UniversalText, default=None, comment='备注')
LONGTEXT().with_variant(TEXT, 'postgresql'),
default=None,
comment='备注',
)
no_changes: bool = False no_changes: bool = False
+7 -4
View File
@@ -2,17 +2,20 @@ from celery import shared_task
from backend.app.admin.service.login_log_service import login_log_service from backend.app.admin.service.login_log_service import login_log_service
from backend.app.admin.service.opera_log_service import opera_log_service from backend.app.admin.service.opera_log_service import opera_log_service
from backend.database.db import async_db_session
@shared_task @shared_task
async def delete_db_opera_log() -> str: async def delete_db_opera_log() -> str:
"""自动删除数据库操作日志""" """自动删除数据库操作日志"""
await opera_log_service.delete_all() async with async_db_session.begin() as db:
return 'Success' await opera_log_service.delete_all(db=db)
return 'Success'
@shared_task @shared_task
async def delete_db_login_log() -> str: async def delete_db_login_log() -> str:
"""自动删除数据库登录日志""" """自动删除数据库登录日志"""
await login_log_service.delete_all() async with async_db_session.begin() as db:
return 'Success' await login_log_service.delete_all(db=db)
return 'Success'
+1 -1
View File
@@ -399,7 +399,7 @@ class DatabaseScheduler(Scheduler):
"""获取所有任务调度""" """获取所有任务调度"""
async with async_db_session() as db: async with async_db_session() as db:
logger.debug('DatabaseScheduler: Fetching database schedule') logger.debug('DatabaseScheduler: Fetching database schedule')
stmt = select(TaskScheduler).where(TaskScheduler.enabled == 1) stmt = select(TaskScheduler).where(TaskScheduler.enabled == True) # noqa: E712
query = await db.execute(stmt) query = await db.execute(stmt)
schedulers = query.scalars().all() schedulers = query.scalars().all()
s = {} s = {}
+2 -1
View File
@@ -145,7 +145,8 @@ async def import_table(
) -> None: ) -> None:
try: try:
obj = ImportParam(app=app, table_schema=table_schema, table_name=table_name) obj = ImportParam(app=app, table_schema=table_schema, table_name=table_name)
await gen_service.import_business_and_model(obj=obj) async with async_db_session.begin() as db:
await gen_service.import_business_and_model(db=db, obj=obj)
except Exception as e: except Exception as e:
raise cappa.Exit(e.msg if isinstance(e, BaseExceptionError) else str(e), code=1) raise cappa.Exit(e.msg if isinstance(e, BaseExceptionError) else str(e), code=1)
@@ -46,16 +46,14 @@ async def _validation_exception_handler(exc: RequestValidationError | Validation
if i18n.current_language != 'en-US': if i18n.current_language != 'en-US':
custom_message = t(f'pydantic.{error["type"]}') custom_message = t(f'pydantic.{error["type"]}')
if custom_message: if custom_message:
ctx = error.get('ctx') error_ctx = error.get('ctx')
if not ctx: if not error_ctx:
error['msg'] = custom_message error['msg'] = custom_message
else: else:
ctx_error = ctx.get('error') e = error_ctx.get('error')
if ctx_error: if e:
error['msg'] = custom_message.format(**ctx) error['msg'] = custom_message.format(**error_ctx)
error['ctx']['error'] = ( error['ctx']['error'] = e.__str__().replace("'", '"') if isinstance(e, Exception) else None
ctx_error.__str__().replace("'", '"') if isinstance(ctx_error, Exception) else None
)
errors.append(error) errors.append(error)
error = errors[0] error = errors[0]
if error.get('type') == 'json_invalid': if error.get('type') == 'json_invalid':
+22 -7
View File
@@ -1,10 +1,12 @@
from datetime import datetime from datetime import datetime
from typing import Annotated from typing import Annotated
from sqlalchemy import BigInteger, DateTime, TypeDecorator from sqlalchemy import BigInteger, DateTime, Text, TypeDecorator
from sqlalchemy.dialects.mysql import LONGTEXT
from sqlalchemy.ext.asyncio import AsyncAttrs from sqlalchemy.ext.asyncio import AsyncAttrs
from sqlalchemy.orm import DeclarativeBase, Mapped, MappedAsDataclass, declared_attr, mapped_column from sqlalchemy.orm import DeclarativeBase, Mapped, MappedAsDataclass, declared_attr, mapped_column
from backend.core.conf import settings
from backend.utils.snowflake import snowflake from backend.utils.snowflake import snowflake
from backend.utils.timezone import timezone from backend.utils.timezone import timezone
@@ -41,16 +43,21 @@ snowflake_id_key = Annotated[
] ]
# Mixin: 一种面向对象编程概念, 使结构变得更加清晰, `Wiki <https://en.wikipedia.org/wiki/Mixin/>`__ class UniversalText(TypeDecorator[str]):
class UserMixin(MappedAsDataclass): """PostgreSQL、MySQL 兼容性(长)文本类型"""
"""用户 Mixin 数据类"""
created_by: Mapped[int] = mapped_column(sort_order=998, comment='创建者') impl = LONGTEXT if settings.DATABASE_TYPE == 'mysql' else Text
updated_by: Mapped[int | None] = mapped_column(init=False, default=None, sort_order=998, comment='修改者') cache_ok = True
def process_bind_param(self, value: str | None, dialect) -> str | None: # noqa: ANN001
return value
def process_result_value(self, value: str | None, dialect) -> str | None: # noqa: ANN001
return value
class TimeZone(TypeDecorator[datetime]): class TimeZone(TypeDecorator[datetime]):
"""时区感知 DateTime""" """PostgreSQL、MySQL 兼容性时区感知类型"""
impl = DateTime(timezone=True) impl = DateTime(timezone=True)
cache_ok = True cache_ok = True
@@ -71,6 +78,14 @@ class TimeZone(TypeDecorator[datetime]):
return value return value
# Mixin: 一种面向对象编程概念, 使结构变得更加清晰, `Wiki <https://en.wikipedia.org/wiki/Mixin/>`__
class UserMixin(MappedAsDataclass):
"""用户 Mixin 数据类"""
created_by: Mapped[int] = mapped_column(sort_order=998, comment='创建者')
updated_by: Mapped[int | None] = mapped_column(init=False, default=None, sort_order=998, comment='修改者')
class DateTimeMixin(MappedAsDataclass): class DateTimeMixin(MappedAsDataclass):
"""日期时间 Mixin 数据类""" """日期时间 Mixin 数据类"""
+17 -18
View File
@@ -2,12 +2,11 @@ from __future__ import annotations
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from sqlalchemy import String import sqlalchemy as sa
from sqlalchemy.dialects.mysql import LONGTEXT
from sqlalchemy.dialects.postgresql import TEXT
from sqlalchemy.orm import Mapped, mapped_column, relationship from sqlalchemy.orm import Mapped, mapped_column, relationship
from backend.common.model import Base, id_key from backend.common.model import Base, UniversalText, id_key
if TYPE_CHECKING: if TYPE_CHECKING:
from backend.plugin.code_generator.model import GenColumn from backend.plugin.code_generator.model import GenColumn
@@ -19,21 +18,21 @@ class GenBusiness(Base):
__tablename__ = 'gen_business' __tablename__ = 'gen_business'
id: Mapped[id_key] = mapped_column(init=False) id: Mapped[id_key] = mapped_column(init=False)
app_name: Mapped[str] = mapped_column(String(50), comment='应用名称(英文)') app_name: Mapped[str] = mapped_column(sa.String(50), comment='应用名称(英文)')
table_name: Mapped[str] = mapped_column(String(255), unique=True, comment='表名称(英文)') table_name: Mapped[str] = mapped_column(sa.String(255), unique=True, comment='表名称(英文)')
doc_comment: Mapped[str] = mapped_column(String(255), comment='文档注释(用于函数/参数文档)') doc_comment: Mapped[str] = mapped_column(sa.String(255), comment='文档注释(用于函数/参数文档)')
table_comment: Mapped[str | None] = mapped_column(String(255), default=None, comment='表描述') table_comment: Mapped[str | None] = mapped_column(sa.String(255), default=None, comment='表描述')
# relate_model_fk: Mapped[int | None] = mapped_column(default=None, comment='关联表外键') # relate_model_fk: Mapped[int | None] = mapped_column(default=None, comment='关联表外键')
class_name: Mapped[str | None] = mapped_column(String(50), default=None, comment='基础类名(默认为英文表名称)') class_name: Mapped[str | None] = mapped_column(sa.String(50), default=None, comment='基础类名(默认为英文表名称)')
schema_name: Mapped[str | None] = mapped_column(String(50), default=None, comment='Schema 名称 (默认为英文表名称)') schema_name: Mapped[str | None] = mapped_column(
filename: Mapped[str | None] = mapped_column(String(50), default=None, comment='基础文件名(默认为英文表名称') sa.String(50), default=None, comment='Schema 名称 (默认为英文表名称)'
default_datetime_column: Mapped[bool] = mapped_column(default=True, comment='是否存在默认时间列')
api_version: Mapped[str] = mapped_column(String(20), default='v1', comment='代码生成 api 版本,默认为 v1')
gen_path: Mapped[str | None] = mapped_column(String(255), default=None, comment='代码生成路径(默认为 app 根路径)')
remark: Mapped[str | None] = mapped_column(
LONGTEXT().with_variant(TEXT, 'postgresql'),
default=None,
comment='备注',
) )
filename: Mapped[str | None] = mapped_column(sa.String(50), default=None, comment='基础文件名(默认为英文表名称)')
default_datetime_column: Mapped[bool] = mapped_column(default=True, comment='是否存在默认时间列')
api_version: Mapped[str] = mapped_column(sa.String(20), default='v1', comment='代码生成 api 版本,默认为 v1')
gen_path: Mapped[str | None] = mapped_column(
sa.String(255), default=None, comment='代码生成路径(默认为 app 根路径)'
)
remark: Mapped[str | None] = mapped_column(UniversalText, default=None, comment='备注')
# 代码生成业务模型列一对多 # 代码生成业务模型列一对多
gen_column: Mapped[list[GenColumn]] = relationship(init=False, back_populates='gen_business') gen_column: Mapped[list[GenColumn]] = relationship(init=False, back_populates='gen_business')
+9 -17
View File
@@ -2,12 +2,11 @@ from __future__ import annotations
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from sqlalchemy import BigInteger, ForeignKey, String import sqlalchemy as sa
from sqlalchemy.dialects.mysql import LONGTEXT
from sqlalchemy.dialects.postgresql import TEXT
from sqlalchemy.orm import Mapped, mapped_column, relationship from sqlalchemy.orm import Mapped, mapped_column, relationship
from backend.common.model import DataClassBase, id_key from backend.common.model import DataClassBase, UniversalText, id_key
if TYPE_CHECKING: if TYPE_CHECKING:
from backend.plugin.code_generator.model import GenBusiness from backend.plugin.code_generator.model import GenBusiness
@@ -19,15 +18,11 @@ class GenColumn(DataClassBase):
__tablename__ = 'gen_column' __tablename__ = 'gen_column'
id: Mapped[id_key] = mapped_column(init=False) id: Mapped[id_key] = mapped_column(init=False)
name: Mapped[str] = mapped_column(String(50), comment='列名称') name: Mapped[str] = mapped_column(sa.String(50), comment='列名称')
comment: Mapped[str | None] = mapped_column(String(255), default=None, comment='列描述') comment: Mapped[str | None] = mapped_column(sa.String(255), default=None, comment='列描述')
type: Mapped[str] = mapped_column(String(20), default='String', comment='SQLA 模型列类型') type: Mapped[str] = mapped_column(sa.String(20), default='String', comment='SQLA 模型列类型')
pd_type: Mapped[str] = mapped_column(String(20), default='str', comment='列类型对应的 pydantic 类型') pd_type: Mapped[str] = mapped_column(sa.String(20), default='str', comment='列类型对应的 pydantic 类型')
default: Mapped[str | None] = mapped_column( default: Mapped[str | None] = mapped_column(UniversalText, default=None, comment='列默认值')
LONGTEXT().with_variant(TEXT, 'postgresql'),
default=None,
comment='列默认值',
)
sort: Mapped[int | None] = mapped_column(default=1, comment='列排序') sort: Mapped[int | None] = mapped_column(default=1, comment='列排序')
length: Mapped[int] = mapped_column(default=0, comment='列长度') length: Mapped[int] = mapped_column(default=0, comment='列长度')
is_pk: Mapped[bool] = mapped_column(default=False, comment='是否主键') is_pk: Mapped[bool] = mapped_column(default=False, comment='是否主键')
@@ -35,9 +30,6 @@ class GenColumn(DataClassBase):
# 代码生成业务模型列一对多 # 代码生成业务模型列一对多
gen_business_id: Mapped[int] = mapped_column( gen_business_id: Mapped[int] = mapped_column(
BigInteger, sa.BigInteger, sa.ForeignKey('gen_business.id', ondelete='CASCADE'), default=0, comment='代码生成业务ID'
ForeignKey('gen_business.id', ondelete='CASCADE'),
default=0,
comment='代码生成业务ID',
) )
gen_business: Mapped[GenBusiness | None] = relationship(init=False, back_populates='gen_column') gen_business: Mapped[GenBusiness | None] = relationship(init=False, back_populates='gen_column')
@@ -37,7 +37,7 @@ async def get_{{ table_name }}(
], ],
) )
async def get_{{ table_name }}s_paginated(db: CurrentSession) -> ResponseSchemaModel[PageData[Get{{ schema_name }}Detail]]: async def get_{{ table_name }}s_paginated(db: CurrentSession) -> ResponseSchemaModel[PageData[Get{{ schema_name }}Detail]]:
page_data = = await {{ table_name }}_service.get_list(db=db) page_data = await {{ table_name }}_service.get_list(db=db)
return response_base.success(data=page_data) return response_base.success(data=page_data)
+9 -18
View File
@@ -1,9 +1,8 @@
from sqlalchemy import Boolean, String import sqlalchemy as sa
from sqlalchemy.dialects.mysql import LONGTEXT
from sqlalchemy.dialects.postgresql import INTEGER, TEXT
from sqlalchemy.orm import Mapped, mapped_column from sqlalchemy.orm import Mapped, mapped_column
from backend.common.model import Base, id_key from backend.common.model import Base, UniversalText, id_key
class Config(Base): class Config(Base):
@@ -12,17 +11,9 @@ class Config(Base):
__tablename__ = 'sys_config' __tablename__ = 'sys_config'
id: Mapped[id_key] = mapped_column(init=False) id: Mapped[id_key] = mapped_column(init=False)
name: Mapped[str] = mapped_column(String(20), comment='名称') name: Mapped[str] = mapped_column(sa.String(20), comment='名称')
type: Mapped[str | None] = mapped_column(String(20), server_default=None, comment='类型') type: Mapped[str | None] = mapped_column(sa.String(20), server_default=None, comment='类型')
key: Mapped[str] = mapped_column(String(50), unique=True, comment='键名') key: Mapped[str] = mapped_column(sa.String(50), unique=True, comment='键名')
value: Mapped[str] = mapped_column(LONGTEXT().with_variant(TEXT, 'postgresql'), comment='键值') value: Mapped[str] = mapped_column(UniversalText, comment='键值')
is_frontend: Mapped[bool] = mapped_column( is_frontend: Mapped[bool] = mapped_column(default=False, comment='是否前端')
Boolean().with_variant(INTEGER, 'postgresql'), remark: Mapped[str | None] = mapped_column(UniversalText, default=None, comment='备注')
default=False,
comment='是否前端',
)
remark: Mapped[str | None] = mapped_column(
LONGTEXT().with_variant(TEXT, 'postgresql'),
default=None,
comment='备注',
)
+6 -6
View File
@@ -1,8 +1,8 @@
insert into sys_config (id, name, type, `key`, value, is_frontend, remark, created_time, updated_time) insert into sys_config (id, name, type, `key`, value, is_frontend, remark, created_time, updated_time)
values values
(1, '状态', 'EMAIL', 'EMAIL_STATUS', '1', 0, null, now(), null), (1, '状态', 'EMAIL', 'EMAIL_STATUS', '1', false, null, now(), null),
(2, '服务器地址', 'EMAIL', 'EMAIL_HOST', 'smtp.qq.com', 0, null, now(), null), (2, '服务器地址', 'EMAIL', 'EMAIL_HOST', 'smtp.qq.com', false, null, now(), null),
(3, '服务器端口', 'EMAIL', 'EMAIL_PORT', '465', 0, null, now(), null), (3, '服务器端口', 'EMAIL', 'EMAIL_PORT', '465', false, null, now(), null),
(4, '邮箱账号', 'EMAIL', 'EMAIL_USERNAME', 'fba@qq.com', 0, null, now(), null), (4, '邮箱账号', 'EMAIL', 'EMAIL_USERNAME', 'fba@qq.com', false, null, now(), null),
(5, '邮箱密码', 'EMAIL', 'EMAIL_PASSWORD', '', 0, null, now(), null), (5, '邮箱密码', 'EMAIL', 'EMAIL_PASSWORD', '', false, null, now(), null),
(6, 'SSL 加密', 'EMAIL', 'EMAIL_SSL', '1', 0, null, now(), null); (6, 'SSL 加密', 'EMAIL', 'EMAIL_SSL', '1', false, null, now(), null);
@@ -1,8 +1,8 @@
insert into sys_config (id, name, type, `key`, value, is_frontend, remark, created_time, updated_time) insert into sys_config (id, name, type, `key`, value, is_frontend, remark, created_time, updated_time)
values values
(2069061886627938304, '状态', 'EMAIL', 'EMAIL_STATUS', '1', 0, null, now(), null), (2069061886627938304, '状态', 'EMAIL', 'EMAIL_STATUS', '1', false, null, now(), null),
(2069061886627938305, '服务器地址', 'EMAIL', 'EMAIL_HOST', 'smtp.qq.com', 0, null, now(), null), (2069061886627938305, '服务器地址', 'EMAIL', 'EMAIL_HOST', 'smtp.qq.com', false, null, now(), null),
(2069061886627938306, '服务器端口', 'EMAIL', 'EMAIL_PORT', '465', 0, null, now(), null), (2069061886627938306, '服务器端口', 'EMAIL', 'EMAIL_PORT', '465', false, null, now(), null),
(2069061886627938307, '邮箱账号', 'EMAIL', 'EMAIL_USERNAME', 'fba@qq.com', 0, null, now(), null), (2069061886627938307, '邮箱账号', 'EMAIL', 'EMAIL_USERNAME', 'fba@qq.com', false, null, now(), null),
(2069061886627938308, '邮箱密码', 'EMAIL', 'EMAIL_PASSWORD', '', 0, null, now(), null), (2069061886627938308, '邮箱密码', 'EMAIL', 'EMAIL_PASSWORD', '', false, null, now(), null),
(2069061886627938309, 'SSL 加密', 'EMAIL', 'EMAIL_SSL', '1', 0, null, now(), null); (2069061886627938309, 'SSL 加密', 'EMAIL', 'EMAIL_SSL', '1', false, null, now(), null);
@@ -1,11 +1,10 @@
insert into sys_config (id, name, type, "key", value, is_frontend, remark, created_time, updated_time) insert into sys_config (id, name, type, "key", value, is_frontend, remark, created_time, updated_time)
values values
(1, '状态', 'EMAIL', 'EMAIL_STATUS', '1', 0, null, now(), null), (1, '状态', 'EMAIL', 'EMAIL_STATUS', '1', false, null, now(), null),
(2, '服务器地址', 'EMAIL', 'EMAIL_HOST', 'smtp.qq.com', 0, null, now(), null), (2, '服务器地址', 'EMAIL', 'EMAIL_HOST', 'smtp.qq.com', false, null, now(), null),
(3, '服务器端口', 'EMAIL', 'EMAIL_PORT', '465', 0, null, now(), null), (3, '服务器端口', 'EMAIL', 'EMAIL_PORT', '465', false, null, now(), null),
(4, '邮箱账号', 'EMAIL', 'EMAIL_USERNAME', 'fba@qq.com', 0, null, now(), null), (4, '邮箱账号', 'EMAIL', 'EMAIL_USERNAME', 'fba@qq.com', false, null, now(), null),
(5, '邮箱密码', 'EMAIL', 'EMAIL_PASSWORD', '', 0, null, now(), null), (5, '邮箱密码', 'EMAIL', 'EMAIL_PASSWORD', '', false, null, now(), null),
(6, 'SSL 加密', 'EMAIL', 'EMAIL_SSL', '1', 0, null, now(), null); (6, 'SSL 加密', 'EMAIL', 'EMAIL_SSL', '1', false, null, now(), null);
-- reset auto-increment values for each table based on max id
select setval(pg_get_serial_sequence('sys_config', 'id'),coalesce(max(id), 0) + 1, true) from sys_config; select setval(pg_get_serial_sequence('sys_config', 'id'),coalesce(max(id), 0) + 1, true) from sys_config;
@@ -1,8 +1,8 @@
insert into sys_config (id, name, type, "key", value, is_frontend, remark, created_time, updated_time) insert into sys_config (id, name, type, "key", value, is_frontend, remark, created_time, updated_time)
values values
(2069061886627938304, '状态', 'EMAIL', 'EMAIL_STATUS', '1', 0, null, now(), null), (2069061886627938304, '状态', 'EMAIL', 'EMAIL_STATUS', '1', false, null, now(), null),
(2069061886627938305, '服务器地址', 'EMAIL', 'EMAIL_HOST', 'smtp.qq.com', 0, null, now(), null), (2069061886627938305, '服务器地址', 'EMAIL', 'EMAIL_HOST', 'smtp.qq.com', false, null, now(), null),
(2069061886627938306, '服务器端口', 'EMAIL', 'EMAIL_PORT', '465', 0, null, now(), null), (2069061886627938306, '服务器端口', 'EMAIL', 'EMAIL_PORT', '465', false, null, now(), null),
(2069061886627938307, '邮箱账号', 'EMAIL', 'EMAIL_USERNAME', 'fba@qq.com', 0, null, now(), null), (2069061886627938307, '邮箱账号', 'EMAIL', 'EMAIL_USERNAME', 'fba@qq.com', false, null, now(), null),
(2069061886627938308, '邮箱密码', 'EMAIL', 'EMAIL_PASSWORD', '', 0, null, now(), null), (2069061886627938308, '邮箱密码', 'EMAIL', 'EMAIL_PASSWORD', '', false, null, now(), null),
(2069061886627938309, 'SSL 加密', 'EMAIL', 'EMAIL_SSL', '1', 0, null, now(), null); (2069061886627938309, 'SSL 加密', 'EMAIL', 'EMAIL_SSL', '1', false, null, now(), null);
+4 -3
View File
@@ -1,6 +1,6 @@
from collections.abc import Sequence from collections.abc import Sequence
from sqlalchemy import Select from sqlalchemy import Select, and_
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy_crud_plus import CRUDPlus from sqlalchemy_crud_plus import CRUDPlus
@@ -79,15 +79,16 @@ class CRUDDictData(CRUDPlus[DictData]):
return await self.select_order('id', 'desc', load_strategies={'type': 'noload'}, **filters) return await self.select_order('id', 'desc', load_strategies={'type': 'noload'}, **filters)
async def get_by_label(self, db: AsyncSession, label: str) -> DictData | None: async def get_by_label_and_type_code(self, db: AsyncSession, label: str, type_code: str) -> DictData | None:
""" """
通过标签获取字典数据 通过标签获取字典数据
:param db: 数据库会话 :param db: 数据库会话
:param label: 字典标签 :param label: 字典标签
:param type_code: 字典类型编码
:return: :return:
""" """
return await self.select_model_by_column(db, label=label) return await self.select_model_by_column(db, and_(self.model.label == label, self.model.type_code == type_code))
async def create(self, db: AsyncSession, obj: CreateDictDataParam, type_code: str) -> None: async def create(self, db: AsyncSession, obj: CreateDictDataParam, type_code: str) -> None:
""" """
+9 -16
View File
@@ -2,12 +2,11 @@ from __future__ import annotations
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from sqlalchemy import ForeignKey, String import sqlalchemy as sa
from sqlalchemy.dialects.mysql import LONGTEXT
from sqlalchemy.dialects.postgresql import TEXT
from sqlalchemy.orm import Mapped, mapped_column, relationship from sqlalchemy.orm import Mapped, mapped_column, relationship
from backend.common.model import Base, id_key from backend.common.model import Base, UniversalText, id_key
if TYPE_CHECKING: if TYPE_CHECKING:
from backend.plugin.dict.model import DictType from backend.plugin.dict.model import DictType
@@ -19,22 +18,16 @@ class DictData(Base):
__tablename__ = 'sys_dict_data' __tablename__ = 'sys_dict_data'
id: Mapped[id_key] = mapped_column(init=False) id: Mapped[id_key] = mapped_column(init=False)
type_code: Mapped[str] = mapped_column(String(32), comment='对应的字典类型编码') type_code: Mapped[str] = mapped_column(sa.String(32), comment='对应的字典类型编码')
label: Mapped[str] = mapped_column(String(32), comment='字典标签') label: Mapped[str] = mapped_column(sa.String(32), comment='字典标签')
value: Mapped[str] = mapped_column(String(32), comment='字典值') value: Mapped[str] = mapped_column(sa.String(32), comment='字典值')
color: Mapped[str | None] = mapped_column(String(32), default=None, comment='标签颜色') color: Mapped[str | None] = mapped_column(sa.String(32), default=None, comment='标签颜色')
sort: Mapped[int] = mapped_column(default=0, comment='排序') sort: Mapped[int] = mapped_column(default=0, comment='排序')
status: Mapped[int] = mapped_column(default=1, comment='状态(0停用 1正常)') status: Mapped[int] = mapped_column(default=1, comment='状态(0停用 1正常)')
remark: Mapped[str | None] = mapped_column( remark: Mapped[str | None] = mapped_column(UniversalText, default=None, comment='备注')
LONGTEXT().with_variant(TEXT, 'postgresql'),
default=None,
comment='备注',
)
# 字典类型一对多 # 字典类型一对多
type_id: Mapped[int] = mapped_column( type_id: Mapped[int] = mapped_column(
ForeignKey('sys_dict_type.id', ondelete='CASCADE'), sa.ForeignKey('sys_dict_type.id', ondelete='CASCADE'), default=0, comment='字典类型关联ID'
default=0,
comment='字典类型关联ID',
) )
type: Mapped[DictType] = relationship(init=False, back_populates='datas') type: Mapped[DictType] = relationship(init=False, back_populates='datas')
+6 -11
View File
@@ -2,12 +2,11 @@ from __future__ import annotations
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from sqlalchemy import String import sqlalchemy as sa
from sqlalchemy.dialects.mysql import LONGTEXT
from sqlalchemy.dialects.postgresql import TEXT
from sqlalchemy.orm import Mapped, mapped_column, relationship from sqlalchemy.orm import Mapped, mapped_column, relationship
from backend.common.model import Base, id_key from backend.common.model import Base, UniversalText, id_key
if TYPE_CHECKING: if TYPE_CHECKING:
from backend.plugin.dict.model import DictData from backend.plugin.dict.model import DictData
@@ -19,13 +18,9 @@ class DictType(Base):
__tablename__ = 'sys_dict_type' __tablename__ = 'sys_dict_type'
id: Mapped[id_key] = mapped_column(init=False) id: Mapped[id_key] = mapped_column(init=False)
name: Mapped[str] = mapped_column(String(32), comment='字典类型名称') name: Mapped[str] = mapped_column(sa.String(32), comment='字典类型名称')
code: Mapped[str] = mapped_column(String(32), unique=True, comment='字典类型编码') code: Mapped[str] = mapped_column(sa.String(32), unique=True, comment='字典类型编码')
remark: Mapped[str | None] = mapped_column( remark: Mapped[str | None] = mapped_column(UniversalText, default=None, comment='备注')
LONGTEXT().with_variant(TEXT, 'postgresql'),
default=None,
comment='备注',
)
# 字典类型一对多 # 字典类型一对多
datas: Mapped[list[DictData]] = relationship(init=False, back_populates='type') datas: Mapped[list[DictData]] = relationship(init=False, back_populates='type')
+1 -1
View File
@@ -1,6 +1,6 @@
[plugin] [plugin]
summary = '数据字典' summary = '数据字典'
version = '0.0.6' version = '0.0.7'
description = '通常用于约束前端工程数据展示' description = '通常用于约束前端工程数据展示'
author = 'wu-clan' author = 'wu-clan'
@@ -94,13 +94,12 @@ class DictDataService:
:param obj: 字典数据创建参数 :param obj: 字典数据创建参数
:return: :return:
""" """
dict_data = await dict_data_dao.get_by_label(db, obj.label)
if dict_data:
raise errors.ConflictError(msg='字典数据已存在')
dict_type = await dict_type_dao.get(db, obj.type_id) dict_type = await dict_type_dao.get(db, obj.type_id)
if not dict_type: if not dict_type:
raise errors.NotFoundError(msg='字典类型不存在') raise errors.NotFoundError(msg='字典类型不存在')
dict_data = await dict_data_dao.get_by_label_and_type_code(db, obj.label, dict_type.code)
if dict_data:
raise errors.ConflictError(msg='字典数据已存在')
await dict_data_dao.create(db, obj, dict_type.code) await dict_data_dao.create(db, obj, dict_type.code)
@staticmethod @staticmethod
@@ -117,11 +116,13 @@ class DictDataService:
dict_data = await dict_data_dao.get(db, pk) dict_data = await dict_data_dao.get(db, pk)
if not dict_data: if not dict_data:
raise errors.NotFoundError(msg='字典数据不存在') raise errors.NotFoundError(msg='字典数据不存在')
if dict_data.label != obj.label and await dict_data_dao.get_by_label(db, obj.label):
raise errors.ConflictError(msg='字典数据已存在')
dict_type = await dict_type_dao.get(db, obj.type_id) dict_type = await dict_type_dao.get(db, obj.type_id)
if not dict_type: if not dict_type:
raise errors.NotFoundError(msg='字典类型不存在') raise errors.NotFoundError(msg='字典类型不存在')
if dict_data.label != obj.label and await dict_data_dao.get_by_label_and_type_code(
db, obj.label, dict_type.code
):
raise errors.ConflictError(msg='字典数据已存在')
count = await dict_data_dao.update(db, pk, obj, dict_type.code) count = await dict_data_dao.update(db, pk, obj, dict_type.code)
return count return count
@@ -51,3 +51,6 @@ values
(34, 'user_online_status', '在线', '1', 'success', 2, 1, '用户在线状态', 11, now(), null), (34, 'user_online_status', '在线', '1', 'success', 2, 1, '用户在线状态', 11, now(), null),
(35, 'sys_plugin_type', '压缩包', '0', 'gold', 1, 1, '插件类型-压缩包', 12, now(), null), (35, 'sys_plugin_type', '压缩包', '0', 'gold', 1, 1, '插件类型-压缩包', 12, now(), null),
(36, 'sys_plugin_type', 'GIT', '1', 'processing', 2, 1, '插件类型-GIT', 12, now(), null); (36, 'sys_plugin_type', 'GIT', '1', 'processing', 2, 1, '插件类型-GIT', 12, now(), null);
select setval(pg_get_serial_sequence('sys_dict_type', 'id'),coalesce(max(id), 0) + 1, true) from sys_dict_type;
select setval(pg_get_serial_sequence('sys_dict_data', 'id'),coalesce(max(id), 0) + 1, true) from sys_dict_data;
+5 -5
View File
@@ -1,8 +1,8 @@
from sqlalchemy import TEXT, String import sqlalchemy as sa
from sqlalchemy.dialects.mysql import LONGTEXT
from sqlalchemy.orm import Mapped, mapped_column from sqlalchemy.orm import Mapped, mapped_column
from backend.common.model import Base, id_key from backend.common.model import Base, UniversalText, id_key
class Notice(Base): class Notice(Base):
@@ -11,7 +11,7 @@ class Notice(Base):
__tablename__ = 'sys_notice' __tablename__ = 'sys_notice'
id: Mapped[id_key] = mapped_column(init=False) id: Mapped[id_key] = mapped_column(init=False)
title: Mapped[str] = mapped_column(String(50), comment='标题') title: Mapped[str] = mapped_column(sa.String(50), comment='标题')
type: Mapped[int] = mapped_column(comment='类型(0:通知、1:公告)') type: Mapped[int] = mapped_column(comment='类型(0:通知、1:公告)')
status: Mapped[int] = mapped_column(comment='状态(0:隐藏、1:显示)') status: Mapped[int] = mapped_column(comment='状态(0:隐藏、1:显示)')
content: Mapped[str] = mapped_column(LONGTEXT().with_variant(TEXT, 'postgresql'), comment='内容') content: Mapped[str] = mapped_column(UniversalText, comment='内容')
+5 -6
View File
@@ -2,7 +2,8 @@ from __future__ import annotations
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from sqlalchemy import BigInteger, ForeignKey, String import sqlalchemy as sa
from sqlalchemy.orm import Mapped, mapped_column, relationship from sqlalchemy.orm import Mapped, mapped_column, relationship
from backend.common.model import Base, id_key from backend.common.model import Base, id_key
@@ -17,13 +18,11 @@ class UserSocial(Base):
__tablename__ = 'sys_user_social' __tablename__ = 'sys_user_social'
id: Mapped[id_key] = mapped_column(init=False) id: Mapped[id_key] = mapped_column(init=False)
sid: Mapped[str] = mapped_column(String(255), comment='第三方用户 ID') sid: Mapped[str] = mapped_column(sa.String(255), comment='第三方用户 ID')
source: Mapped[str] = mapped_column(String(20), comment='第三方用户来源') source: Mapped[str] = mapped_column(sa.String(20), comment='第三方用户来源')
# 用户社交信息一对多 # 用户社交信息一对多
user_id: Mapped[int] = mapped_column( user_id: Mapped[int] = mapped_column(
BigInteger, sa.BigInteger, sa.ForeignKey('sys_user.id', ondelete='CASCADE'), comment='用户关联ID'
ForeignKey('sys_user.id', ondelete='CASCADE'),
comment='用户关联ID',
) )
user: Mapped[User | None] = relationship(init=False, backref='socials') user: Mapped[User | None] = relationship(init=False, backref='socials')
@@ -1,5 +1,5 @@
insert into sys_dept (id, name, sort, leader, phone, email, status, del_flag, parent_id, created_time, updated_time) insert into sys_dept (id, name, sort, leader, phone, email, status, del_flag, parent_id, created_time, updated_time)
values (2048601258595581952, '测试', 0, null, null, null, 1, 0, null, now(), null); values (2048601258595581952, '测试', 0, null, null, null, 1, false, null, now(), null);
insert into sys_menu (id, title, name, path, sort, icon, type, component, perms, status, display, cache, link, remark, parent_id, created_time, updated_time) insert into sys_menu (id, title, name, path, sort, icon, type, component, perms, status, display, cache, link, remark, parent_id, created_time, updated_time)
values values
@@ -82,7 +82,7 @@ values
(2049629108257816588, '写入', 'WriteGenCode', null, 0, null, 2, null, 'codegen:local:write', 1, 0, 1, '', null, 2049629108257816580, '2025-06-26 20:29:06', null); (2049629108257816588, '写入', 'WriteGenCode', null, 0, null, 2, null, 'codegen:local:write', 1, 0, 1, '', null, 2049629108257816580, '2025-06-26 20:29:06', null);
insert into sys_role (id, name, status, is_filter_scopes, remark, created_time, updated_time) insert into sys_role (id, name, status, is_filter_scopes, remark, created_time, updated_time)
values (2048601263515500544, '测试', 1, 1, null, now(), null); values (2048601263515500544, '测试', 1, true, null, now(), null);
insert into sys_role_menu (id, role_id, menu_id) insert into sys_role_menu (id, role_id, menu_id)
values values
@@ -91,10 +91,10 @@ values
(2048601263708438528, 2048601263515500544, 2049629108245233666), (2048601263708438528, 2048601263515500544, 2049629108245233666),
(2048601263775547392, 2048601263515500544, 2049629108253622282); (2048601263775547392, 2048601263515500544, 2049629108253622282);
insert into sys_user (id, uuid, username, nickname, password, salt, email, is_superuser, is_staff, status, is_multi_login, avatar, phone, join_time, last_login_time, dept_id, created_time, updated_time) insert into sys_user (id, uuid, username, nickname, password, salt, email, status, is_superuser, is_staff, is_multi_login, avatar, phone, join_time, last_login_time, dept_id, created_time, updated_time)
values values
(2048601263834267648, uuid(), 'admin', '用户88888', '$2b$12$8y2eNucX19VjmZ3tYhBLcOsBwy9w1IjBQE4SSqwMDL5bGQVp2wqS.', unhex('24326224313224387932654E7563583139566A6D5A33745968424C634F'), 'admin@example.com', 1, 1, 1, 1, null, null, now(), now(), 2048601258595581952, now(), null), (2048601263834267648, uuid(), 'admin', '用户88888', '$2b$12$8y2eNucX19VjmZ3tYhBLcOsBwy9w1IjBQE4SSqwMDL5bGQVp2wqS.', unhex('24326224313224387932654E7563583139566A6D5A33745968424C634F'), 'admin@example.com', 1, true, true, true, null, null, now(), now(), 2048601258595581952, now(), null),
(2049946297615646720, uuid(), 'test', '用户66666', '$2b$12$BMiXsNQAgTx7aNc7kVgnwedXGyUxPEHRnJMFbiikbqHgVoT3y14Za', unhex('24326224313224424D6958734E514167547837614E63376B56676E7765'), 'test@example.com', 0, 0, 1, 0, null, null, now(), now(), 2048601258595581952, now(), null); (2049946297615646720, uuid(), 'test', '用户66666', '$2b$12$BMiXsNQAgTx7aNc7kVgnwedXGyUxPEHRnJMFbiikbqHgVoT3y14Za', unhex('24326224313224424D6958734E514167547837614E63376B56676E7765'), 'test@example.com', 1, false, false, false, null, null, now(), now(), 2048601258595581952, now(), null);
insert into sys_user_role (id, user_id, role_id) insert into sys_user_role (id, user_id, role_id)
values values
+5 -5
View File
@@ -1,5 +1,5 @@
insert into sys_dept (id, name, sort, leader, phone, email, status, del_flag, parent_id, created_time, updated_time) insert into sys_dept (id, name, sort, leader, phone, email, status, del_flag, parent_id, created_time, updated_time)
values (1, '测试', 0, null, null, null, 1, 0, null, now(), null); values (1, '测试', 0, null, null, null, 1, false, null, now(), null);
insert into sys_menu (id, title, name, path, sort, icon, type, component, perms, status, display, cache, link, remark, parent_id, created_time, updated_time) insert into sys_menu (id, title, name, path, sort, icon, type, component, perms, status, display, cache, link, remark, parent_id, created_time, updated_time)
values values
@@ -82,7 +82,7 @@ values
(77, '写入', 'WriteGenCode', null, 0, null, 2, null, 'codegen:local:write', 1, 0, 1, '', null, 69, '2025-06-26 20:29:06', null); (77, '写入', 'WriteGenCode', null, 0, null, 2, null, 'codegen:local:write', 1, 0, 1, '', null, 69, '2025-06-26 20:29:06', null);
insert into sys_role (id, name, status, is_filter_scopes, remark, created_time, updated_time) insert into sys_role (id, name, status, is_filter_scopes, remark, created_time, updated_time)
values (1, '测试', 1, 1, null, now(), null); values (1, '测试', 1, true, null, now(), null);
insert into sys_role_menu (id, role_id, menu_id) insert into sys_role_menu (id, role_id, menu_id)
values values
@@ -91,10 +91,10 @@ values
(3, 1, 3), (3, 1, 3),
(4, 1, 53); (4, 1, 53);
insert into sys_user (id, uuid, username, nickname, password, salt, email, is_superuser, is_staff, status, is_multi_login, avatar, phone, join_time, last_login_time, dept_id, created_time, updated_time) insert into sys_user (id, uuid, username, nickname, password, salt, email, status, is_superuser, is_staff, is_multi_login, avatar, phone, join_time, last_login_time, dept_id, created_time, updated_time)
values values
(1, uuid(), 'admin', '用户88888', '$2b$12$8y2eNucX19VjmZ3tYhBLcOsBwy9w1IjBQE4SSqwMDL5bGQVp2wqS.', unhex('24326224313224387932654E7563583139566A6D5A33745968424C634F'), 'admin@example.com', 1, 1, 1, 1, null, null, now(), now(), 1, now(), null), (1, uuid(), 'admin', '用户88888', '$2b$12$8y2eNucX19VjmZ3tYhBLcOsBwy9w1IjBQE4SSqwMDL5bGQVp2wqS.', unhex('24326224313224387932654E7563583139566A6D5A33745968424C634F'), 'admin@example.com', 1, true, true, true, null, null, now(), now(), 1, now(), null),
(2, uuid(), 'test', '用户66666', '$2b$12$BMiXsNQAgTx7aNc7kVgnwedXGyUxPEHRnJMFbiikbqHgVoT3y14Za', unhex('24326224313224424D6958734E514167547837614E63376B56676E7765'), 'test@example.com', 0, 0, 1, 0, null, null, now(), now(), 1, now(), null); (2, uuid(), 'test', '用户66666', '$2b$12$BMiXsNQAgTx7aNc7kVgnwedXGyUxPEHRnJMFbiikbqHgVoT3y14Za', unhex('24326224313224424D6958734E514167547837614E63376B56676E7765'), 'test@example.com', 1, false, false, false, null, null, now(), now(), 1, now(), null);
insert into sys_user_role (id, user_id, role_id) insert into sys_user_role (id, user_id, role_id)
values values
@@ -1,5 +1,5 @@
insert into sys_dept (id, name, sort, leader, phone, email, status, del_flag, parent_id, created_time, updated_time) insert into sys_dept (id, name, sort, leader, phone, email, status, del_flag, parent_id, created_time, updated_time)
values (2048601264366944256, '测试', 0, null, null, null, 1, 0, null, now(), null); values (2048601264366944256, '测试', 0, null, null, null, 1, false, null, now(), null);
insert into sys_menu (id, title, name, path, sort, icon, type, component, perms, status, display, cache, link, remark, parent_id, created_time, updated_time) insert into sys_menu (id, title, name, path, sort, icon, type, component, perms, status, display, cache, link, remark, parent_id, created_time, updated_time)
values values
@@ -82,7 +82,7 @@ values
(2049629108257816588, '写入', 'WriteGenCode', null, 0, null, 2, null, 'codegen:local:write', 1, 0, 1, '', null, 2049629108257816580, '2025-06-26 20:29:06', null); (2049629108257816588, '写入', 'WriteGenCode', null, 0, null, 2, null, 'codegen:local:write', 1, 0, 1, '', null, 2049629108257816580, '2025-06-26 20:29:06', null);
insert into sys_role (id, name, status, is_filter_scopes, remark, created_time, updated_time) insert into sys_role (id, name, status, is_filter_scopes, remark, created_time, updated_time)
values (2048601269345583104, '测试', 1, 1, null, now(), null); values (2048601269345583104, '测试', 1, true, null, now(), null);
insert into sys_role_menu (id, role_id, menu_id) insert into sys_role_menu (id, role_id, menu_id)
values values
@@ -91,10 +91,10 @@ values
(2048601269546909696, 2048601269345583104, 2049629108245233666), (2048601269546909696, 2048601269345583104, 2049629108245233666),
(2048601269609824256, 2048601269345583104, 2049629108253622282); (2048601269609824256, 2048601269345583104, 2049629108253622282);
insert into sys_user (id, uuid, username, nickname, password, salt, email, is_superuser, is_staff, status, is_multi_login, avatar, phone, join_time, last_login_time, dept_id, created_time, updated_time) insert into sys_user (id, uuid, username, nickname, password, salt, email, status, is_superuser, is_staff, is_multi_login, avatar, phone, join_time, last_login_time, dept_id, created_time, updated_time)
values values
(2048601269672738816, gen_random_uuid(), 'admin', '用户88888', '$2b$12$8y2eNucX19VjmZ3tYhBLcOsBwy9w1IjBQE4SSqwMDL5bGQVp2wqS.', decode('24326224313224387932654E7563583139566A6D5A33745968424C634F', 'hex'), 'admin@example.com', 1, 1, 1, 1, null, null, now(), now(), 2048601264366944256, now(), null), (2048601269672738816, gen_random_uuid(), 'admin', '用户88888', '$2b$12$8y2eNucX19VjmZ3tYhBLcOsBwy9w1IjBQE4SSqwMDL5bGQVp2wqS.', decode('24326224313224387932654E7563583139566A6D5A33745968424C634F', 'hex'), 'admin@example.com', 1, true, true, true, null, null, now(), now(), 2048601264366944256, now(), null),
(2049946297615646720, gen_random_uuid(), 'test', '用户66666', '$2b$12$BMiXsNQAgTx7aNc7kVgnwedXGyUxPEHRnJMFbiikbqHgVoT3y14Za', decode('24326224313224424D6958734E514167547837614E63376B56676E7765', 'hex'), 'test@example.com', 0, 0, 1, 0, null, null, now(), now(), 2048601264366944256, now(), null); (2049946297615646720, gen_random_uuid(), 'test', '用户66666', '$2b$12$BMiXsNQAgTx7aNc7kVgnwedXGyUxPEHRnJMFbiikbqHgVoT3y14Za', decode('24326224313224424D6958734E514167547837614E63376B56676E7765', 'hex'), 'test@example.com', 1, false, false, false, null, null, now(), now(), 2048601264366944256, now(), null);
insert into sys_user_role (id, user_id, role_id) insert into sys_user_role (id, user_id, role_id)
values values
+5 -6
View File
@@ -1,5 +1,5 @@
insert into sys_dept (id, name, sort, leader, phone, email, status, del_flag, parent_id, created_time, updated_time) insert into sys_dept (id, name, sort, leader, phone, email, status, del_flag, parent_id, created_time, updated_time)
values (1, '测试', 0, null, null, null, 1, 0, null, now(), null); values (1, '测试', 0, null, null, null, 1, false, null, now(), null);
insert into sys_menu (id, title, name, path, sort, icon, type, component, perms, status, display, cache, link, remark, parent_id, created_time, updated_time) insert into sys_menu (id, title, name, path, sort, icon, type, component, perms, status, display, cache, link, remark, parent_id, created_time, updated_time)
values values
@@ -82,7 +82,7 @@ values
(77, '写入', 'WriteGenCode', null, 0, null, 2, null, 'codegen:local:write', 1, 0, 1, '', null, 69, '2025-06-26 20:29:06', null); (77, '写入', 'WriteGenCode', null, 0, null, 2, null, 'codegen:local:write', 1, 0, 1, '', null, 69, '2025-06-26 20:29:06', null);
insert into sys_role (id, name, status, is_filter_scopes, remark, created_time, updated_time) insert into sys_role (id, name, status, is_filter_scopes, remark, created_time, updated_time)
values (1, '测试', 1, 1, null, now(), null); values (1, '测试', 1, true, null, now(), null);
insert into sys_role_menu (id, role_id, menu_id) insert into sys_role_menu (id, role_id, menu_id)
values values
@@ -91,10 +91,10 @@ values
(3, 1, 3), (3, 1, 3),
(4, 1, 53); (4, 1, 53);
insert into sys_user (id, uuid, username, nickname, password, salt, email, is_superuser, is_staff, status, is_multi_login, avatar, phone, join_time, last_login_time, dept_id, created_time, updated_time) insert into sys_user (id, uuid, username, nickname, password, salt, email, status, is_superuser, is_staff, is_multi_login, avatar, phone, join_time, last_login_time, dept_id, created_time, updated_time)
values values
(1, gen_random_uuid(), 'admin', '用户88888', '$2b$12$8y2eNucX19VjmZ3tYhBLcOsBwy9w1IjBQE4SSqwMDL5bGQVp2wqS.', decode('24326224313224387932654E7563583139566A6D5A33745968424C634F', 'hex'), 'admin@example.com', 1, 1, 1, 1, null, null, now(), now(), 1, now(), null), (1, gen_random_uuid(), 'admin', '用户88888', '$2b$12$8y2eNucX19VjmZ3tYhBLcOsBwy9w1IjBQE4SSqwMDL5bGQVp2wqS.', decode('24326224313224387932654E7563583139566A6D5A33745968424C634F', 'hex'), 'admin@example.com', 1, true, true, true, null, null, now(), now(), 1, now(), null),
(2, gen_random_uuid(), 'test', '用户66666', '$2b$12$BMiXsNQAgTx7aNc7kVgnwedXGyUxPEHRnJMFbiikbqHgVoT3y14Za', decode('24326224313224424D6958734E514167547837614E63376B56676E7765', 'hex'), 'test@example.com', 0, 0, 1, 0, null, null, now(), now(), 1, now(), null); (2, gen_random_uuid(), 'test', '用户66666', '$2b$12$BMiXsNQAgTx7aNc7kVgnwedXGyUxPEHRnJMFbiikbqHgVoT3y14Za', decode('24326224313224424D6958734E514167547837614E63376B56676E7765', 'hex'), 'test@example.com', 1, false, false, false, null, null, now(), now(), 1, now(), null);
insert into sys_user_role (id, user_id, role_id) insert into sys_user_role (id, user_id, role_id)
values values
@@ -117,7 +117,6 @@ values
(2, 2, 1), (2, 2, 1),
(3, 2, 2); (3, 2, 2);
-- reset auto-increment values for each table based on max id
select setval(pg_get_serial_sequence('sys_dept', 'id'),coalesce(max(id), 0) + 1, true) from sys_dept; select setval(pg_get_serial_sequence('sys_dept', 'id'),coalesce(max(id), 0) + 1, true) from sys_dept;
select setval(pg_get_serial_sequence('sys_menu', 'id'),coalesce(max(id), 0) + 1, true) from sys_menu; select setval(pg_get_serial_sequence('sys_menu', 'id'),coalesce(max(id), 0) + 1, true) from sys_menu;
select setval(pg_get_serial_sequence('sys_role', 'id'),coalesce(max(id), 0) + 1, true) from sys_role; select setval(pg_get_serial_sequence('sys_role', 'id'),coalesce(max(id), 0) + 1, true) from sys_role;
+34 -34
View File
@@ -8,9 +8,9 @@ networks:
- subnet: 172.10.10.0/24 - subnet: 172.10.10.0/24
volumes: volumes:
# 如果你是 postgres 用户,应将 fba_mysql 修改为 fba_postgres # 如果你是 mysql 用户,应将 fba_postgres 修改为 fba_mysql
fba_mysql: fba_postgres:
name: fba_mysql name: fba_postgres
fba_redis: fba_redis:
name: fba_redis name: fba_redis
fba_static: fba_static:
@@ -28,9 +28,9 @@ services:
image: fba_server:latest image: fba_server:latest
container_name: fba_server container_name: fba_server
restart: always restart: always
# 如果你是 postgres 用户,应将 fba_mysql 修改为 fba_postgres # 如果你是 mysql 用户,应将 fba_postgres 修改为 fba_mysql
depends_on: depends_on:
- fba_mysql - fba_postgres
- fba_redis - fba_redis
- fba_celery - fba_celery
volumes: volumes:
@@ -39,50 +39,50 @@ services:
- fba_static_upload:/fba/backend/static/upload - fba_static_upload:/fba/backend/static/upload
networks: networks:
- fba_network - fba_network
# 如果你是 postgres 用户,应将 fba_mysql:3306 修改为 fba_postgres:5432 # 如果你是 mysql 用户,应将 fba_postgres:5432 修改为 fba_mysql:3306
command: command:
- bash - bash
- -c - -c
- | - |
wait-for-it -s fba_mysql:3306 -s fba_redis:6379 -t 300 wait-for-it -s fba_postgres:5432 -s fba_redis:6379 -t 300
supervisord -c /etc/supervisor/supervisord.conf supervisord -c /etc/supervisor/supervisord.conf
supervisorctl restart supervisorctl restart
fba_mysql: fba_postgres:
image: mysql:8.0.41 image: postgres:16
ports: ports:
- "${DOCKER_MYSQL_MAP_PORT:-3306}:3306" - "${DOCKER_POSTGRES_MAP_PORT:-5432}:5432"
container_name: fba_mysql container_name: fba_postgres
restart: always restart: always
environment: environment:
MYSQL_DATABASE: fba POSTGRES_DB: fba
MYSQL_ROOT_PASSWORD: 123456 POSTGRES_PASSWORD: 123456
TZ: Asia/Shanghai TZ: Asia/Shanghai
volumes: volumes:
- fba_mysql:/var/lib/mysql - fba_postgres:/var/lib/postgresql/data
networks: networks:
- fba_network - fba_network
command:
--default-authentication-plugin=mysql_native_password
--character-set-server=utf8mb4
--collation-server=utf8mb4_general_ci
--lower_case_table_names=1
# # 如果你是 postgres 用户,应保留 fba_postgres 容器脚本并删除 fba_mysql 容器脚本 # # 如果你是 mysql 用户,应保留 fba_mysql 容器脚本并删除 fba_postgres 容器脚本
# fba_postgres: # fba_mysql:
# image: postgres:16 # image: mysql:8.0.41
# ports: # ports:
# - "${DOCKER_POSTGRES_MAP_PORT:-5432}:5432" # - "${DOCKER_MYSQL_MAP_PORT:-3306}:3306"
# container_name: fba_postgres # container_name: fba_mysql
# restart: always # restart: always
# environment: # environment:
# POSTGRES_DB: fba # MYSQL_DATABASE: fba
# POSTGRES_PASSWORD: 123456 # MYSQL_ROOT_PASSWORD: 123456
# TZ: Asia/Shanghai # TZ: Asia/Shanghai
# volumes: # volumes:
# - fba_postgres:/var/lib/postgresql/data # - fba_mysql:/var/lib/mysql
# networks: # networks:
# - fba_network # - fba_network
# command:
# --default-authentication-plugin=mysql_native_password
# --character-set-server=utf8mb4
# --collation-server=utf8mb4_general_ci
# --lower_case_table_names=1
fba_redis: fba_redis:
image: redis image: redis
+8 -8
View File
@@ -12,7 +12,7 @@ license = { text = "MIT" }
requires-python = ">=3.10" requires-python = ">=3.10"
dynamic = ['version'] dynamic = ['version']
dependencies = [ dependencies = [
"alembic>=1.16.5", "alembic>=1.17.0",
"asgiref>=3.10.0", "asgiref>=3.10.0",
"asyncmy>=0.2.10", "asyncmy>=0.2.10",
"asyncpg>=0.30.0", "asyncpg>=0.30.0",
@@ -22,12 +22,12 @@ dependencies = [
# When celery version < 6.0.0 # When celery version < 6.0.0
# https://github.com/celery/celery/issues/7874 # https://github.com/celery/celery/issues/7874
"celery-aio-pool>=0.1.0rc8", "celery-aio-pool>=0.1.0rc8",
"cryptography>=46.0.2", "cryptography>=46.0.3",
"dulwich>=0.24.2", "dulwich>=0.24.6",
"fast-captcha>=0.3.2", "fast-captcha>=0.3.2",
"fastapi-limiter>=0.1.6", "fastapi-limiter>=0.1.6",
"fastapi-pagination>=0.14.3", "fastapi-pagination>=0.14.3",
"fastapi[standard-no-fastapi-cloud-cli]>=0.119.0", "fastapi[standard-no-fastapi-cloud-cli]>=0.119.1",
"flower>=2.0.1", "flower>=2.0.1",
"gevent>=25.9.1", "gevent>=25.9.1",
"granian>=2.5.5", "granian>=2.5.5",
@@ -36,16 +36,16 @@ dependencies = [
"jinja2>=3.1.6", "jinja2>=3.1.6",
"loguru>=0.7.3", "loguru>=0.7.3",
"msgspec>=0.19.0", "msgspec>=0.19.0",
"psutil>=7.1.0", "psutil>=7.1.1",
"psycopg[binary]>=3.2.10", "psycopg[binary]>=3.2.10",
"pwdlib>=0.2.1", "pwdlib>=0.2.1",
"pydantic>=2.12.1", "pydantic>=2.12.3",
"pydantic-settings>=2.11.0", "pydantic-settings>=2.11.0",
"pymysql>=1.1.2", "pymysql>=1.1.2",
"python-jose>=3.5.0", "python-jose>=3.5.0",
"python-socketio>=5.14.1", "python-socketio>=5.14.2",
"redis[hiredis]>=6.4.0", "redis[hiredis]>=6.4.0",
"rtoml>=0.12.0", "rtoml>=0.13.0",
"sqlalchemy-crud-plus>=1.12.1", "sqlalchemy-crud-plus>=1.12.1",
"sqlalchemy[asyncio]>=2.0.44", "sqlalchemy[asyncio]>=2.0.44",
"sqlparse>=0.5.3", "sqlparse>=0.5.3",
+17 -17
View File
@@ -69,13 +69,13 @@ colorama==0.4.6 ; sys_platform == 'win32'
# loguru # loguru
# pytest # pytest
# uvicorn # uvicorn
cryptography==46.0.2 cryptography==46.0.3
# via fastapi-best-architecture # via fastapi-best-architecture
distlib==0.4.0 distlib==0.4.0
# via virtualenv # via virtualenv
dnspython==2.8.0 dnspython==2.8.0
# via email-validator # via email-validator
dulwich==0.24.3 dulwich==0.24.6
# via fastapi-best-architecture # via fastapi-best-architecture
ecdsa==0.19.1 ecdsa==0.19.1
# via python-jose # via python-jose
@@ -87,7 +87,7 @@ exceptiongroup==1.3.0 ; python_full_version < '3.11'
# pytest # pytest
fast-captcha==0.3.2 fast-captcha==0.3.2
# via fastapi-best-architecture # via fastapi-best-architecture
fastapi==0.119.0 fastapi==0.119.1
# via # via
# fastapi-best-architecture # fastapi-best-architecture
# fastapi-limiter # fastapi-limiter
@@ -115,7 +115,7 @@ h11==0.16.0
# httpcore # httpcore
# uvicorn # uvicorn
# wsproto # wsproto
hiredis==3.2.1 hiredis==3.3.0
# via redis # via redis
httpcore==1.0.9 httpcore==1.0.9
# via httpx # via httpx
@@ -123,7 +123,7 @@ httptools==0.7.1
# via uvicorn # via uvicorn
httpx==0.28.1 httpx==0.28.1
# via fastapi # via fastapi
humanize==4.13.0 humanize==4.14.0
# via flower # via flower
identify==2.6.15 identify==2.6.15
# via pre-commit # via pre-commit
@@ -132,7 +132,7 @@ idna==3.11
# anyio # anyio
# email-validator # email-validator
# httpx # httpx
iniconfig==2.1.0 iniconfig==2.3.0
# via pytest # via pytest
ip2loc==1.0.0 ip2loc==1.0.0
# via fastapi-best-architecture # via fastapi-best-architecture
@@ -164,7 +164,7 @@ packaging==25.0
# via # via
# kombu # kombu
# pytest # pytest
pillow==11.3.0 pillow==12.0.0
# via fast-captcha # via fast-captcha
platformdirs==4.5.0 platformdirs==4.5.0
# via virtualenv # via virtualenv
@@ -175,11 +175,11 @@ prometheus-client==0.23.1
# via flower # via flower
prompt-toolkit==3.0.52 prompt-toolkit==3.0.52
# via click-repl # via click-repl
psutil==7.1.0 psutil==7.1.1
# via fastapi-best-architecture # via fastapi-best-architecture
psycopg==3.2.10 psycopg==3.2.11
# via fastapi-best-architecture # via fastapi-best-architecture
psycopg-binary==3.2.10 ; implementation_name != 'pypy' psycopg-binary==3.2.11 ; implementation_name != 'pypy'
# via psycopg # via psycopg
pwdlib==0.2.1 pwdlib==0.2.1
# via fastapi-best-architecture # via fastapi-best-architecture
@@ -189,14 +189,14 @@ pyasn1==0.6.1
# rsa # rsa
pycparser==2.23 ; implementation_name != 'PyPy' and platform_python_implementation != 'PyPy' pycparser==2.23 ; implementation_name != 'PyPy' and platform_python_implementation != 'PyPy'
# via cffi # via cffi
pydantic==2.12.1 pydantic==2.12.3
# via # via
# fastapi # fastapi
# fastapi-best-architecture # fastapi-best-architecture
# fastapi-pagination # fastapi-pagination
# pydantic-settings # pydantic-settings
# sqlalchemy-crud-plus # sqlalchemy-crud-plus
pydantic-core==2.41.3 pydantic-core==2.41.4
# via pydantic # via pydantic
pydantic-settings==2.11.0 pydantic-settings==2.11.0
# via fastapi-best-architecture # via fastapi-best-architecture
@@ -221,7 +221,7 @@ python-jose==3.5.0
# via fastapi-best-architecture # via fastapi-best-architecture
python-multipart==0.0.20 python-multipart==0.0.20
# via fastapi # via fastapi
python-socketio==5.14.1 python-socketio==5.14.2
# via fastapi-best-architecture # via fastapi-best-architecture
pytz==2025.2 pytz==2025.2
# via flower # via flower
@@ -242,7 +242,7 @@ rich-toolkit==0.15.1
# via fastapi-cli # via fastapi-cli
rsa==4.9.1 rsa==4.9.1
# via python-jose # via python-jose
rtoml==0.12.0 rtoml==0.13.0
# via fastapi-best-architecture # via fastapi-best-architecture
setuptools==80.9.0 setuptools==80.9.0
# via zope-event # via zope-event
@@ -321,11 +321,11 @@ urllib3==2.5.0
# via dulwich # via dulwich
user-agents==2.2.0 user-agents==2.2.0
# via fastapi-best-architecture # via fastapi-best-architecture
uvicorn==0.37.0 uvicorn==0.38.0
# via # via
# fastapi # fastapi
# fastapi-cli # fastapi-cli
uvloop==0.21.0 ; platform_python_implementation != 'PyPy' and sys_platform != 'cygwin' and sys_platform != 'win32' uvloop==0.22.1 ; platform_python_implementation != 'PyPy' and sys_platform != 'cygwin' and sys_platform != 'win32'
# via uvicorn # via uvicorn
vine==5.1.0 vine==5.1.0
# via # via
@@ -334,7 +334,7 @@ vine==5.1.0
# kombu # kombu
virtualenv==20.35.3 virtualenv==20.35.3
# via pre-commit # via pre-commit
watchfiles==1.1.0 watchfiles==1.1.1
# via uvicorn # via uvicorn
wcwidth==0.2.14 wcwidth==0.2.14
# via prompt-toolkit # via prompt-toolkit
Generated
+646 -606
View File
File diff suppressed because it is too large Load Diff