From aa91d90be927ab87cd3fc2823d7d8d89ed314260 Mon Sep 17 00:00:00 2001 From: RegirX Date: Wed, 28 Feb 2024 09:46:00 +0800 Subject: [PATCH] chore: rm models --- .editorconfig | 24 +- .gitignore | 952 ++++++++++++++-------------- README.md | 10 +- database.py | 56 +- generate_key.sh | 4 +- main.py | 102 +-- models/user.py | 12 - renovate.json | 12 +- requirements.txt | 78 +-- routers/activities_router.py | 1044 +++++++++++++++---------------- routers/groups_router.py | 268 ++++---- routers/notifications_router.py | 164 ++--- routers/users_router.py | 482 +++++++------- typings/activity.py | 186 +++--- typings/group.py | 58 +- typings/notification.py | 44 +- typings/trophy.py | 118 ++-- typings/user.py | 76 +-- util/.gitignore | 4 +- util/cases.py | 30 +- util/cert.py | 224 +++---- util/get_class.py | 146 ++--- util/group.py | 32 +- util/response.py | 34 +- utils.py | 208 +++--- 25 files changed, 2178 insertions(+), 2190 deletions(-) delete mode 100755 models/user.py diff --git a/.editorconfig b/.editorconfig index c1e2c64..729534f 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,12 +1,12 @@ -# EditorConfig is awesome: https://EditorConfig.org - -# top-most EditorConfig file -root = true - -[*] -indent_style = space -indent_size = 4 -end_of_line = lf -charset = utf-8 -trim_trailing_whitespace = true -insert_final_newline = true +# EditorConfig is awesome: https://EditorConfig.org + +# top-most EditorConfig file +root = true + +[*] +indent_style = space +indent_size = 4 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true diff --git a/.gitignore b/.gitignore index c886393..e14ee7e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,476 +1,476 @@ -# Byte-compiled / optimized / DLL files -__pycache__/ -*.py[cod] -*$py.class - -# C extensions -*.so - -# Distribution / packaging -.Python -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -share/python-wheels/ -*.egg-info/ -.installed.cfg -*.egg -MANIFEST - -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt - -# Unit test / coverage reports -htmlcov/ -.tox/ -.nox/ -.coverage -.coverage.* -.cache -nosetests.xml -coverage.xml -*.cover -*.py,cover -.hypothesis/ -.pytest_cache/ -cover/ - -# Translations -*.mo -*.pot - -# Django stuff: -*.log -local_settings.py -db.sqlite3 -db.sqlite3-journal - -# Flask stuff: -instance/ -.webassets-cache - -# Scrapy stuff: -.scrapy - -# Sphinx documentation -docs/_build/ - -# PyBuilder -.pybuilder/ -target/ - -# Jupyter Notebook -.ipynb_checkpoints - -# IPython -profile_default/ -ipython_config.py - -# pyenv -# For a library or package, you might want to ignore these files since the code is -# intended to run in multiple environments; otherwise, check them in: -# .python-version - -# pipenv -# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. -# However, in case of collaboration, if having platform-specific dependencies or dependencies -# having no cross-platform support, pipenv may install dependencies that don't work, or not -# install all needed dependencies. -#Pipfile.lock - -# poetry -# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. -# This is especially recommended for binary packages to ensure reproducibility, and is more -# commonly ignored for libraries. -# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control -#poetry.lock - -# pdm -# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. -#pdm.lock -# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it -# in version control. -# https://pdm.fming.dev/#use-with-ide -.pdm.toml - -# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm -__pypackages__/ - -# Celery stuff -celerybeat-schedule -celerybeat.pid - -# SageMath parsed files -*.sage.py - -# Environments -.env -.venv -env/ -venv/ -ENV/ -env.bak/ -venv.bak/ - -# Spyder project settings -.spyderproject -.spyproject - -# Rope project settings -.ropeproject - -# mkdocs documentation -/site - -# mypy -.mypy_cache/ -.dmypy.json -dmypy.json - -# Pyre type checker -.pyre/ - -# pytype static type analyzer -.pytype/ - -# Cython debug symbols -cython_debug/ - -# PyCharm -# JetBrains specific template is maintained in a separate JetBrains.gitignore that can -# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore -# and can be added to the global gitignore or merged into this file. For a more nuclear -# option (not recommended) you can uncomment the following to ignore the entire idea folder. -#.idea/ - -## Core latex/pdflatex auxiliary files: -*.aux -*.lof -*.log -*.lot -*.fls -*.out -*.toc -*.fmt -*.fot -*.cb -*.cb2 -.*.lb - -## Intermediate documents: -*.dvi -*.xdv -*-converted-to.* -# these rules might exclude image files for figures etc. -# *.ps -# *.eps -# *.pdf - -## Generated if empty string is given at "Please type another file name for output:" -.pdf - -## Bibliography auxiliary files (bibtex/biblatex/biber): -*.bbl -*.bcf -*.blg -*-blx.aux -*-blx.bib -*.run.xml - -## Build tool auxiliary files: -*.fdb_latexmk -*.synctex -*.synctex(busy) -*.synctex.gz -*.synctex.gz(busy) -*.pdfsync - -## Build tool directories for auxiliary files -# latexrun -latex.out/ - -## Auxiliary and intermediate files from other packages: -# algorithms -*.alg -*.loa - -# achemso -acs-*.bib - -# amsthm -*.thm - -# beamer -*.nav -*.pre -*.snm -*.vrb - -# changes -*.soc - -# comment -*.cut - -# cprotect -*.cpt - -# elsarticle (documentclass of Elsevier journals) -*.spl - -# endnotes -*.ent - -# fixme -*.lox - -# feynmf/feynmp -*.mf -*.mp -*.t[1-9] -*.t[1-9][0-9] -*.tfm - -#(r)(e)ledmac/(r)(e)ledpar -*.end -*.?end -*.[1-9] -*.[1-9][0-9] -*.[1-9][0-9][0-9] -*.[1-9]R -*.[1-9][0-9]R -*.[1-9][0-9][0-9]R -*.eledsec[1-9] -*.eledsec[1-9]R -*.eledsec[1-9][0-9] -*.eledsec[1-9][0-9]R -*.eledsec[1-9][0-9][0-9] -*.eledsec[1-9][0-9][0-9]R - -# glossaries -*.acn -*.acr -*.glg -*.glo -*.gls -*.glsdefs -*.lzo -*.lzs -*.slg -*.slo -*.sls - -# uncomment this for glossaries-extra (will ignore makeindex's style files!) -# *.ist - -# gnuplot -*.gnuplot -*.table - -# gnuplottex -*-gnuplottex-* - -# gregoriotex -*.gaux -*.glog -*.gtex - -# htlatex -*.4ct -*.4tc -*.idv -*.lg -*.trc -*.xref - -# hyperref -*.brf - -# knitr -*-concordance.tex -# TODO Uncomment the next line if you use knitr and want to ignore its generated tikz files -# *.tikz -*-tikzDictionary - -# listings -*.lol - -# luatexja-ruby -*.ltjruby - -# makeidx -*.idx -*.ilg -*.ind - -# minitoc -*.maf -*.mlf -*.mlt -*.mtc[0-9]* -*.slf[0-9]* -*.slt[0-9]* -*.stc[0-9]* - -# minted -_minted* -*.pyg - -# morewrites -*.mw - -# newpax -*.newpax - -# nomencl -*.nlg -*.nlo -*.nls - -# pax -*.pax - -# pdfpcnotes -*.pdfpc - -# sagetex -*.sagetex.sage -*.sagetex.py -*.sagetex.scmd - -# scrwfile -*.wrt - -# svg -svg-inkscape/ - -# sympy -*.sout -*.sympy -sympy-plots-for-*.tex/ - -# pdfcomment -*.upa -*.upb - -# pythontex -*.pytxcode -pythontex-files-*/ - -# tcolorbox -*.listing - -# thmtools -*.loe - -# TikZ & PGF -*.dpth -*.md5 -*.auxlock - -# titletoc -*.ptc - -# todonotes -*.tdo - -# vhistory -*.hst -*.ver - -# easy-todo -*.lod - -# xcolor -*.xcp - -# xmpincl -*.xmpi - -# xindy -*.xdy - -# xypic precompiled matrices and outlines -*.xyc -*.xyd - -# endfloat -*.ttt -*.fff - -# Latexian -TSWLatexianTemp* - -## Editors: -# WinEdt -*.bak -*.sav - -# Texpad -.texpadtmp - -# LyX -*.lyx~ - -# Kile -*.backup - -# gummi -.*.swp - -# KBibTeX -*~[0-9]* - -# TeXnicCenter -*.tps - -# auto folder when using emacs and auctex -./auto/* -*.el - -# expex forward references with \gathertags -*-tags.tex - -# standalone packages -*.sta - -# Makeindex log files -*.lpz - -# xwatermark package -*.xwm - -# REVTeX puts footnotes in the bibliography by default, unless the nofootinbib -# option is specified. Footnotes are the stored in a file with suffix Notes.bib. -# Uncomment the next line to have this generated file ignored. -#*Notes.bib - -data - -.DS_Store -hue.py -heu.py -hsv.txt -hsv.npy - -*.pem -*.key -aes_key.txt - -settings.py +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +## Core latex/pdflatex auxiliary files: +*.aux +*.lof +*.log +*.lot +*.fls +*.out +*.toc +*.fmt +*.fot +*.cb +*.cb2 +.*.lb + +## Intermediate documents: +*.dvi +*.xdv +*-converted-to.* +# these rules might exclude image files for figures etc. +# *.ps +# *.eps +# *.pdf + +## Generated if empty string is given at "Please type another file name for output:" +.pdf + +## Bibliography auxiliary files (bibtex/biblatex/biber): +*.bbl +*.bcf +*.blg +*-blx.aux +*-blx.bib +*.run.xml + +## Build tool auxiliary files: +*.fdb_latexmk +*.synctex +*.synctex(busy) +*.synctex.gz +*.synctex.gz(busy) +*.pdfsync + +## Build tool directories for auxiliary files +# latexrun +latex.out/ + +## Auxiliary and intermediate files from other packages: +# algorithms +*.alg +*.loa + +# achemso +acs-*.bib + +# amsthm +*.thm + +# beamer +*.nav +*.pre +*.snm +*.vrb + +# changes +*.soc + +# comment +*.cut + +# cprotect +*.cpt + +# elsarticle (documentclass of Elsevier journals) +*.spl + +# endnotes +*.ent + +# fixme +*.lox + +# feynmf/feynmp +*.mf +*.mp +*.t[1-9] +*.t[1-9][0-9] +*.tfm + +#(r)(e)ledmac/(r)(e)ledpar +*.end +*.?end +*.[1-9] +*.[1-9][0-9] +*.[1-9][0-9][0-9] +*.[1-9]R +*.[1-9][0-9]R +*.[1-9][0-9][0-9]R +*.eledsec[1-9] +*.eledsec[1-9]R +*.eledsec[1-9][0-9] +*.eledsec[1-9][0-9]R +*.eledsec[1-9][0-9][0-9] +*.eledsec[1-9][0-9][0-9]R + +# glossaries +*.acn +*.acr +*.glg +*.glo +*.gls +*.glsdefs +*.lzo +*.lzs +*.slg +*.slo +*.sls + +# uncomment this for glossaries-extra (will ignore makeindex's style files!) +# *.ist + +# gnuplot +*.gnuplot +*.table + +# gnuplottex +*-gnuplottex-* + +# gregoriotex +*.gaux +*.glog +*.gtex + +# htlatex +*.4ct +*.4tc +*.idv +*.lg +*.trc +*.xref + +# hyperref +*.brf + +# knitr +*-concordance.tex +# TODO Uncomment the next line if you use knitr and want to ignore its generated tikz files +# *.tikz +*-tikzDictionary + +# listings +*.lol + +# luatexja-ruby +*.ltjruby + +# makeidx +*.idx +*.ilg +*.ind + +# minitoc +*.maf +*.mlf +*.mlt +*.mtc[0-9]* +*.slf[0-9]* +*.slt[0-9]* +*.stc[0-9]* + +# minted +_minted* +*.pyg + +# morewrites +*.mw + +# newpax +*.newpax + +# nomencl +*.nlg +*.nlo +*.nls + +# pax +*.pax + +# pdfpcnotes +*.pdfpc + +# sagetex +*.sagetex.sage +*.sagetex.py +*.sagetex.scmd + +# scrwfile +*.wrt + +# svg +svg-inkscape/ + +# sympy +*.sout +*.sympy +sympy-plots-for-*.tex/ + +# pdfcomment +*.upa +*.upb + +# pythontex +*.pytxcode +pythontex-files-*/ + +# tcolorbox +*.listing + +# thmtools +*.loe + +# TikZ & PGF +*.dpth +*.md5 +*.auxlock + +# titletoc +*.ptc + +# todonotes +*.tdo + +# vhistory +*.hst +*.ver + +# easy-todo +*.lod + +# xcolor +*.xcp + +# xmpincl +*.xmpi + +# xindy +*.xdy + +# xypic precompiled matrices and outlines +*.xyc +*.xyd + +# endfloat +*.ttt +*.fff + +# Latexian +TSWLatexianTemp* + +## Editors: +# WinEdt +*.bak +*.sav + +# Texpad +.texpadtmp + +# LyX +*.lyx~ + +# Kile +*.backup + +# gummi +.*.swp + +# KBibTeX +*~[0-9]* + +# TeXnicCenter +*.tps + +# auto folder when using emacs and auctex +./auto/* +*.el + +# expex forward references with \gathertags +*-tags.tex + +# standalone packages +*.sta + +# Makeindex log files +*.lpz + +# xwatermark package +*.xwm + +# REVTeX puts footnotes in the bibliography by default, unless the nofootinbib +# option is specified. Footnotes are the stored in a file with suffix Notes.bib. +# Uncomment the next line to have this generated file ignored. +#*Notes.bib + +data + +.DS_Store +hue.py +heu.py +hsv.txt +hsv.npy + +*.pem +*.key +aes_key.txt + +settings.py diff --git a/README.md b/README.md index 3455344..8437d80 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -# ZVMS Backend - -## RSA Key Generation - -You need to refer [7086cmd's repo](https://github.com/zvms/rsa-bcrypt-jwt-login-eg). +# ZVMS Backend + +## RSA Key Generation + +You need to refer [7086cmd's repo](https://github.com/zvms/rsa-bcrypt-jwt-login-eg). diff --git a/database.py b/database.py index 3eb1d63..7d79521 100755 --- a/database.py +++ b/database.py @@ -1,28 +1,28 @@ -from motor.motor_asyncio import AsyncIOMotorClient, AsyncIOMotorDatabase -import settings -import logging - - -class DataBase: - client: AsyncIOMotorClient = None # type: ignore - zvms: AsyncIOMotorDatabase = None # type: ignore - - -db = DataBase() - - -async def connect_to_mongo(): - logging.info("Connecting to mongo...") - db.client = AsyncIOMotorClient(settings.MONGODB_URI, - maxPoolSize=10, - minPoolSize=10) - db.zvms = db.client['zvms'] - # 获取 client 所有 database 名称 - print(await db.client.list_database_names()) - logging.info("connected to zvms...") - - -async def close_mongo_connection(): - logging.info("closing connection...") - db.client.close() - logging.info("closed connection") +from motor.motor_asyncio import AsyncIOMotorClient, AsyncIOMotorDatabase +import settings +import logging + + +class DataBase: + client: AsyncIOMotorClient = None # type: ignore + zvms: AsyncIOMotorDatabase = None # type: ignore + + +db = DataBase() + + +async def connect_to_mongo(): + logging.info("Connecting to mongo...") + db.client = AsyncIOMotorClient(settings.MONGODB_URI, + maxPoolSize=10, + minPoolSize=10) + db.zvms = db.client['zvms'] + # 获取 client 所有 database 名称 + print(await db.client.list_database_names()) + logging.info("connected to zvms...") + + +async def close_mongo_connection(): + logging.info("closing connection...") + db.client.close() + logging.info("closed connection") diff --git a/generate_key.sh b/generate_key.sh index 2061ed7..fecf89d 100644 --- a/generate_key.sh +++ b/generate_key.sh @@ -1,3 +1,3 @@ -openssl genrsa -out rsa_private_key.pem 1024 -openssl rsa -in rsa_private_key.pem -pubout -out rsa_public_key.pem +openssl genrsa -out rsa_private_key.pem 1024 +openssl rsa -in rsa_private_key.pem -pubout -out rsa_public_key.pem openssl rand -hex 32 > aes_key.txt \ No newline at end of file diff --git a/main.py b/main.py index fe182fa..1238b03 100755 --- a/main.py +++ b/main.py @@ -1,51 +1,51 @@ -import code -from fastapi import FastAPI, Depends -from bson.objectid import ObjectId -from typing import List -from routers import notifications_router, users_router, activities_router, groups_router -from fastapi import FastAPI -from database import close_mongo_connection, connect_to_mongo -from fastapi.middleware.cors import CORSMiddleware - -app = FastAPI() - -app.add_middleware( - CORSMiddleware, - allow_origins=["*"], - allow_credentials=True, - allow_methods=["*"], - allow_headers=["*"], -) - -# 注册事件 -app.add_event_handler("startup", connect_to_mongo) -app.add_event_handler("shutdown", close_mongo_connection) - -# 注册路由 -app.include_router(users_router.router, prefix="/api/user", tags=["users"]) -app.include_router( - activities_router.router, prefix="/api/activity", tags=["activities"] -) -app.include_router( - notifications_router.router, prefix="/api/notification", tags=["notifications"] -) -app.include_router(groups_router.router, prefix="/api/group", tags=["groups"]) - -@app.get("/api/cert") -async def get_cert(): - return { - "status": "ok", - "code": 200, - "data": open("./rsa_public_key.pem", "r").read(), - } - - -@app.get("/api/version") -async def get_version(): - return {"status": "ok", "code": 200, "data": "0.1.0-alpha.1"} - - -if __name__ == "__main__": - import uvicorn - - uvicorn.run(app=app, host="0.0.0.0", port=8000) +import code +from fastapi import FastAPI, Depends +from bson.objectid import ObjectId +from typing import List +from routers import notifications_router, users_router, activities_router, groups_router +from fastapi import FastAPI +from database import close_mongo_connection, connect_to_mongo +from fastapi.middleware.cors import CORSMiddleware + +app = FastAPI() + +app.add_middleware( + CORSMiddleware, + allow_origins=["*"], + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + +# 注册事件 +app.add_event_handler("startup", connect_to_mongo) +app.add_event_handler("shutdown", close_mongo_connection) + +# 注册路由 +app.include_router(users_router.router, prefix="/api/user", tags=["users"]) +app.include_router( + activities_router.router, prefix="/api/activity", tags=["activities"] +) +app.include_router( + notifications_router.router, prefix="/api/notification", tags=["notifications"] +) +app.include_router(groups_router.router, prefix="/api/group", tags=["groups"]) + +@app.get("/api/cert") +async def get_cert(): + return { + "status": "ok", + "code": 200, + "data": open("./rsa_public_key.pem", "r").read(), + } + + +@app.get("/api/version") +async def get_version(): + return {"status": "ok", "code": 200, "data": "0.1.0-alpha.1"} + + +if __name__ == "__main__": + import uvicorn + + uvicorn.run(app=app, host="0.0.0.0", port=8000) diff --git a/models/user.py b/models/user.py deleted file mode 100755 index 2ccd6e3..0000000 --- a/models/user.py +++ /dev/null @@ -1,12 +0,0 @@ -from pydantic import BaseModel -from typing import Optional - -class User(BaseModel): - _id: str # 用户 UUID(数据库 oid) - username: str # 用户名(学号) - password_hashed: str # 密码(MD5) - realname: str # 真实姓名 - permission: int # 权限等级 - -class TokenData(BaseModel): - username: Optional[str] = None diff --git a/renovate.json b/renovate.json index 93afd4a..8a4dad9 100644 --- a/renovate.json +++ b/renovate.json @@ -1,6 +1,6 @@ -{ - "$schema": "https://docs.renovatebot.com/renovate-schema.json", - "extends": [ - "config:recommended" - ] -} +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": [ + "config:recommended" + ] +} diff --git a/requirements.txt b/requirements.txt index 28bdd0b..4f06711 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,39 +1,39 @@ -annotated-types==0.6.0 -anyio==4.2.0 -bcrypt==4.1.2 -certifi==2024.2.2 -charset-normalizer==3.3.2 -click==8.1.7 -dnspython==2.6.0 -docopt==0.6.2 -ecdsa==0.18.0 -fastapi==0.109.2 -h11==0.14.0 -httptools==0.6.1 -idna==3.6 -motor==3.3.2 -pipreqs==0.4.13 -pyasn1==0.5.1 -pycryptodome==3.20.0 -pydantic==2.6.1 -pydantic_core==2.16.2 -PyJWT==2.8.0 -pymongo==4.6.1 -python-dotenv==1.0.1 -python-jose==3.3.0 -python-multipart==0.0.9 -PyYAML==6.0.1 -requests==2.31.0 -rsa==4.9 -setuptools==69.0.3 -six==1.16.0 -sniffio==1.3.0 -starlette==0.36.3 -typing_extensions==4.9.0 -urllib3==2.2.0 -uvicorn==0.27.1 -uvloop==0.19.0 -watchfiles==0.21.0 -websockets==12.0 -wheel==0.42.0 -yarg==0.1.9 +annotated-types==0.6.0 +anyio==4.2.0 +bcrypt==4.1.2 +certifi==2024.2.2 +charset-normalizer==3.3.2 +click==8.1.7 +dnspython==2.6.0 +docopt==0.6.2 +ecdsa==0.18.0 +fastapi==0.109.2 +h11==0.14.0 +httptools==0.6.1 +idna==3.6 +motor==3.3.2 +pipreqs==0.4.13 +pyasn1==0.5.1 +pycryptodome==3.20.0 +pydantic==2.6.1 +pydantic_core==2.16.2 +PyJWT==2.8.0 +pymongo==4.6.1 +python-dotenv==1.0.1 +python-jose==3.3.0 +python-multipart==0.0.9 +PyYAML==6.0.1 +requests==2.31.0 +rsa==4.9 +setuptools==69.0.3 +six==1.16.0 +sniffio==1.3.0 +starlette==0.36.3 +typing_extensions==4.9.0 +urllib3==2.2.0 +uvicorn==0.27.1 +uvloop==0.19.0 +watchfiles==0.21.0 +websockets==12.0 +wheel==0.42.0 +yarg==0.1.9 diff --git a/routers/activities_router.py b/routers/activities_router.py index 0c4170d..bada276 100755 --- a/routers/activities_router.py +++ b/routers/activities_router.py @@ -1,522 +1,522 @@ -from typings.activity import ( - Activity, - ActivityMember, - ActivityStatus, - ActivityType, - MemberActivityStatus, - SpecialActivityClassify, -) -from bson import ObjectId -from fastapi import APIRouter, HTTPException, Depends, Form, Request -from typing import List -from util.get_class import get_activities_related_to_user, get_classid_by_code, get_classid_by_user_id -from utils import compulsory_temporary_token, get_current_user, validate_object_id, timestamp_change -from datetime import datetime, timedelta, timezone -from jose import JWTError, jwt -from database import db -from pydantic import BaseModel, Field -from typing import Optional -import settings - -router = APIRouter() - - -@router.post("") -async def create_activity(payload: Activity, user=Depends(get_current_user)): - """ - Create activity - """ - - # remove _id - - none_permission = len(user["per"]) == 1 and "student" in user["per"] - only_secretary = ( - len(user["per"]) == 2 - and "secretary" in user["per"] - and "student" in user["per"] - ) - - payload.creator = user["id"] - - if payload.type == ActivityType.special and payload.special is None: - raise HTTPException( - status_code=400, detail="Special activity must have a classify" - ) - - if payload.type == ActivityType.specified and payload.registration is None: - raise HTTPException( - status_code=400, detail="Specified activity must have a registration" - ) - - if ( - none_permission - and payload.type == ActivityType.social - or payload.type == ActivityType.scale - ): - payload.status = ActivityStatus.pending - elif ( - none_permission - and payload.type == ActivityType.specified - or payload.type == ActivityType.special - ): - raise HTTPException(status_code=403, detail="Permission denied") - elif ( - only_secretary - and payload.type == ActivityType.social - or payload.type == ActivityType.scale - ): - payload.status = ActivityStatus.effective - elif only_secretary and payload.type == ActivityType.specified: - payload.status = ActivityStatus.pending - elif only_secretary and payload.type == ActivityType.special: - raise HTTPException(status_code=403, detail="Permission denied") - elif ( - "admin" not in user["per"] - and payload.type == ActivityType.special - and payload.special is not None - and payload.special.classify is not None - and payload.special.classify == SpecialActivityClassify.import_ - ): - raise HTTPException(status_code=403, detail="Permission denied") - - diction = payload.model_dump() - - members = diction["members"] - - for member in members: - member["_id"] = member["id"] - del member["id"] - - diction["members"] = members - - # Crezate activity - result = await db.zvms.activities.insert_one(diction) - - id = result.inserted_id - - return {"status": "ok", "code": 201, "data": str(id)} - - -class PutDescription(BaseModel): - description: str - -@router.put("/{activity_oid}/description") -async def change_activity_description( - activity_oid: str, payload: PutDescription, user=Depends(get_current_user) -): - """ - Edit activity description - """ - description = payload.description - # Check permission - if user["id"] != validate_object_id(activity_oid) and "admin" not in user["per"]: - raise HTTPException(status_code=403, detail="Permission denied") - - # Edit activity description - await db.zvms.activities.update_one( - {"_id": validate_object_id(activity_oid)}, - { - "$set": { - "description": description, - "updatedAt": int(datetime.now().timestamp()), - } - }, - ) - - return { - "status": "ok", - "code": 200, - } - - -class PutActivityName(BaseModel): - name: str - -@router.put("/{activity_oid}/name") -async def change_activity_title( - activity_oid: str, payload: PutActivityName, user=Depends(get_current_user) -): - """ - Modify Activity Title - """ - name = payload.name - # Check permission - if user["id"] != validate_object_id(activity_oid) and "admin" not in user["per"]: - raise HTTPException(status_code=403, detail="Permission denied") - - # 修改义工标题 - await db.zvms.activities.update_one( - {"_id": validate_object_id(activity_oid)}, - {"$set": {"name": name, "updatedAt": int(datetime.now().timestamp())}}, - ) - - return { - "status": "ok", - "code": 200, - } - -class PutActivityStatus(BaseModel): - status: str - -@router.put("/{activity_oid}/status") -async def change_activity_status( - activity_oid: str, payload: PutActivityStatus, user=Depends(get_current_user) -): - """ - Modify activity status - """ - - status = payload.status - - target_activity = await db.zvms.activities.find_one( - {"_id": validate_object_id(activity_oid)} - ) - # Check user permission - if ( - "secretary" not in user["per"] - and "department" not in user["per"] - and "admin" not in user["per"] - and (target_activity["type"] == "social" or target_activity["type"] == "scale") - ): - raise HTTPException(status_code=403, detail="Permission denied") - if ( - "department" not in user["per"] - and "admin" not in user["per"] - and (target_activity["type"] == "specified") - ): - raise HTTPException(status_code=403, detail="Permission denied") - - # Update activity status - await db.zvms.activities.update_one( - {"_id": validate_object_id(activity_oid)}, - {"$set": {"status": status, "updatedAt": int(datetime.now().timestamp())}}, - ) - - return { - "status": "ok", - "code": 200, - } - - -@router.get("") -async def read_activities( - type: str | None, - mode: str, - user=Depends(get_current_user), -): - """ - Return activities - """ - - # User permission check - if ( - "admin" not in user["per"] - and "auditor" not in user["per"] - and "department" not in user["per"] - and mode == "campus" - ): - raise HTTPException(status_code=403, detail="Permission denied") - elif ( - "secretary" not in user["per"] - and "admin" not in user["per"] - and mode == "class" - ): - raise HTTPException(status_code=403, detail="Permission denied") - if mode == "campus": - # 读取义工列表 - cursor = db.zvms.activities.find() - activities = await cursor.to_list(length=1500) - result = list() - for activity in activities: - activity["_id"] = str(activity["_id"]) - if type is None or activity["type"] == type or type == "all": - # 遍历 activity 将所有 $OID 转换为 str - for key in activity: - if isinstance(activity[key], ObjectId): - activity[key] = str(activity[key]) - result.append(activity) - return {"status": "ok", "code": 200, "data": result} - elif mode == "class": - result = await get_activities_related_to_user(user["id"]) - return {"status": "ok", "code": 200, "data": result} - - -@router.get("/{activity_oid}") -async def read_activity(activity_oid: str, user=Depends(get_current_user)): - """ - Return activity - """ - # Read activity - activity = await db.zvms.activities.find_one( - {"_id": validate_object_id(activity_oid)} - ) - if not activity: - raise HTTPException(status_code=404, detail="Activity not found") - - # Change ObjectId to str - for key in activity: - if isinstance(activity[key], ObjectId): - activity[key] = str(activity[key]) - return {"status": "ok", "code": 200, "data": activity} - - -@router.post("/{activity_oid}/member") -async def user_activity_signup(activity_oid: str, member: ActivityMember, user=Depends(get_current_user)): - """ - Append user to activity - If user doesn't have permission, regard as a registration. Check the register limit, if full, raise 403. - If user is department, directly append user to activity if the activity is created by the department. - If user is secretary, user is allowed to append user who is in the same class. - Admin is allowed to append user to any activity. - """ - - # Read activity - activity = await db.zvms.activities.find_one( - {"_id": validate_object_id(activity_oid)} - ) - - target_classid = get_classid_by_user_id(user["id"]) - - # Check available if user doesn't have any other permission - _flag = False - if 'secretary' not in user["per"] and 'admin' not in user["per"] and 'department' not in user['per']: - for i in activity["registration"]["classes"]: - if target_classid == i["classid"]: - members_in_class = list(filter(lambda x: get_classid_by_user_id(x["_id"]) == target_classid, activity["members"])) - if len(members_in_class) >= i["max"]: - raise HTTPException(status_code=403, detail="Permission denied, full.") - else: - _flag = True - if not _flag: - raise HTTPException(status_code=403, detail="Permission denied, not in class.") - else: - member.status = MemberActivityStatus.draft - if activity['type'] != ActivityType.specified: - raise HTTPException(status_code=403, detail="Permission denied, cannot be appended to this activity.") - elif 'secretary' in user["per"] and 'department' not in user["per"]: - member.status = MemberActivityStatus.draft - user_classid = get_classid_by_user_id(user["id"]) - target_classid = get_classid_by_user_id(member.id) - if user_classid != target_classid: - raise HTTPException(status_code=403, detail="Permission denied, not in class.") - if activity['type'] == ActivityType.special: - raise HTTPException(status_code=403, detail="Permission denied, cannot be appended to this activity.") - elif 'department' in user["per"] or 'admin' in user["per"]: - status = MemberActivityStatus.effective if activity['type'] == ActivityType.special else MemberActivityStatus.pending - member.status = status - else: - raise HTTPException(status_code=403, detail="Permission denied.") - - diction = member.model_dump() - diction["_id"] = diction["id"] - del diction["id"] - - # Append user to activity - await db.zvms.activities.update_one( - {"_id": validate_object_id(activity_oid)}, - { - "$addToSet": { - "members": diction - } - }, - ) - - return { - "status": "ok", - "code": 201, - } - - -@router.delete("/{activity_oid}/member/{uid}") -async def user_activity_signoff( - activity_oid: str, uid: str, user=Depends(compulsory_temporary_token) -): - """ - User exit activity or admin remove user from activity - """ - - # Check if member in activity - activity = await db.zvms.activities.find_one( - {"_id": validate_object_id(activity_oid)} - ) - - _flag = False - for member in activity["members"]: - if member["_id"] == uid: - _flag = True - break - if not _flag: - raise HTTPException(status_code=400, detail="User not in activity") - # Check user permission - if user["id"] != str(validate_object_id(uid)) and ("admin" not in user["per"] and "department" not in user["per"]): - raise HTTPException(status_code=403, detail="Permission denied") - - # Remove user from activity - target = [] - for member in activity["members"]: - if member["_id"] != uid: - target.append(member) - - # Update activity - result = await db.zvms.activities.update_one( - {"_id": validate_object_id(activity_oid)}, {"$set": {"members": target}} - ) - - return { - "status": "ok", - "code": 200, - } - - -class PutImpression(BaseModel): - impression: str - - -@router.put("/{activity_oid}/member/{id}/impression") -async def user_impression_edit( - activity_oid: str, - id: str, - impression: PutImpression, - user=Depends(get_current_user), -): - """ - User modify activity impression - """ - - result = impression.impression - - # Fetch activity - activity = await db.zvms.activities.find_one( - {"_id": validate_object_id(activity_oid)} - ) - - # Check if user is in activity - _flag = False - for member in activity["members"]: - if member["_id"] == id: - _flag = True - break - if not _flag: - raise HTTPException( - status_code=403, detail="Permission denied, not in activity." - ) - - # Check user permission - if user["id"] != str(validate_object_id(id)) and "admin" not in user["per"]: - raise HTTPException(status_code=403, detail="Permission denied") - - # Modify user impression - await db.zvms.activities.update_one( - {"_id": validate_object_id(activity_oid), "members._id": id}, - {"$set": {"members.$.impression": result}}, - ) - - return { - "status": "ok", - "code": 200, - } - - -class PutStatus(BaseModel): - status: MemberActivityStatus - - -@router.put("/{activity_oid}/member/{user_oid}/status") -async def user_status_edit( - activity_oid: str, user_oid: str, payload: PutStatus, user=Depends(get_current_user) -): - """ - User modify activity status - """ - - # Get target activity - status = payload.status - - # Get activity information - activity = await db.zvms.activities.find_one( - {"_id": validate_object_id(activity_oid)} - ) - - # Check if user is in activity - _flag = False - for member in activity["members"]: - if member["_id"] == user_oid: - _flag = True - break - if not _flag: - raise HTTPException(status_code=400, detail="User not in activity") - - # Check user status - if ( - "auditor" not in user["per"] - and "admin" not in user["per"] - and status != "pending" - and status != "draft" - ): - raise HTTPException( - status_code=403, detail="Permission denied, not enough permission" - ) - - activity = await db.zvms.activities.find_one( - {"_id": validate_object_id(activity_oid)} - ) - - member = None - - for i in activity["members"]: - if i["_id"] == user_oid: - member = i - break - - if member is None: - raise HTTPException(status_code=400, detail="User not in activity") - - if member["status"] == "effective" or member["status"] == "refused": - raise HTTPException(status_code=400, detail="User status cannot be changed") - - if user["id"] != user_oid and (status == "draft" or status == "pending"): - raise HTTPException( - status_code=403, - detail="Permission denied. This action is only allowed to be done by the user himself / herself", - ) - - # Modify user status - await db.zvms.activities.update_one( - {"_id": validate_object_id(activity_oid), "members._id": user_oid}, - {"$set": {"members.$.status": status}}, - ) - - return { - "status": "ok", - "code": 200, - } - - -@router.delete("/{activity_oid}") -async def delete_activity(activity_oid: str, user=Depends(compulsory_temporary_token)): - """ - Remove activity - """ - - activity = await db.zvms.activities.find_one( - {"_id": validate_object_id(activity_oid)} - ) - - if not activity: - raise HTTPException(status_code=404, detail="Activity not found") - - if ( - user["id"] != activity["creator"] - and "admin" not in user["per"] - and "department" not in user["per"] - ): - raise HTTPException(status_code=403, detail="Permission denied") - - result = await db.zvms.activities.delete_one( - {"_id": validate_object_id(activity_oid)} - ) - - return { - "status": "ok", - "code": 200, - } +from typings.activity import ( + Activity, + ActivityMember, + ActivityStatus, + ActivityType, + MemberActivityStatus, + SpecialActivityClassify, +) +from bson import ObjectId +from fastapi import APIRouter, HTTPException, Depends, Form, Request +from typing import List +from util.get_class import get_activities_related_to_user, get_classid_by_code, get_classid_by_user_id +from utils import compulsory_temporary_token, get_current_user, validate_object_id, timestamp_change +from datetime import datetime, timedelta, timezone +from jose import JWTError, jwt +from database import db +from pydantic import BaseModel, Field +from typing import Optional +import settings + +router = APIRouter() + + +@router.post("") +async def create_activity(payload: Activity, user=Depends(get_current_user)): + """ + Create activity + """ + + # remove _id + + none_permission = len(user["per"]) == 1 and "student" in user["per"] + only_secretary = ( + len(user["per"]) == 2 + and "secretary" in user["per"] + and "student" in user["per"] + ) + + payload.creator = user["id"] + + if payload.type == ActivityType.special and payload.special is None: + raise HTTPException( + status_code=400, detail="Special activity must have a classify" + ) + + if payload.type == ActivityType.specified and payload.registration is None: + raise HTTPException( + status_code=400, detail="Specified activity must have a registration" + ) + + if ( + none_permission + and payload.type == ActivityType.social + or payload.type == ActivityType.scale + ): + payload.status = ActivityStatus.pending + elif ( + none_permission + and payload.type == ActivityType.specified + or payload.type == ActivityType.special + ): + raise HTTPException(status_code=403, detail="Permission denied") + elif ( + only_secretary + and payload.type == ActivityType.social + or payload.type == ActivityType.scale + ): + payload.status = ActivityStatus.effective + elif only_secretary and payload.type == ActivityType.specified: + payload.status = ActivityStatus.pending + elif only_secretary and payload.type == ActivityType.special: + raise HTTPException(status_code=403, detail="Permission denied") + elif ( + "admin" not in user["per"] + and payload.type == ActivityType.special + and payload.special is not None + and payload.special.classify is not None + and payload.special.classify == SpecialActivityClassify.import_ + ): + raise HTTPException(status_code=403, detail="Permission denied") + + diction = payload.model_dump() + + members = diction["members"] + + for member in members: + member["_id"] = member["id"] + del member["id"] + + diction["members"] = members + + # Crezate activity + result = await db.zvms.activities.insert_one(diction) + + id = result.inserted_id + + return {"status": "ok", "code": 201, "data": str(id)} + + +class PutDescription(BaseModel): + description: str + +@router.put("/{activity_oid}/description") +async def change_activity_description( + activity_oid: str, payload: PutDescription, user=Depends(get_current_user) +): + """ + Edit activity description + """ + description = payload.description + # Check permission + if user["id"] != validate_object_id(activity_oid) and "admin" not in user["per"]: + raise HTTPException(status_code=403, detail="Permission denied") + + # Edit activity description + await db.zvms.activities.update_one( + {"_id": validate_object_id(activity_oid)}, + { + "$set": { + "description": description, + "updatedAt": int(datetime.now().timestamp()), + } + }, + ) + + return { + "status": "ok", + "code": 200, + } + + +class PutActivityName(BaseModel): + name: str + +@router.put("/{activity_oid}/name") +async def change_activity_title( + activity_oid: str, payload: PutActivityName, user=Depends(get_current_user) +): + """ + Modify Activity Title + """ + name = payload.name + # Check permission + if user["id"] != validate_object_id(activity_oid) and "admin" not in user["per"]: + raise HTTPException(status_code=403, detail="Permission denied") + + # 修改义工标题 + await db.zvms.activities.update_one( + {"_id": validate_object_id(activity_oid)}, + {"$set": {"name": name, "updatedAt": int(datetime.now().timestamp())}}, + ) + + return { + "status": "ok", + "code": 200, + } + +class PutActivityStatus(BaseModel): + status: str + +@router.put("/{activity_oid}/status") +async def change_activity_status( + activity_oid: str, payload: PutActivityStatus, user=Depends(get_current_user) +): + """ + Modify activity status + """ + + status = payload.status + + target_activity = await db.zvms.activities.find_one( + {"_id": validate_object_id(activity_oid)} + ) + # Check user permission + if ( + "secretary" not in user["per"] + and "department" not in user["per"] + and "admin" not in user["per"] + and (target_activity["type"] == "social" or target_activity["type"] == "scale") + ): + raise HTTPException(status_code=403, detail="Permission denied") + if ( + "department" not in user["per"] + and "admin" not in user["per"] + and (target_activity["type"] == "specified") + ): + raise HTTPException(status_code=403, detail="Permission denied") + + # Update activity status + await db.zvms.activities.update_one( + {"_id": validate_object_id(activity_oid)}, + {"$set": {"status": status, "updatedAt": int(datetime.now().timestamp())}}, + ) + + return { + "status": "ok", + "code": 200, + } + + +@router.get("") +async def read_activities( + type: str | None, + mode: str, + user=Depends(get_current_user), +): + """ + Return activities + """ + + # User permission check + if ( + "admin" not in user["per"] + and "auditor" not in user["per"] + and "department" not in user["per"] + and mode == "campus" + ): + raise HTTPException(status_code=403, detail="Permission denied") + elif ( + "secretary" not in user["per"] + and "admin" not in user["per"] + and mode == "class" + ): + raise HTTPException(status_code=403, detail="Permission denied") + if mode == "campus": + # 读取义工列表 + cursor = db.zvms.activities.find() + activities = await cursor.to_list(length=1500) + result = list() + for activity in activities: + activity["_id"] = str(activity["_id"]) + if type is None or activity["type"] == type or type == "all": + # 遍历 activity 将所有 $OID 转换为 str + for key in activity: + if isinstance(activity[key], ObjectId): + activity[key] = str(activity[key]) + result.append(activity) + return {"status": "ok", "code": 200, "data": result} + elif mode == "class": + result = await get_activities_related_to_user(user["id"]) + return {"status": "ok", "code": 200, "data": result} + + +@router.get("/{activity_oid}") +async def read_activity(activity_oid: str, user=Depends(get_current_user)): + """ + Return activity + """ + # Read activity + activity = await db.zvms.activities.find_one( + {"_id": validate_object_id(activity_oid)} + ) + if not activity: + raise HTTPException(status_code=404, detail="Activity not found") + + # Change ObjectId to str + for key in activity: + if isinstance(activity[key], ObjectId): + activity[key] = str(activity[key]) + return {"status": "ok", "code": 200, "data": activity} + + +@router.post("/{activity_oid}/member") +async def user_activity_signup(activity_oid: str, member: ActivityMember, user=Depends(get_current_user)): + """ + Append user to activity + If user doesn't have permission, regard as a registration. Check the register limit, if full, raise 403. + If user is department, directly append user to activity if the activity is created by the department. + If user is secretary, user is allowed to append user who is in the same class. + Admin is allowed to append user to any activity. + """ + + # Read activity + activity = await db.zvms.activities.find_one( + {"_id": validate_object_id(activity_oid)} + ) + + target_classid = get_classid_by_user_id(user["id"]) + + # Check available if user doesn't have any other permission + _flag = False + if 'secretary' not in user["per"] and 'admin' not in user["per"] and 'department' not in user['per']: + for i in activity["registration"]["classes"]: + if target_classid == i["classid"]: + members_in_class = list(filter(lambda x: get_classid_by_user_id(x["_id"]) == target_classid, activity["members"])) + if len(members_in_class) >= i["max"]: + raise HTTPException(status_code=403, detail="Permission denied, full.") + else: + _flag = True + if not _flag: + raise HTTPException(status_code=403, detail="Permission denied, not in class.") + else: + member.status = MemberActivityStatus.draft + if activity['type'] != ActivityType.specified: + raise HTTPException(status_code=403, detail="Permission denied, cannot be appended to this activity.") + elif 'secretary' in user["per"] and 'department' not in user["per"]: + member.status = MemberActivityStatus.draft + user_classid = get_classid_by_user_id(user["id"]) + target_classid = get_classid_by_user_id(member.id) + if user_classid != target_classid: + raise HTTPException(status_code=403, detail="Permission denied, not in class.") + if activity['type'] == ActivityType.special: + raise HTTPException(status_code=403, detail="Permission denied, cannot be appended to this activity.") + elif 'department' in user["per"] or 'admin' in user["per"]: + status = MemberActivityStatus.effective if activity['type'] == ActivityType.special else MemberActivityStatus.pending + member.status = status + else: + raise HTTPException(status_code=403, detail="Permission denied.") + + diction = member.model_dump() + diction["_id"] = diction["id"] + del diction["id"] + + # Append user to activity + await db.zvms.activities.update_one( + {"_id": validate_object_id(activity_oid)}, + { + "$addToSet": { + "members": diction + } + }, + ) + + return { + "status": "ok", + "code": 201, + } + + +@router.delete("/{activity_oid}/member/{uid}") +async def user_activity_signoff( + activity_oid: str, uid: str, user=Depends(compulsory_temporary_token) +): + """ + User exit activity or admin remove user from activity + """ + + # Check if member in activity + activity = await db.zvms.activities.find_one( + {"_id": validate_object_id(activity_oid)} + ) + + _flag = False + for member in activity["members"]: + if member["_id"] == uid: + _flag = True + break + if not _flag: + raise HTTPException(status_code=400, detail="User not in activity") + # Check user permission + if user["id"] != str(validate_object_id(uid)) and ("admin" not in user["per"] and "department" not in user["per"]): + raise HTTPException(status_code=403, detail="Permission denied") + + # Remove user from activity + target = [] + for member in activity["members"]: + if member["_id"] != uid: + target.append(member) + + # Update activity + result = await db.zvms.activities.update_one( + {"_id": validate_object_id(activity_oid)}, {"$set": {"members": target}} + ) + + return { + "status": "ok", + "code": 200, + } + + +class PutImpression(BaseModel): + impression: str + + +@router.put("/{activity_oid}/member/{id}/impression") +async def user_impression_edit( + activity_oid: str, + id: str, + impression: PutImpression, + user=Depends(get_current_user), +): + """ + User modify activity impression + """ + + result = impression.impression + + # Fetch activity + activity = await db.zvms.activities.find_one( + {"_id": validate_object_id(activity_oid)} + ) + + # Check if user is in activity + _flag = False + for member in activity["members"]: + if member["_id"] == id: + _flag = True + break + if not _flag: + raise HTTPException( + status_code=403, detail="Permission denied, not in activity." + ) + + # Check user permission + if user["id"] != str(validate_object_id(id)) and "admin" not in user["per"]: + raise HTTPException(status_code=403, detail="Permission denied") + + # Modify user impression + await db.zvms.activities.update_one( + {"_id": validate_object_id(activity_oid), "members._id": id}, + {"$set": {"members.$.impression": result}}, + ) + + return { + "status": "ok", + "code": 200, + } + + +class PutStatus(BaseModel): + status: MemberActivityStatus + + +@router.put("/{activity_oid}/member/{user_oid}/status") +async def user_status_edit( + activity_oid: str, user_oid: str, payload: PutStatus, user=Depends(get_current_user) +): + """ + User modify activity status + """ + + # Get target activity + status = payload.status + + # Get activity information + activity = await db.zvms.activities.find_one( + {"_id": validate_object_id(activity_oid)} + ) + + # Check if user is in activity + _flag = False + for member in activity["members"]: + if member["_id"] == user_oid: + _flag = True + break + if not _flag: + raise HTTPException(status_code=400, detail="User not in activity") + + # Check user status + if ( + "auditor" not in user["per"] + and "admin" not in user["per"] + and status != "pending" + and status != "draft" + ): + raise HTTPException( + status_code=403, detail="Permission denied, not enough permission" + ) + + activity = await db.zvms.activities.find_one( + {"_id": validate_object_id(activity_oid)} + ) + + member = None + + for i in activity["members"]: + if i["_id"] == user_oid: + member = i + break + + if member is None: + raise HTTPException(status_code=400, detail="User not in activity") + + if member["status"] == "effective" or member["status"] == "refused": + raise HTTPException(status_code=400, detail="User status cannot be changed") + + if user["id"] != user_oid and (status == "draft" or status == "pending"): + raise HTTPException( + status_code=403, + detail="Permission denied. This action is only allowed to be done by the user himself / herself", + ) + + # Modify user status + await db.zvms.activities.update_one( + {"_id": validate_object_id(activity_oid), "members._id": user_oid}, + {"$set": {"members.$.status": status}}, + ) + + return { + "status": "ok", + "code": 200, + } + + +@router.delete("/{activity_oid}") +async def delete_activity(activity_oid: str, user=Depends(compulsory_temporary_token)): + """ + Remove activity + """ + + activity = await db.zvms.activities.find_one( + {"_id": validate_object_id(activity_oid)} + ) + + if not activity: + raise HTTPException(status_code=404, detail="Activity not found") + + if ( + user["id"] != activity["creator"] + and "admin" not in user["per"] + and "department" not in user["per"] + ): + raise HTTPException(status_code=403, detail="Permission denied") + + result = await db.zvms.activities.delete_one( + {"_id": validate_object_id(activity_oid)} + ) + + return { + "status": "ok", + "code": 200, + } diff --git a/routers/groups_router.py b/routers/groups_router.py index 1baf588..481ca40 100644 --- a/routers/groups_router.py +++ b/routers/groups_router.py @@ -1,134 +1,134 @@ -from typings.group import Group -from bson import ObjectId -from fastapi import APIRouter, HTTPException, Depends -from database import db -from pydantic import BaseModel - -from utils import compulsory_temporary_token, get_current_user - -router = APIRouter() - - -@router.post("") -async def create_group(payload: Group, user=Depends(get_current_user)): - """ - Create a user group - """ - - if not "admin" in user["per"]: - raise HTTPException(status_code=403, detail="Permission denied") - - group = payload.model_dump() - - result = await db.zvms.groups.insert_one(group) - - id = str(result.inserted_id) - - return { - "status": "ok", - "code": 201, - "data": {"_id": id}, - } - - -@router.get("") -async def get_groups(user=Depends(get_current_user)): - """ - Get all groups - """ - - if len(user["per"]) == 0: - raise HTTPException(status_code=403, detail="Permission denied") - - result = await db.zvms.groups.find().to_list(1000) - - return { - "status": "ok", - "code": 200, - "data": result, - } - - -@router.get("/{group_id}") -async def get_group(group_id: str, user=Depends(get_current_user)): - """ - Get a group - """ - - if len(user["per"]) == 0: - raise HTTPException(status_code=403, detail="Permission denied") - - result = await db.zvms.groups.find_one({"_id": ObjectId(group_id)}) - - return { - "status": "ok", - "code": 200, - "data": result, - } - - -class PutGroupName(BaseModel): - name: str - - -@router.put("/{group_id}/name") -async def update_group_name( - group_id: str, payload: PutGroupName, user=Depends(get_current_user) -): - """ - Update group name - """ - - if not "admin" in user["per"]: - raise HTTPException(status_code=403, detail="Permission denied") - - await db.zvms.groups.update_one( - {"_id": ObjectId(group_id)}, {"$set": {"name": payload.name}} - ) - - return { - "status": "ok", - "code": 200, - } - - -class PutGroupDescription(BaseModel): - description: str - - -@router.put("/{group_id}/description") -async def update_group_description( - group_id: str, payload: PutGroupDescription, user=Depends(get_current_user) -): - """ - Update group description - """ - - if not "admin" in user["per"]: - raise HTTPException(status_code=403, detail="Permission denied") - - await db.zvms.groups.update_one( - {"_id": ObjectId(group_id)}, {"$set": {"description": payload.description}} - ) - - return { - "status": "ok", - "code": 200, - } - - -@router.delete("/{group_id}") -async def delete_group(group_id: str, user=Depends(compulsory_temporary_token)): - """ - Remove group - """ - - if not "admin" in user["per"]: - raise HTTPException(status_code=403, detail="Permission denied") - - await db.zvms.groups.delete_one({"_id": ObjectId(group_id)}) - - return { - "status": "ok", - "code": 200, - } +from typings.group import Group +from bson import ObjectId +from fastapi import APIRouter, HTTPException, Depends +from database import db +from pydantic import BaseModel + +from utils import compulsory_temporary_token, get_current_user + +router = APIRouter() + + +@router.post("") +async def create_group(payload: Group, user=Depends(get_current_user)): + """ + Create a user group + """ + + if not "admin" in user["per"]: + raise HTTPException(status_code=403, detail="Permission denied") + + group = payload.model_dump() + + result = await db.zvms.groups.insert_one(group) + + id = str(result.inserted_id) + + return { + "status": "ok", + "code": 201, + "data": {"_id": id}, + } + + +@router.get("") +async def get_groups(user=Depends(get_current_user)): + """ + Get all groups + """ + + if len(user["per"]) == 0: + raise HTTPException(status_code=403, detail="Permission denied") + + result = await db.zvms.groups.find().to_list(1000) + + return { + "status": "ok", + "code": 200, + "data": result, + } + + +@router.get("/{group_id}") +async def get_group(group_id: str, user=Depends(get_current_user)): + """ + Get a group + """ + + if len(user["per"]) == 0: + raise HTTPException(status_code=403, detail="Permission denied") + + result = await db.zvms.groups.find_one({"_id": ObjectId(group_id)}) + + return { + "status": "ok", + "code": 200, + "data": result, + } + + +class PutGroupName(BaseModel): + name: str + + +@router.put("/{group_id}/name") +async def update_group_name( + group_id: str, payload: PutGroupName, user=Depends(get_current_user) +): + """ + Update group name + """ + + if not "admin" in user["per"]: + raise HTTPException(status_code=403, detail="Permission denied") + + await db.zvms.groups.update_one( + {"_id": ObjectId(group_id)}, {"$set": {"name": payload.name}} + ) + + return { + "status": "ok", + "code": 200, + } + + +class PutGroupDescription(BaseModel): + description: str + + +@router.put("/{group_id}/description") +async def update_group_description( + group_id: str, payload: PutGroupDescription, user=Depends(get_current_user) +): + """ + Update group description + """ + + if not "admin" in user["per"]: + raise HTTPException(status_code=403, detail="Permission denied") + + await db.zvms.groups.update_one( + {"_id": ObjectId(group_id)}, {"$set": {"description": payload.description}} + ) + + return { + "status": "ok", + "code": 200, + } + + +@router.delete("/{group_id}") +async def delete_group(group_id: str, user=Depends(compulsory_temporary_token)): + """ + Remove group + """ + + if not "admin" in user["per"]: + raise HTTPException(status_code=403, detail="Permission denied") + + await db.zvms.groups.delete_one({"_id": ObjectId(group_id)}) + + return { + "status": "ok", + "code": 200, + } diff --git a/routers/notifications_router.py b/routers/notifications_router.py index 7c6ad97..f60db5b 100755 --- a/routers/notifications_router.py +++ b/routers/notifications_router.py @@ -1,82 +1,82 @@ -from fastapi import APIRouter, HTTPException, Depends, Request -from typings.notification import Notification -from utils import compulsory_temporary_token, get_current_user, validate_object_id -from database import db -from pydantic import BaseModel - -router = APIRouter() - - -@router.post("/") -async def create_notification(request: Notification, user=Depends(get_current_user)): - """ - Create Notification - """ - # Create notification - if ( - request.global_ - and "admin" not in user["per"] - and "department" not in user["per"] - or user["id"] != request.publisher - ): - raise HTTPException(status_code=403, detail="Permission denied") - notification = request.model_dump() - notification["global"] = notification["global_"] - notification["publisher"] = user["id"] - for i in notification["receivers"]: - validate_object_id(i) - result = await db.zvms.notifications.insert_one(notification) - return { - "status": "ok", - "code": 201, - "data": {"_id": str(result.inserted_id)}, - } - - -@router.get("/") -async def get_notifications(user=Depends(get_current_user)): - """ - Get Notifications - """ - # Get notifications - result = await db.zvms.notifications.find().to_list(1000) - return result - - -class PutContent(BaseModel): - content: str - - -@router.put("/{notification_oid}/content") -async def update_notification( - notification_oid: str, request: PutContent, user=Depends(get_current_user) -): - """ - Update Notification Content - """ - item = await db.zvms.notifications.find_one( - {"_id": validate_object_id(notification_oid)} - ) - if user["id"] != item["publisher"] and "admin" not in user["per"]: - raise HTTPException(status_code=403, detail="Permission denied") - # Update notification content - await db.zvms.notifications.update_one( - {"_id": validate_object_id(notification_oid)}, - {"$set": {"content": request.content}}, - ) - - -@router.delete("/{notification_oid}") -async def delete_notification(notification_oid: str, user=Depends(compulsory_temporary_token)): - """ - Remove Notification - """ - item = await db.zvms.notifications.find_one( - {"_id": validate_object_id(notification_oid)} - ) - if user["id"] != item["publisher"] and "admin" not in user["per"]: - raise HTTPException(status_code=403, detail="Permission denied") - # Remove notification - await db.zvms.notifications.delete_one( - {"_id": validate_object_id(notification_oid)} - ) +from fastapi import APIRouter, HTTPException, Depends, Request +from typings.notification import Notification +from utils import compulsory_temporary_token, get_current_user, validate_object_id +from database import db +from pydantic import BaseModel + +router = APIRouter() + + +@router.post("/") +async def create_notification(request: Notification, user=Depends(get_current_user)): + """ + Create Notification + """ + # Create notification + if ( + request.global_ + and "admin" not in user["per"] + and "department" not in user["per"] + or user["id"] != request.publisher + ): + raise HTTPException(status_code=403, detail="Permission denied") + notification = request.model_dump() + notification["global"] = notification["global_"] + notification["publisher"] = user["id"] + for i in notification["receivers"]: + validate_object_id(i) + result = await db.zvms.notifications.insert_one(notification) + return { + "status": "ok", + "code": 201, + "data": {"_id": str(result.inserted_id)}, + } + + +@router.get("/") +async def get_notifications(user=Depends(get_current_user)): + """ + Get Notifications + """ + # Get notifications + result = await db.zvms.notifications.find().to_list(1000) + return result + + +class PutContent(BaseModel): + content: str + + +@router.put("/{notification_oid}/content") +async def update_notification( + notification_oid: str, request: PutContent, user=Depends(get_current_user) +): + """ + Update Notification Content + """ + item = await db.zvms.notifications.find_one( + {"_id": validate_object_id(notification_oid)} + ) + if user["id"] != item["publisher"] and "admin" not in user["per"]: + raise HTTPException(status_code=403, detail="Permission denied") + # Update notification content + await db.zvms.notifications.update_one( + {"_id": validate_object_id(notification_oid)}, + {"$set": {"content": request.content}}, + ) + + +@router.delete("/{notification_oid}") +async def delete_notification(notification_oid: str, user=Depends(compulsory_temporary_token)): + """ + Remove Notification + """ + item = await db.zvms.notifications.find_one( + {"_id": validate_object_id(notification_oid)} + ) + if user["id"] != item["publisher"] and "admin" not in user["per"]: + raise HTTPException(status_code=403, detail="Permission denied") + # Remove notification + await db.zvms.notifications.delete_one( + {"_id": validate_object_id(notification_oid)} + ) diff --git a/routers/users_router.py b/routers/users_router.py index 267f952..362161a 100755 --- a/routers/users_router.py +++ b/routers/users_router.py @@ -1,241 +1,241 @@ -from fastapi import APIRouter, HTTPException, Depends, Form, Request -from typing import List - -from pydantic import BaseModel -from util.cases import kebab_case_to_camel_case -from util.response import generate_response -from utils import compulsory_temporary_token, get_current_user, timestamp_change, validate_object_id -from datetime import datetime, timedelta -from jose import JWTError, jwt -from database import db -from bson import ObjectId -import settings -from util.cert import get_hashed_password_by_cert, validate_by_cert - -router = APIRouter() - - -class AuthUser(BaseModel): - id: str - mode: str - credential: str - - -@router.post("/auth") -async def auth_user(auth: AuthUser): - id = auth.id - mode = auth.mode - credential = auth.credential - - if mode is None: - mode = 'long' - - result = await validate_by_cert(id, credential, mode) - - return { - "token": result, - "_id": id, - } - - -class PutPassword(BaseModel): - credential: str - -@router.put("/{user_oid}/password") -async def change_password( - user_oid: str, credential: PutPassword, user=Depends(compulsory_temporary_token) -): - print(user) - # Validate user's permission - if "admin" not in user["per"] and user["id"] != validate_object_id(user_oid): - raise HTTPException(status_code=403, detail="Permission denied") - - password = await get_hashed_password_by_cert(credential.credential) - - print(str(password)) - - # Change user's password - await db.zvms.users.update_one( - {"_id": validate_object_id(user_oid)}, {"$set": {"password": str(password)}} - ) - - return { - "status": "ok", - "code": 200, - } - - -@router.put("/{user_oid}/position") -async def change_permission( - user_oid: str, position: int = Form(...), user=Depends(get_current_user) -): - """ - 参数示例 值 说明 - position 16 用户新权限等级 - """ - # 验证用户权限, 仅管理员可修改他人权限 - if "admin" not in user["per"]: - raise HTTPException(status_code=403, detail="Permission denied") - - # 修改用户权限 - await db.zvms.users.update_one( - {"_id": validate_object_id(user_oid)}, {"$set": {"permission": position}} - ) - - # PUT 请求无需返回值 - - -@router.get("") -async def read_users(query: str): - """ - 返回用户列表 - """ - # 读取用户列表 - result = await db.zvms["users"].find().to_list(3000) - - query_result = [] - - for user in result: - if query in user["name"] or query in str(user["id"]): - user["_id"] = str(user["_id"]) - user["password"] = "" - query_result.append(user) - - # 返回用户列表, 排除密码 - return {"status": "ok", "code": 200, "data": query_result} - - -@router.get("/{user_oid}") -async def read_user(user_oid: str): - """ - Return user's information - """ - # # 验证用户权限, 仅管理员可查看他人信息 - # if user["permission"] < 16 and user["_id"] != validate_object_id(user_oid): - # raise HTTPException(status_code=403, detail="Permission denied") - - # Read user's information - user = await db.zvms.users.find_one({"_id": validate_object_id(user_oid)}) - - user["_id"] = str(user["_id"]) - del user["password"] - return { - "status": "ok", - "code": 200, - "data": user, - } - - -@router.get("/{user_oid}/activity") -async def read_user_activity( - user_oid: str, - registration: bool = False, # 是否返回报名的义工, parameters - user=Depends(get_current_user), -): - """ - Return user's activities - """ - # Check user's permission - - if "admin" not in user["per"] and user["id"] != str(validate_object_id(user_oid)): - raise HTTPException(status_code=403, detail="Permission denied") - - # Read user's activities - all_activities = await db.zvms.activities.find().to_list(1000) - ret = list() - - # Filter activities - for activity in all_activities: - _flag = False - for member in activity["members"]: - if member["_id"] == user_oid: - ret.append(activity) - _flag = True - break - if not registration and not _flag and activity["type"] == "specified" and "registration" in activity: - # Check if the activity is effective and the deadline is not passed - if activity["status"] == "effective" and timestamp_change( - activity["registration"]["deadline"] - ) > int(datetime.utcnow().timestamp()): - # Check if the user's class is in the registration list - for _ in activity["registration"]["classes"]: - if str(_["class"]) == str(user["class"]): - ret.append(activity) - _flag = True - break - pass - - def convert_objectid_to_str(data): - if isinstance(data, dict): - for key, value in data.items(): - if isinstance(value, ObjectId): - data[key] = str(value) - else: - convert_objectid_to_str(value) - elif isinstance(data, list): - for index, item in enumerate(data): - if isinstance(item, ObjectId): - # 如果当前项目是一个 ObjectId,那么将它转换为字符串 - data[index] = str(item) - else: - convert_objectid_to_str(item) - - convert_objectid_to_str(ret) - return { - "status": "ok", - "code": 200, - "data": ret, - } - - -@router.get("/{user_oid}/time") -async def read_user_time(user_oid: str, user=Depends(get_current_user)): - """ - 返回用户义工时长 - """ - # 验证用户权限, 仅管理员可查看他人时长 - if "admin" not in user["per"] and user["id"] != str(validate_object_id(user_oid)): - raise HTTPException(status_code=403, detail="Permission denied") - - # 读取用户义工时长 - user_activity = (await read_user_activity(user_oid, False, user))["data"] - - # 计算用户义工时长 - ret = {"onCampus": 0, "offCampus": 0, "socialPractice": 0, "trophy": 0} - - for activity in user_activity: - for i in activity["members"]: - if i["_id"] == user_oid and i["status"] == "effective": - mode = kebab_case_to_camel_case(i["mode"]) - ret[mode] += i["duration"] - break - - return { - "status": "ok", - "code": 200, - "data": ret, - } - - -@router.get("/{user_oid}/notification") -async def read_notifications(user_oid: str, user=Depends(get_current_user)): - """ - Get Notifications - """ - # Get notification list - notifications = await db.zvms.notifications.find().to_list(2000) - - if user_oid != user["id"]: - raise HTTPException(status_code=403, detail="Permission denied") - - result = [] - for notification in notifications: - notification['_id'] = str(notification['_id']) - if str(user_oid) in notification["receivers"] or notification["global"]: - result.append(notification) - - return { - "status": "ok", - "code": 200, - "data": result, - } +from fastapi import APIRouter, HTTPException, Depends, Form, Request +from typing import List + +from pydantic import BaseModel +from util.cases import kebab_case_to_camel_case +from util.response import generate_response +from utils import compulsory_temporary_token, get_current_user, timestamp_change, validate_object_id +from datetime import datetime, timedelta +from jose import JWTError, jwt +from database import db +from bson import ObjectId +import settings +from util.cert import get_hashed_password_by_cert, validate_by_cert + +router = APIRouter() + + +class AuthUser(BaseModel): + id: str + mode: str + credential: str + + +@router.post("/auth") +async def auth_user(auth: AuthUser): + id = auth.id + mode = auth.mode + credential = auth.credential + + if mode is None: + mode = 'long' + + result = await validate_by_cert(id, credential, mode) + + return { + "token": result, + "_id": id, + } + + +class PutPassword(BaseModel): + credential: str + +@router.put("/{user_oid}/password") +async def change_password( + user_oid: str, credential: PutPassword, user=Depends(compulsory_temporary_token) +): + print(user) + # Validate user's permission + if "admin" not in user["per"] and user["id"] != validate_object_id(user_oid): + raise HTTPException(status_code=403, detail="Permission denied") + + password = await get_hashed_password_by_cert(credential.credential) + + print(str(password)) + + # Change user's password + await db.zvms.users.update_one( + {"_id": validate_object_id(user_oid)}, {"$set": {"password": str(password)}} + ) + + return { + "status": "ok", + "code": 200, + } + + +@router.put("/{user_oid}/position") +async def change_permission( + user_oid: str, position: int = Form(...), user=Depends(get_current_user) +): + """ + 参数示例 值 说明 + position 16 用户新权限等级 + """ + # 验证用户权限, 仅管理员可修改他人权限 + if "admin" not in user["per"]: + raise HTTPException(status_code=403, detail="Permission denied") + + # 修改用户权限 + await db.zvms.users.update_one( + {"_id": validate_object_id(user_oid)}, {"$set": {"permission": position}} + ) + + # PUT 请求无需返回值 + + +@router.get("") +async def read_users(query: str): + """ + 返回用户列表 + """ + # 读取用户列表 + result = await db.zvms["users"].find().to_list(3000) + + query_result = [] + + for user in result: + if query in user["name"] or query in str(user["id"]): + user["_id"] = str(user["_id"]) + user["password"] = "" + query_result.append(user) + + # 返回用户列表, 排除密码 + return {"status": "ok", "code": 200, "data": query_result} + + +@router.get("/{user_oid}") +async def read_user(user_oid: str): + """ + Return user's information + """ + # # 验证用户权限, 仅管理员可查看他人信息 + # if user["permission"] < 16 and user["_id"] != validate_object_id(user_oid): + # raise HTTPException(status_code=403, detail="Permission denied") + + # Read user's information + user = await db.zvms.users.find_one({"_id": validate_object_id(user_oid)}) + + user["_id"] = str(user["_id"]) + del user["password"] + return { + "status": "ok", + "code": 200, + "data": user, + } + + +@router.get("/{user_oid}/activity") +async def read_user_activity( + user_oid: str, + registration: bool = False, # 是否返回报名的义工, parameters + user=Depends(get_current_user), +): + """ + Return user's activities + """ + # Check user's permission + + if "admin" not in user["per"] and user["id"] != str(validate_object_id(user_oid)): + raise HTTPException(status_code=403, detail="Permission denied") + + # Read user's activities + all_activities = await db.zvms.activities.find().to_list(1000) + ret = list() + + # Filter activities + for activity in all_activities: + _flag = False + for member in activity["members"]: + if member["_id"] == user_oid: + ret.append(activity) + _flag = True + break + if not registration and not _flag and activity["type"] == "specified" and "registration" in activity: + # Check if the activity is effective and the deadline is not passed + if activity["status"] == "effective" and timestamp_change( + activity["registration"]["deadline"] + ) > int(datetime.utcnow().timestamp()): + # Check if the user's class is in the registration list + for _ in activity["registration"]["classes"]: + if str(_["class"]) == str(user["class"]): + ret.append(activity) + _flag = True + break + pass + + def convert_objectid_to_str(data): + if isinstance(data, dict): + for key, value in data.items(): + if isinstance(value, ObjectId): + data[key] = str(value) + else: + convert_objectid_to_str(value) + elif isinstance(data, list): + for index, item in enumerate(data): + if isinstance(item, ObjectId): + # 如果当前项目是一个 ObjectId,那么将它转换为字符串 + data[index] = str(item) + else: + convert_objectid_to_str(item) + + convert_objectid_to_str(ret) + return { + "status": "ok", + "code": 200, + "data": ret, + } + + +@router.get("/{user_oid}/time") +async def read_user_time(user_oid: str, user=Depends(get_current_user)): + """ + 返回用户义工时长 + """ + # 验证用户权限, 仅管理员可查看他人时长 + if "admin" not in user["per"] and user["id"] != str(validate_object_id(user_oid)): + raise HTTPException(status_code=403, detail="Permission denied") + + # 读取用户义工时长 + user_activity = (await read_user_activity(user_oid, False, user))["data"] + + # 计算用户义工时长 + ret = {"onCampus": 0, "offCampus": 0, "socialPractice": 0, "trophy": 0} + + for activity in user_activity: + for i in activity["members"]: + if i["_id"] == user_oid and i["status"] == "effective": + mode = kebab_case_to_camel_case(i["mode"]) + ret[mode] += i["duration"] + break + + return { + "status": "ok", + "code": 200, + "data": ret, + } + + +@router.get("/{user_oid}/notification") +async def read_notifications(user_oid: str, user=Depends(get_current_user)): + """ + Get Notifications + """ + # Get notification list + notifications = await db.zvms.notifications.find().to_list(2000) + + if user_oid != user["id"]: + raise HTTPException(status_code=403, detail="Permission denied") + + result = [] + for notification in notifications: + notification['_id'] = str(notification['_id']) + if str(user_oid) in notification["receivers"] or notification["global"]: + result.append(notification) + + return { + "status": "ok", + "code": 200, + "data": result, + } diff --git a/typings/activity.py b/typings/activity.py index 777aaf9..03b87bf 100644 --- a/typings/activity.py +++ b/typings/activity.py @@ -1,93 +1,93 @@ -from typing import Optional -from pydantic import BaseModel, Field -from bson import ObjectId -from enum import Enum - - -class ActivityType(str, Enum): - specified = "specified" - special = "special" - social = "social" - scale = "scale" - - -class MemberActivityStatus(str, Enum): - draft = "draft" - pending = "pending" - effective = "effective" - refused = "refused" - rejected = "rejected" - - -class ActivityMode(str, Enum): - on_campus = "on-campus" - off_campus = "off-campus" - social_practice = "social-practice" - - -class ActivityMemberHistory(BaseModel): - impression: str - duration: float - time: str # ISO 8601 - actioner: str - action: MemberActivityStatus - - -class ActivityMember(BaseModel): - id: str = Field(..., alias='_id') - status: MemberActivityStatus - impression: str - mode: ActivityMode - duration: float - history: list[ActivityMemberHistory] - images: list[str] - - -class ClassRegistration(BaseModel): - classid: int - max: int - min: int | None = None - - -class Registration(BaseModel): - deadline: str # ISO 8601 - place: str - duration: float - classes: list[ClassRegistration] - - -class ActivityStatus(str, Enum): - pending = "pending" - effective = "effective" - refused = "refused" - - -class SpecialActivityClassify(str, Enum): - prize = "prize" - import_ = "import" - club = "club" - other = "other" - deduction = "deduction" - - -class Special(BaseModel): - classify: SpecialActivityClassify - prize: str | None = None - origin: str | None = None - reason: str | None = None - - -class Activity(BaseModel): - _id: str - type: ActivityType - name: str - description: str - members: list[ActivityMember] - registration: Optional[Registration | None] = None - date: str # ISO 8601 - createdAt: str # ISO 8601 - updatedAt: str # ISO 8601 - creator: str - status: ActivityStatus - url: Optional[str | None] = None - special: Optional[Special | None] = None +from typing import Optional +from pydantic import BaseModel, Field +from bson import ObjectId +from enum import Enum + + +class ActivityType(str, Enum): + specified = "specified" + special = "special" + social = "social" + scale = "scale" + + +class MemberActivityStatus(str, Enum): + draft = "draft" + pending = "pending" + effective = "effective" + refused = "refused" + rejected = "rejected" + + +class ActivityMode(str, Enum): + on_campus = "on-campus" + off_campus = "off-campus" + social_practice = "social-practice" + + +class ActivityMemberHistory(BaseModel): + impression: str + duration: float + time: str # ISO 8601 + actioner: str + action: MemberActivityStatus + + +class ActivityMember(BaseModel): + id: str = Field(..., alias='_id') + status: MemberActivityStatus + impression: str + mode: ActivityMode + duration: float + history: list[ActivityMemberHistory] + images: list[str] + + +class ClassRegistration(BaseModel): + classid: int + max: int + min: int | None = None + + +class Registration(BaseModel): + deadline: str # ISO 8601 + place: str + duration: float + classes: list[ClassRegistration] + + +class ActivityStatus(str, Enum): + pending = "pending" + effective = "effective" + refused = "refused" + + +class SpecialActivityClassify(str, Enum): + prize = "prize" + import_ = "import" + club = "club" + other = "other" + deduction = "deduction" + + +class Special(BaseModel): + classify: SpecialActivityClassify + prize: str | None = None + origin: str | None = None + reason: str | None = None + + +class Activity(BaseModel): + _id: str + type: ActivityType + name: str + description: str + members: list[ActivityMember] + registration: Optional[Registration | None] = None + date: str # ISO 8601 + createdAt: str # ISO 8601 + updatedAt: str # ISO 8601 + creator: str + status: ActivityStatus + url: Optional[str | None] = None + special: Optional[Special | None] = None diff --git a/typings/group.py b/typings/group.py index 451ca39..80c7970 100644 --- a/typings/group.py +++ b/typings/group.py @@ -1,29 +1,29 @@ -from typing import Optional -from pydantic import BaseModel, Field -from enum import Enum -from bson import ObjectId - - -class GroupType(str, Enum): - class_ = "class" - permission = "permission" - group = "group" - - -class UserPosition(str, Enum): - student = "student" - secretary = "secretary" - department = "department" - auditor = "auditor" - admin = "admin" - system = "system" - - - -class Group(BaseModel): - _id: ObjectId | str - name: str - type: GroupType - description: Optional[str] - permissions: list[UserPosition] - # Doesn't contain members, and it's not necessary to contain it in the model +from typing import Optional +from pydantic import BaseModel, Field +from enum import Enum +from bson import ObjectId + + +class GroupType(str, Enum): + class_ = "class" + permission = "permission" + group = "group" + + +class UserPosition(str, Enum): + student = "student" + secretary = "secretary" + department = "department" + auditor = "auditor" + admin = "admin" + system = "system" + + + +class Group(BaseModel): + _id: ObjectId | str + name: str + type: GroupType + description: Optional[str] + permissions: list[UserPosition] + # Doesn't contain members, and it's not necessary to contain it in the model diff --git a/typings/notification.py b/typings/notification.py index f5c437e..f010353 100644 --- a/typings/notification.py +++ b/typings/notification.py @@ -1,22 +1,22 @@ -from enum import Enum -from typing import Optional -from pydantic import BaseModel, Field - - -class NotificationType(str, Enum): - pin = "pin" - important = "important" - normal = "normal" - - -class Notification(BaseModel): - id: str = Field(..., alias="_id") - global_: bool = Field(..., alias="global") - title: str - content: str - time: str - publisher: str - receivers: Optional[list[str]] - anonymous: bool - expire: str # ISO 8601 - type: NotificationType +from enum import Enum +from typing import Optional +from pydantic import BaseModel, Field + + +class NotificationType(str, Enum): + pin = "pin" + important = "important" + normal = "normal" + + +class Notification(BaseModel): + id: str = Field(..., alias="_id") + global_: bool = Field(..., alias="global") + title: str + content: str + time: str + publisher: str + receivers: Optional[list[str]] + anonymous: bool + expire: str # ISO 8601 + type: NotificationType diff --git a/typings/trophy.py b/typings/trophy.py index 435f71e..7c66c2b 100644 --- a/typings/trophy.py +++ b/typings/trophy.py @@ -1,59 +1,59 @@ -from enum import Enum -from bson import ObjectId -from pydantic import BaseModel -from typings.activity import ActivityMode - - -class TrophyType(str, Enum): - academic = "academic" - art = "art" - sports = "sports" - others = "others" - - -class TrophyLevel(str, Enum): - district = "district" - city = "city" - province = "province" - national = "national" - international = "international" - - -class TrophyAward(BaseModel): - name: str - duration: float - - -class TrophyStatus(str, Enum): - pending = "pending" - effective = "effective" - refused = "refused" - - -class TrophyMemberStatus(str, Enum): - pending = "pending" - effective = "effective" - refused = "refused" - - -class TrophyMember(BaseModel): - _id: ObjectId | str - award: str - mode: ActivityMode - status: TrophyMemberStatus - - -class Trophy(BaseModel): - _id: ObjectId | str - name: str - type: TrophyType - level: TrophyLevel - awards: list[TrophyAward] - team: bool - status: TrophyStatus - members: list[TrophyMember] - creator: ObjectId | str - instructor: str - deadline: str # ISO 8601 - time: str # ISO 8601 - createdAt: str # ISO 8601 +from enum import Enum +from bson import ObjectId +from pydantic import BaseModel +from typings.activity import ActivityMode + + +class TrophyType(str, Enum): + academic = "academic" + art = "art" + sports = "sports" + others = "others" + + +class TrophyLevel(str, Enum): + district = "district" + city = "city" + province = "province" + national = "national" + international = "international" + + +class TrophyAward(BaseModel): + name: str + duration: float + + +class TrophyStatus(str, Enum): + pending = "pending" + effective = "effective" + refused = "refused" + + +class TrophyMemberStatus(str, Enum): + pending = "pending" + effective = "effective" + refused = "refused" + + +class TrophyMember(BaseModel): + _id: ObjectId | str + award: str + mode: ActivityMode + status: TrophyMemberStatus + + +class Trophy(BaseModel): + _id: ObjectId | str + name: str + type: TrophyType + level: TrophyLevel + awards: list[TrophyAward] + team: bool + status: TrophyStatus + members: list[TrophyMember] + creator: ObjectId | str + instructor: str + deadline: str # ISO 8601 + time: str # ISO 8601 + createdAt: str # ISO 8601 diff --git a/typings/user.py b/typings/user.py index 9fe4d64..9d4fd92 100644 --- a/typings/user.py +++ b/typings/user.py @@ -1,38 +1,38 @@ -from enum import Enum -from bson import ObjectId -from pydantic import BaseModel - - -class UserSex(str, Enum): - male = "male" - female = "female" - unknown = "unknown" - - -class UserPosition(str, Enum): - student = "student" - secretary = "secretary" - department = "department" - auditor = "auditor" - admin = "admin" - system = "system" - - -class UserLogin(BaseModel): - id: ObjectId | str - credential: str - - -class User(BaseModel): - _id: ObjectId | str - id: int - name: str - sex: UserSex - group: list[str] - - -class UserActivityTimeSums(BaseModel): - onCampus: float - offCampus: float - socialPractice: float - trophy: float +from enum import Enum +from bson import ObjectId +from pydantic import BaseModel + + +class UserSex(str, Enum): + male = "male" + female = "female" + unknown = "unknown" + + +class UserPosition(str, Enum): + student = "student" + secretary = "secretary" + department = "department" + auditor = "auditor" + admin = "admin" + system = "system" + + +class UserLogin(BaseModel): + id: ObjectId | str + credential: str + + +class User(BaseModel): + _id: ObjectId | str + id: int + name: str + sex: UserSex + group: list[str] + + +class UserActivityTimeSums(BaseModel): + onCampus: float + offCampus: float + socialPractice: float + trophy: float diff --git a/util/.gitignore b/util/.gitignore index 14d40db..bb3fb4b 100644 --- a/util/.gitignore +++ b/util/.gitignore @@ -1,2 +1,2 @@ -*.pem -*.txt +*.pem +*.txt diff --git a/util/cases.py b/util/cases.py index 9ec6a73..5810b95 100644 --- a/util/cases.py +++ b/util/cases.py @@ -1,15 +1,15 @@ -import re - - -def kebab_case_to_camel_case(s: str) -> str: - result = "".join([word.capitalize() for word in s.split("-")]) - # the first word should be lowercase - return result[0].lower() + result[1:] - - -def kebab_case_to_snake_case(s: str) -> str: - return "_".join([word.lower() for word in s.split("-")]) - - -def camel_case_to_kebab_case(s: str) -> str: - return "-".join([word.lower() for word in re.findall(r"[A-Z][a-z]*", s)]) +import re + + +def kebab_case_to_camel_case(s: str) -> str: + result = "".join([word.capitalize() for word in s.split("-")]) + # the first word should be lowercase + return result[0].lower() + result[1:] + + +def kebab_case_to_snake_case(s: str) -> str: + return "_".join([word.lower() for word in s.split("-")]) + + +def camel_case_to_kebab_case(s: str) -> str: + return "-".join([word.lower() for word in re.findall(r"[A-Z][a-z]*", s)]) diff --git a/util/cert.py b/util/cert.py index bec87eb..af935b6 100644 --- a/util/cert.py +++ b/util/cert.py @@ -1,112 +1,112 @@ -from typing import Optional -import bcrypt -from Crypto.PublicKey import RSA -from Crypto.Cipher import PKCS1_OAEP -from fastapi import HTTPException -import jwt -import json -import datetime -from database import db -from bson import ObjectId -from bcrypt import checkpw, gensalt, hashpw -from Crypto.Hash import SHA256 - - -class Auth: - password: str - time: int - - -def hash_password(password): - return bcrypt.hashpw(password.encode("utf-8"), bcrypt.gensalt()) - - -def check_password(password, hashed): - return bcrypt.checkpw(password.encode("utf-8"), hashed) - - -public_key = RSA.import_key(open("rsa_public_key.pem", "rb").read()) -private_key = RSA.import_key(open("rsa_private_key.pem", "rb").read()) -jwt_private_key = open("aes_key.txt", "r").read() - - -def rsa_encrypt(plaintext): - cipher = PKCS1_OAEP.new(public_key, hashAlgo=SHA256) - encrypt_text = cipher.encrypt(bytes(plaintext.encode("utf8"))) - return encrypt_text.hex() - - -def rsa_decrypt(ciphertext): - cipher = PKCS1_OAEP.new(private_key, hashAlgo=SHA256) - decrypt_text = cipher.decrypt(bytes.fromhex(ciphertext)) - return decrypt_text.decode("utf8") - - -def jwt_encode( - id: str, - permissions: list[str], - type: Optional[str] = "long", -): - duration = ( - datetime.timedelta(days=15) - if type == "long" - else datetime.timedelta(minutes=15) - ) - payload = { - "iss": "zvms", - "exp": datetime.datetime.utcnow() + duration, - "iat": datetime.datetime.utcnow(), - "sub": id, - "scope": ( - "access_token" if type == "long" else "temporary_token" - ), # Dangerous Zone Access needs temporary token, others need access token. - "per": permissions, - "jti": str(ObjectId()), - } - result = jwt.encode(payload, jwt_private_key, algorithm="HS256") - return result - - -def jwt_decode(token): - return jwt.decode(token, jwt_private_key, algorithms=["HS256"], verify=True) - - -async def get_renewed_password(id: str, credential: str): - field = json.loads(rsa_decrypt(credential)) - time = field["time"] - if time < datetime.datetime.now().timestamp() - 60: - raise HTTPException(status_code=401, detail="Token expired") - new_password = hashpw(bytes(field["password"], "utf-8"), gensalt()) - await db.zvms.users.update_one( - {"_id": ObjectId(id)}, {"$set": {"password": new_password}} - ) - - -async def validate_by_cert(id: str, cert: str, type: Optional[str] = "long"): - auth_field = json.loads(rsa_decrypt(cert)) - time = auth_field["time"] - # in a minute - if time < datetime.datetime.now().timestamp() - 60: - raise HTTPException(status_code=401, detail="Token expired") - user = await db.zvms.users.find_one({"_id": ObjectId(id)}) - if checkpw( - bytes(auth_field["password"], "utf-8"), bytes(user["password"], "utf-8") - ): - return jwt_encode(id, user["position"], type=type) - raise HTTPException(status_code=401, detail="Password incorrect") - -async def get_hashed_password_by_cert(cert: str): - auth_field = json.loads(rsa_decrypt(cert)) - time = auth_field["time"] - # in a minute - if time < datetime.datetime.now().timestamp() - 60: - raise HTTPException(status_code=401, detail="Token expired") - password = auth_field["password"] - return hashpw(bytes(password, "utf-8"), gensalt()) - - -async def checkpwd(id: str, pwd: str): - user = await db.zvms.users.find_one({"_id": ObjectId(id)}) - if checkpw(bytes(pwd, "utf-8"), bytes(user["password"], "utf-8")): - return True - return False +from typing import Optional +import bcrypt +from Crypto.PublicKey import RSA +from Crypto.Cipher import PKCS1_OAEP +from fastapi import HTTPException +import jwt +import json +import datetime +from database import db +from bson import ObjectId +from bcrypt import checkpw, gensalt, hashpw +from Crypto.Hash import SHA256 + + +class Auth: + password: str + time: int + + +def hash_password(password): + return bcrypt.hashpw(password.encode("utf-8"), bcrypt.gensalt()) + + +def check_password(password, hashed): + return bcrypt.checkpw(password.encode("utf-8"), hashed) + + +public_key = RSA.import_key(open("rsa_public_key.pem", "rb").read()) +private_key = RSA.import_key(open("rsa_private_key.pem", "rb").read()) +jwt_private_key = open("aes_key.txt", "r").read() + + +def rsa_encrypt(plaintext): + cipher = PKCS1_OAEP.new(public_key, hashAlgo=SHA256) + encrypt_text = cipher.encrypt(bytes(plaintext.encode("utf8"))) + return encrypt_text.hex() + + +def rsa_decrypt(ciphertext): + cipher = PKCS1_OAEP.new(private_key, hashAlgo=SHA256) + decrypt_text = cipher.decrypt(bytes.fromhex(ciphertext)) + return decrypt_text.decode("utf8") + + +def jwt_encode( + id: str, + permissions: list[str], + type: Optional[str] = "long", +): + duration = ( + datetime.timedelta(days=15) + if type == "long" + else datetime.timedelta(minutes=15) + ) + payload = { + "iss": "zvms", + "exp": datetime.datetime.utcnow() + duration, + "iat": datetime.datetime.utcnow(), + "sub": id, + "scope": ( + "access_token" if type == "long" else "temporary_token" + ), # Dangerous Zone Access needs temporary token, others need access token. + "per": permissions, + "jti": str(ObjectId()), + } + result = jwt.encode(payload, jwt_private_key, algorithm="HS256") + return result + + +def jwt_decode(token): + return jwt.decode(token, jwt_private_key, algorithms=["HS256"], verify=True) + + +async def get_renewed_password(id: str, credential: str): + field = json.loads(rsa_decrypt(credential)) + time = field["time"] + if time < datetime.datetime.now().timestamp() - 60: + raise HTTPException(status_code=401, detail="Token expired") + new_password = hashpw(bytes(field["password"], "utf-8"), gensalt()) + await db.zvms.users.update_one( + {"_id": ObjectId(id)}, {"$set": {"password": new_password}} + ) + + +async def validate_by_cert(id: str, cert: str, type: Optional[str] = "long"): + auth_field = json.loads(rsa_decrypt(cert)) + time = auth_field["time"] + # in a minute + if time < datetime.datetime.now().timestamp() - 60: + raise HTTPException(status_code=401, detail="Token expired") + user = await db.zvms.users.find_one({"_id": ObjectId(id)}) + if checkpw( + bytes(auth_field["password"], "utf-8"), bytes(user["password"], "utf-8") + ): + return jwt_encode(id, user["position"], type=type) + raise HTTPException(status_code=401, detail="Password incorrect") + +async def get_hashed_password_by_cert(cert: str): + auth_field = json.loads(rsa_decrypt(cert)) + time = auth_field["time"] + # in a minute + if time < datetime.datetime.now().timestamp() - 60: + raise HTTPException(status_code=401, detail="Token expired") + password = auth_field["password"] + return hashpw(bytes(password, "utf-8"), gensalt()) + + +async def checkpwd(id: str, pwd: str): + user = await db.zvms.users.find_one({"_id": ObjectId(id)}) + if checkpw(bytes(pwd, "utf-8"), bytes(user["password"], "utf-8")): + return True + return False diff --git a/util/get_class.py b/util/get_class.py index e17bc51..ea9bc75 100644 --- a/util/get_class.py +++ b/util/get_class.py @@ -1,73 +1,73 @@ -from fastapi import HTTPException -from database import db -from utils import validate_object_id - -def get_classid_by_code(code: int): - codeid = str(code) - if len(str(code)) == 7: - codeid = '0' + str(code) - - if len(codeid) != 8: - return None - - typeid = codeid[0:2] - gradeid = codeid[2:4] - classid = codeid[4:6] - - if gradeid == '00' or classid == '00': - return None - - if typeid == '09': - return 200000 + int(gradeid) * 100 + int(classid) - else: - return 200000 + int(gradeid) * 100 + 10 + int(classid) - -async def get_classid_by_user_id(user_id: str): - user = await db.zvms.users.find_one({"_id": validate_object_id(user_id)}) - if not user: - return None - return get_classid_by_code(user['code']) - -async def get_activities_related_to_user(user_oid: str): - user = await db.zvms.users.find_one({"_id": validate_object_id(user_oid)}) - if not user: - return HTTPException(status_code=404, detail="User not found") - activities = await db.zvms.activities.find().to_list(1200) - - user_classid = get_classid_by_code(user['code']) - - user_map = [] - result = [] - - users = await db.zvms.users.find().to_list(3000) - - for user in users: - user['_id'] = str(user['_id']) - user_map.append({ - 'id': user['_id'], - 'code': user['code'], - 'classid': get_classid_by_code(user['code']), - }) - - def get_classid_by_user_id(user_id: str): - for user in user_map: - if user['id'] == user_id: - return user['classid'] - return None - - for activity in activities: - activity['_id'] = str(activity['_id']) - flag_ = False - if activity['creator'] == user_oid: - flag_ = True - for member in activity['members']: - member_class = get_classid_by_user_id(member['_id']) - if activity == activities[-1]: - print(member_class, user_classid) - if member_class == user_classid: - flag_ = True - break - if not flag_: - continue - result.append(activity) - return result +from fastapi import HTTPException +from database import db +from utils import validate_object_id + +def get_classid_by_code(code: int): + codeid = str(code) + if len(str(code)) == 7: + codeid = '0' + str(code) + + if len(codeid) != 8: + return None + + typeid = codeid[0:2] + gradeid = codeid[2:4] + classid = codeid[4:6] + + if gradeid == '00' or classid == '00': + return None + + if typeid == '09': + return 200000 + int(gradeid) * 100 + int(classid) + else: + return 200000 + int(gradeid) * 100 + 10 + int(classid) + +async def get_classid_by_user_id(user_id: str): + user = await db.zvms.users.find_one({"_id": validate_object_id(user_id)}) + if not user: + return None + return get_classid_by_code(user['code']) + +async def get_activities_related_to_user(user_oid: str): + user = await db.zvms.users.find_one({"_id": validate_object_id(user_oid)}) + if not user: + return HTTPException(status_code=404, detail="User not found") + activities = await db.zvms.activities.find().to_list(1200) + + user_classid = get_classid_by_code(user['code']) + + user_map = [] + result = [] + + users = await db.zvms.users.find().to_list(3000) + + for user in users: + user['_id'] = str(user['_id']) + user_map.append({ + 'id': user['_id'], + 'code': user['code'], + 'classid': get_classid_by_code(user['code']), + }) + + def get_classid_by_user_id(user_id: str): + for user in user_map: + if user['id'] == user_id: + return user['classid'] + return None + + for activity in activities: + activity['_id'] = str(activity['_id']) + flag_ = False + if activity['creator'] == user_oid: + flag_ = True + for member in activity['members']: + member_class = get_classid_by_user_id(member['_id']) + if activity == activities[-1]: + print(member_class, user_classid) + if member_class == user_classid: + flag_ = True + break + if not flag_: + continue + result.append(activity) + return result diff --git a/util/group.py b/util/group.py index c7c8932..7144504 100644 --- a/util/group.py +++ b/util/group.py @@ -1,16 +1,16 @@ -from database import db -from typings.user import User - -async def get_user_permissions(user: User): - """ - Get user's permissions - """ - groups = user.group - - permissions = [] - - for group in groups: - group = await db.zvms.groups.find_one({"_id": group}) - permissions.extend(group["permissions"]) - - return permissions +from database import db +from typings.user import User + +async def get_user_permissions(user: User): + """ + Get user's permissions + """ + groups = user.group + + permissions = [] + + for group in groups: + group = await db.zvms.groups.find_one({"_id": group}) + permissions.extend(group["permissions"]) + + return permissions diff --git a/util/response.py b/util/response.py index 7c077bc..f937708 100644 --- a/util/response.py +++ b/util/response.py @@ -1,17 +1,17 @@ -from enum import Enum - - -class Status(str, Enum): - ok = "ok" - error = "error" - - -def generate_response(status: Status, code: int, message: str, data): - result = { - "status": status, - "code": code, - } - if message: - result["message"] = message - if data: - result["data"] = data +from enum import Enum + + +class Status(str, Enum): + ok = "ok" + error = "error" + + +def generate_response(status: Status, code: int, message: str, data): + result = { + "status": status, + "code": code, + } + if message: + result["message"] = message + if data: + result["data"] = data diff --git a/utils.py b/utils.py index 8367e48..9edd40a 100755 --- a/utils.py +++ b/utils.py @@ -1,104 +1,104 @@ -from fastapi import Depends, HTTPException, status -from fastapi.security import OAuth2PasswordBearer -from h11 import Data -from models.user import User -import jwt -from typing import Optional -from datetime import datetime, timezone -from database import db -from bson import ObjectId -import settings -from util.cert import jwt_decode - - -# Secret key and algorithm for JWT -SECRET_KEY = open("aes_key.txt", "r").read() -ALGORITHM = "HS256" -oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") - - -def validate_object_id(id: str): - try: - _id = ObjectId(id) - except Exception: - raise HTTPException(status_code=400, detail="Invalid Object ID") - return _id - - -async def get_user(oid: str): - """ - Get user by oid - """ - user = await db.zvms.users.find_one({"_id": validate_object_id(oid)}) - if user: - return user - print(user) - return None - - -async def authenticate_user(oid: str, password: str): - """ - 给定用户名和密码 (MD5), 验证用户身份 - """ - user = await get_user(oid) - if not user: - return None - if user['password_hashed'] != password: - return None - print(user) - return user - - -async def compulsory_temporary_token(token: str = Depends(oauth2_scheme)): - result = await get_current_user(token, 'short') - return result - -async def get_current_user(token: str = Depends(oauth2_scheme), scope: Optional[str] = 'long'): - """ - 用于 Depends 注入, 返回当前用户信息 User - """ - credentials_exception = HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail="Could not validate credentials", - headers={"WWW-Authenticate": "Bearer"}, - ) - - try: - # Decode JWT - payload = jwt_decode(token) - oid: str = payload.get("sub", None) - exp: int = payload.get("exp", None) - jti: str = payload.get("jti", None) - - # Check if the token is valid - if oid is None or exp is None or jti is None: - raise credentials_exception - - # Check if the token is expired - if exp is not None and datetime.utcnow() >= datetime.fromtimestamp(exp): - raise credentials_exception - if scope == 'short' and payload['scope'] == 'access_token': - raise credentials_exception - except jwt.PyJWTError: - raise credentials_exception - - user = { - "id": oid, - "per": payload.get("per", None), - } - if user is None: - raise credentials_exception - return user - - -def timestamp_change(date_string: str): - """ - Change ISO-8601 to timestamp - """ - dt = datetime.strptime(date_string, "%Y-%m-%dT%H:%M:%S.%fZ") - # Change the time zone to UTC - dt = dt.replace(tzinfo=timezone.utc) - # Get the timestamp - timestamp = dt.timestamp() - # Return the timestamp - return int(timestamp) +from fastapi import Depends, HTTPException, status +from fastapi.security import OAuth2PasswordBearer +from h11 import Data +from models.user import User +import jwt +from typing import Optional +from datetime import datetime, timezone +from database import db +from bson import ObjectId +import settings +from util.cert import jwt_decode + + +# Secret key and algorithm for JWT +SECRET_KEY = open("aes_key.txt", "r").read() +ALGORITHM = "HS256" +oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") + + +def validate_object_id(id: str): + try: + _id = ObjectId(id) + except Exception: + raise HTTPException(status_code=400, detail="Invalid Object ID") + return _id + + +async def get_user(oid: str): + """ + Get user by oid + """ + user = await db.zvms.users.find_one({"_id": validate_object_id(oid)}) + if user: + return user + print(user) + return None + + +async def authenticate_user(oid: str, password: str): + """ + 给定用户名和密码 (MD5), 验证用户身份 + """ + user = await get_user(oid) + if not user: + return None + if user['password_hashed'] != password: + return None + print(user) + return user + + +async def compulsory_temporary_token(token: str = Depends(oauth2_scheme)): + result = await get_current_user(token, 'short') + return result + +async def get_current_user(token: str = Depends(oauth2_scheme), scope: Optional[str] = 'long'): + """ + 用于 Depends 注入, 返回当前用户信息 User + """ + credentials_exception = HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Could not validate credentials", + headers={"WWW-Authenticate": "Bearer"}, + ) + + try: + # Decode JWT + payload = jwt_decode(token) + oid: str = payload.get("sub", None) + exp: int = payload.get("exp", None) + jti: str = payload.get("jti", None) + + # Check if the token is valid + if oid is None or exp is None or jti is None: + raise credentials_exception + + # Check if the token is expired + if exp is not None and datetime.utcnow() >= datetime.fromtimestamp(exp): + raise credentials_exception + if scope == 'short' and payload['scope'] == 'access_token': + raise credentials_exception + except jwt.PyJWTError: + raise credentials_exception + + user = { + "id": oid, + "per": payload.get("per", None), + } + if user is None: + raise credentials_exception + return user + + +def timestamp_change(date_string: str): + """ + Change ISO-8601 to timestamp + """ + dt = datetime.strptime(date_string, "%Y-%m-%dT%H:%M:%S.%fZ") + # Change the time zone to UTC + dt = dt.replace(tzinfo=timezone.utc) + # Get the timestamp + timestamp = dt.timestamp() + # Return the timestamp + return int(timestamp)