init commit
This commit is contained in:
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
/client/.venv
|
||||||
|
/server/.venv
|
||||||
|
.key
|
||||||
|
.auth_data
|
||||||
6
.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file
6
.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<settings>
|
||||||
|
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||||
|
<version value="1.0" />
|
||||||
|
</settings>
|
||||||
|
</component>
|
||||||
7
.idea/misc.xml
generated
Normal file
7
.idea/misc.xml
generated
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="Black">
|
||||||
|
<option name="sdkName" value="Poetry (sonoma-app) (2)" />
|
||||||
|
</component>
|
||||||
|
<component name="ProjectRootManager" version="2" project-jdk-name="Poetry (sonoma-app) (3)" project-jdk-type="Python SDK" />
|
||||||
|
</project>
|
||||||
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/sonoma-app.iml" filepath="$PROJECT_DIR$/.idea/sonoma-app.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
10
.idea/sonoma-app.iml
generated
Normal file
10
.idea/sonoma-app.iml
generated
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="PYTHON_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<content url="file://$MODULE_DIR$">
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/client/.venv" />
|
||||||
|
</content>
|
||||||
|
<orderEntry type="jdk" jdkName="Poetry (sonoma-app) (3)" jdkType="Python SDK" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
</module>
|
||||||
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
180
.idea/workspace.xml
generated
Normal file
180
.idea/workspace.xml
generated
Normal file
@ -0,0 +1,180 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="AutoImportSettings">
|
||||||
|
<option name="autoReloadType" value="SELECTIVE" />
|
||||||
|
</component>
|
||||||
|
<component name="ChangeListManager">
|
||||||
|
<list default="true" id="83104c25-b1e9-4482-ae4f-ce8de8785017" name="Changes" comment="">
|
||||||
|
<change afterPath="$PROJECT_DIR$/.gitignore" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/.idea/inspectionProfiles/profiles_settings.xml" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/.idea/modules.xml" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/.idea/sonoma-app.iml" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/.idea/vcs.xml" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/client/poetry.toml" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/client/pyproject.toml" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/client/src/assets/sonoma.png" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/client/src/auth/login.py" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/server/docker-compose.yaml" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/server/main-server/.gitignore" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/server/main-server/src/auth/transport.py" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/server/main-server/src/auth/user_manager.py" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/server/main-server/src/database/role.py" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/server/main-server/src/database/user.py" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/server/main-server/src/main.py" afterDir="false" />
|
||||||
|
</list>
|
||||||
|
<option name="SHOW_DIALOG" value="false" />
|
||||||
|
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||||
|
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
||||||
|
<option name="LAST_RESOLUTION" value="IGNORE" />
|
||||||
|
</component>
|
||||||
|
<component name="FileTemplateManagerImpl">
|
||||||
|
<option name="RECENT_TEMPLATES">
|
||||||
|
<list>
|
||||||
|
<option value="Python Script" />
|
||||||
|
<option value="Dockerfile" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
<component name="Git.Settings">
|
||||||
|
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
|
||||||
|
</component>
|
||||||
|
<component name="ProjectColorInfo">{
|
||||||
|
"associatedIndex": 4
|
||||||
|
}</component>
|
||||||
|
<component name="ProjectId" id="2zD4Z14OpesWKHpd50zSPhhB4bQ" />
|
||||||
|
<component name="ProjectViewState">
|
||||||
|
<option name="hideEmptyMiddlePackages" value="true" />
|
||||||
|
<option name="showLibraryContents" value="true" />
|
||||||
|
</component>
|
||||||
|
<component name="PropertiesComponent">{
|
||||||
|
"keyToString": {
|
||||||
|
"Docker.server/docker-compose.yaml.postgres: Compose Deployment.executor": "Run",
|
||||||
|
"Docker.server/docker-compose.yaml: Compose Deployment.executor": "Run",
|
||||||
|
"ModuleVcsDetector.initialDetectionPerformed": "true",
|
||||||
|
"Python.client run.executor": "Run",
|
||||||
|
"Python.main.executor": "Run",
|
||||||
|
"Python.start client.executor": "Run",
|
||||||
|
"Python.start main server.executor": "Run",
|
||||||
|
"RunOnceActivity.ShowReadmeOnStart": "true",
|
||||||
|
"RunOnceActivity.git.unshallow": "true",
|
||||||
|
"git-widget-placeholder": "main",
|
||||||
|
"last_opened_file_path": "/Users/saddy/Documents/projects/sonoma-app/server/main-server",
|
||||||
|
"settings.editor.selected.configurable": "com.jetbrains.python.configuration.PyActiveSdkModuleConfigurable"
|
||||||
|
}
|
||||||
|
}</component>
|
||||||
|
<component name="RecentsManager">
|
||||||
|
<key name="CopyFile.RECENT_KEYS">
|
||||||
|
<recent name="$PROJECT_DIR$/server/main-server" />
|
||||||
|
<recent name="$PROJECT_DIR$/client" />
|
||||||
|
</key>
|
||||||
|
<key name="MoveFile.RECENT_KEYS">
|
||||||
|
<recent name="$PROJECT_DIR$/client/src/assets" />
|
||||||
|
<recent name="$PROJECT_DIR$/client" />
|
||||||
|
</key>
|
||||||
|
</component>
|
||||||
|
<component name="RunManager" selected="Python.start main server">
|
||||||
|
<configuration name="start client" type="PythonConfigurationType" factoryName="Python">
|
||||||
|
<module name="sonoma-app" />
|
||||||
|
<option name="ENV_FILES" value="" />
|
||||||
|
<option name="INTERPRETER_OPTIONS" value="" />
|
||||||
|
<option name="PARENT_ENVS" value="true" />
|
||||||
|
<envs>
|
||||||
|
<env name="PYTHONUNBUFFERED" value="1" />
|
||||||
|
</envs>
|
||||||
|
<option name="SDK_HOME" value="" />
|
||||||
|
<option name="SDK_NAME" value="Poetry (sonoma-app) (3)" />
|
||||||
|
<option name="WORKING_DIRECTORY" value="" />
|
||||||
|
<option name="IS_MODULE_SDK" value="false" />
|
||||||
|
<option name="ADD_CONTENT_ROOTS" value="true" />
|
||||||
|
<option name="ADD_SOURCE_ROOTS" value="true" />
|
||||||
|
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/client/src/main.py" />
|
||||||
|
<option name="PARAMETERS" value="" />
|
||||||
|
<option name="SHOW_COMMAND_LINE" value="false" />
|
||||||
|
<option name="EMULATE_TERMINAL" value="false" />
|
||||||
|
<option name="MODULE_MODE" value="false" />
|
||||||
|
<option name="REDIRECT_INPUT" value="false" />
|
||||||
|
<option name="INPUT_FILE" value="" />
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
<configuration name="start main server" type="PythonConfigurationType" factoryName="Python">
|
||||||
|
<module name="sonoma-app" />
|
||||||
|
<option name="ENV_FILES" value="" />
|
||||||
|
<option name="INTERPRETER_OPTIONS" value="" />
|
||||||
|
<option name="PARENT_ENVS" value="true" />
|
||||||
|
<envs>
|
||||||
|
<env name="PYTHONUNBUFFERED" value="1" />
|
||||||
|
</envs>
|
||||||
|
<option name="SDK_HOME" value="" />
|
||||||
|
<option name="SDK_NAME" value="Poetry (main-server)" />
|
||||||
|
<option name="WORKING_DIRECTORY" value="" />
|
||||||
|
<option name="IS_MODULE_SDK" value="false" />
|
||||||
|
<option name="ADD_CONTENT_ROOTS" value="true" />
|
||||||
|
<option name="ADD_SOURCE_ROOTS" value="true" />
|
||||||
|
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/server/main-server/src/main.py" />
|
||||||
|
<option name="PARAMETERS" value="" />
|
||||||
|
<option name="SHOW_COMMAND_LINE" value="false" />
|
||||||
|
<option name="EMULATE_TERMINAL" value="false" />
|
||||||
|
<option name="MODULE_MODE" value="false" />
|
||||||
|
<option name="REDIRECT_INPUT" value="false" />
|
||||||
|
<option name="INPUT_FILE" value="" />
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
<configuration default="true" type="docker-deploy" factoryName="docker-compose.yml" temporary="true">
|
||||||
|
<deployment type="docker-compose.yml">
|
||||||
|
<settings />
|
||||||
|
</deployment>
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
<configuration name="server/docker-compose.yaml: Compose Deployment" type="docker-deploy" factoryName="docker-compose.yml" temporary="true" server-name="Docker">
|
||||||
|
<deployment type="docker-compose.yml">
|
||||||
|
<settings>
|
||||||
|
<option name="sourceFilePath" value="server/docker-compose.yaml" />
|
||||||
|
</settings>
|
||||||
|
</deployment>
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
<configuration name="server/docker-compose.yaml.postgres: Compose Deployment" type="docker-deploy" factoryName="docker-compose.yml" server-name="Docker">
|
||||||
|
<deployment type="docker-compose.yml">
|
||||||
|
<settings>
|
||||||
|
<option name="envFilePath" value="" />
|
||||||
|
<option name="services">
|
||||||
|
<list>
|
||||||
|
<option value="postgres" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
<option name="sourceFilePath" value="server/docker-compose.yaml" />
|
||||||
|
</settings>
|
||||||
|
</deployment>
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
<list>
|
||||||
|
<item itemvalue="Docker.server/docker-compose.yaml.postgres: Compose Deployment" />
|
||||||
|
<item itemvalue="Docker.server/docker-compose.yaml: Compose Deployment" />
|
||||||
|
<item itemvalue="Python.start client" />
|
||||||
|
<item itemvalue="Python.start main server" />
|
||||||
|
</list>
|
||||||
|
<recent_temporary>
|
||||||
|
<list>
|
||||||
|
<item itemvalue="Docker.server/docker-compose.yaml: Compose Deployment" />
|
||||||
|
</list>
|
||||||
|
</recent_temporary>
|
||||||
|
</component>
|
||||||
|
<component name="SharedIndexes">
|
||||||
|
<attachedChunks>
|
||||||
|
<set>
|
||||||
|
<option value="bundled-python-sdk-53e2683a6804-9cdd278e9d02-com.jetbrains.pycharm.community.sharedIndexes.bundled-PC-251.26094.141" />
|
||||||
|
</set>
|
||||||
|
</attachedChunks>
|
||||||
|
</component>
|
||||||
|
<component name="TaskManager">
|
||||||
|
<task active="true" id="Default" summary="Default task">
|
||||||
|
<changelist id="83104c25-b1e9-4482-ae4f-ce8de8785017" name="Changes" comment="" />
|
||||||
|
<created>1751252361654</created>
|
||||||
|
<option name="number" value="Default" />
|
||||||
|
<option name="presentableId" value="Default" />
|
||||||
|
<updated>1751252361654</updated>
|
||||||
|
</task>
|
||||||
|
<servers />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
163
client/.gitignore
vendored
Normal file
163
client/.gitignore
vendored
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
# 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
|
||||||
|
*.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/
|
||||||
|
|
||||||
|
# Flet
|
||||||
|
storage/
|
||||||
2
client/poetry.toml
Normal file
2
client/poetry.toml
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
[virtualenvs]
|
||||||
|
in-project = true
|
||||||
36
client/pyproject.toml
Normal file
36
client/pyproject.toml
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
[project]
|
||||||
|
name = "sonoma-app-client"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = ""
|
||||||
|
requires-python = ">=3.12"
|
||||||
|
authors = [
|
||||||
|
{ name = "SaddyDEAD", email = "saddydead@sonoma.su" }
|
||||||
|
]
|
||||||
|
dependencies = [
|
||||||
|
"flet==0.28.3",
|
||||||
|
"cryptography",
|
||||||
|
"httpx (>=0.28.1,<0.29.0)"
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.flet]
|
||||||
|
org = "su.sonoma"
|
||||||
|
|
||||||
|
product = "SApp"
|
||||||
|
|
||||||
|
company = "Sonoma"
|
||||||
|
|
||||||
|
copyright = "Copyright (C) 2025 by Sonoma Org."
|
||||||
|
|
||||||
|
[tool.flet.app]
|
||||||
|
path = "src"
|
||||||
|
|
||||||
|
[tool.uv]
|
||||||
|
dev-dependencies = [
|
||||||
|
"flet[all]==0.28.3",
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.poetry]
|
||||||
|
package-mode = false
|
||||||
|
|
||||||
|
[tool.poetry.group.dev.dependencies]
|
||||||
|
flet = {extras = ["all"], version = "0.28.3"}
|
||||||
BIN
client/src/assets/sonoma.png
Normal file
BIN
client/src/assets/sonoma.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.3 MiB |
28
client/src/auth/login.py
Normal file
28
client/src/auth/login.py
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
from cryptography.fernet import Fernet
|
||||||
|
import os
|
||||||
|
import httpx
|
||||||
|
|
||||||
|
KEY_FILE = ".key"
|
||||||
|
REMEMBER_FILE = ".auth_data"
|
||||||
|
|
||||||
|
def generate_key():
|
||||||
|
if not os.path.exists(KEY_FILE):
|
||||||
|
key = Fernet.generate_key()
|
||||||
|
with open(KEY_FILE, "wb") as f:
|
||||||
|
f.write(key)
|
||||||
|
|
||||||
|
|
||||||
|
async def load_fernet():
|
||||||
|
with open(KEY_FILE, "rb") as f:
|
||||||
|
key = f.read()
|
||||||
|
return Fernet(key)
|
||||||
|
|
||||||
|
|
||||||
|
async def login(name: str, password: str):
|
||||||
|
async with httpx.AsyncClient() as client:
|
||||||
|
r = await client.post(f'http://127.0.0.1:7535/login?name={name}&password={password}')
|
||||||
|
|
||||||
|
if r.is_success:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
102
client/src/main.py
Normal file
102
client/src/main.py
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
import flet as ft
|
||||||
|
from auth.login import *
|
||||||
|
|
||||||
|
USERNAME = "admin"
|
||||||
|
PASSWORD = "1234"
|
||||||
|
|
||||||
|
|
||||||
|
def generate_key():
|
||||||
|
if not os.path.exists(KEY_FILE):
|
||||||
|
key = Fernet.generate_key()
|
||||||
|
with open(KEY_FILE, "wb") as f:
|
||||||
|
f.write(key)
|
||||||
|
|
||||||
|
|
||||||
|
async def load_fernet():
|
||||||
|
with open(KEY_FILE, "rb") as f:
|
||||||
|
key = f.read()
|
||||||
|
return Fernet(key)
|
||||||
|
|
||||||
|
|
||||||
|
async def main(page: ft.Page):
|
||||||
|
generate_key()
|
||||||
|
fernet = await load_fernet()
|
||||||
|
|
||||||
|
page.title = "SClient"
|
||||||
|
page.window_width = 400
|
||||||
|
page.window_height = 300
|
||||||
|
page.vertical_alignment = ft.MainAxisAlignment.CENTER
|
||||||
|
|
||||||
|
login_input = ft.TextField(label="Логин", autofocus=True)
|
||||||
|
password_input = ft.TextField(label="Пароль", password=True, can_reveal_password=True)
|
||||||
|
remember_checkbox = ft.Checkbox(label="Запомнить меня")
|
||||||
|
status_text = ft.Text(color=ft.Colors.RED)
|
||||||
|
|
||||||
|
if os.path.exists(REMEMBER_FILE):
|
||||||
|
try:
|
||||||
|
with open(REMEMBER_FILE, "rb") as f:
|
||||||
|
lines = f.readlines()
|
||||||
|
if len(lines) == 2:
|
||||||
|
saved_login = lines[0].decode().strip()
|
||||||
|
encrypted_pw = lines[1].strip()
|
||||||
|
decrypted_pw = fernet.decrypt(encrypted_pw).decode()
|
||||||
|
|
||||||
|
login_input.value = saved_login
|
||||||
|
password_input.value = decrypted_pw
|
||||||
|
remember_checkbox.value = True
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[ОШИБКА] Не удалось прочитать данные: {e}")
|
||||||
|
|
||||||
|
async def login_click(e):
|
||||||
|
if await login(login_input.value, password_input.value):
|
||||||
|
if remember_checkbox.value:
|
||||||
|
encrypted_pw = fernet.encrypt(password_input.value.encode())
|
||||||
|
with open(REMEMBER_FILE, "wb") as f:
|
||||||
|
f.write(f"{login_input.value}\n".encode())
|
||||||
|
f.write(encrypted_pw)
|
||||||
|
else:
|
||||||
|
if os.path.exists(REMEMBER_FILE):
|
||||||
|
os.remove(REMEMBER_FILE)
|
||||||
|
|
||||||
|
page.clean()
|
||||||
|
page.add(
|
||||||
|
ft.Column(
|
||||||
|
[
|
||||||
|
ft.Text(f"Добро пожаловать, {USERNAME}!", size=24),
|
||||||
|
ft.ElevatedButton("Выйти", on_click=lambda e: page.go("/"))
|
||||||
|
],
|
||||||
|
alignment=ft.MainAxisAlignment.CENTER,
|
||||||
|
horizontal_alignment=ft.CrossAxisAlignment.CENTER
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
status_text.value = "Неверный логин или пароль"
|
||||||
|
page.update()
|
||||||
|
|
||||||
|
page.add(
|
||||||
|
ft.Container(
|
||||||
|
content=ft.Column(
|
||||||
|
controls=[
|
||||||
|
ft.Image(
|
||||||
|
src="sonoma.png",
|
||||||
|
width=50,
|
||||||
|
height=50,
|
||||||
|
fit=ft.ImageFit.CONTAIN
|
||||||
|
),
|
||||||
|
ft.Text("SClient", size=24, weight=ft.FontWeight.BOLD),
|
||||||
|
login_input,
|
||||||
|
password_input,
|
||||||
|
remember_checkbox,
|
||||||
|
ft.ElevatedButton("Войти", on_click=login_click),
|
||||||
|
status_text
|
||||||
|
],
|
||||||
|
width=300,
|
||||||
|
alignment=ft.MainAxisAlignment.CENTER,
|
||||||
|
horizontal_alignment=ft.CrossAxisAlignment.CENTER
|
||||||
|
),
|
||||||
|
alignment=ft.alignment.center,
|
||||||
|
expand=True
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
ft.app(main)
|
||||||
20
server/docker-compose.yaml
Normal file
20
server/docker-compose.yaml
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
version: '3.9'
|
||||||
|
|
||||||
|
services:
|
||||||
|
postgres:
|
||||||
|
image: postgres:latest
|
||||||
|
container_name: postgres_container
|
||||||
|
environment:
|
||||||
|
POSTGRES_USER: ADMIN
|
||||||
|
POSTGRES_PASSWORD: 123123
|
||||||
|
POSTGRES_DB: sonoma-db
|
||||||
|
ports:
|
||||||
|
- "5432:5432"
|
||||||
|
volumes:
|
||||||
|
- pgdata:/var/lib/postgresql/data/pgdata
|
||||||
|
restart: unless-stopped
|
||||||
|
tty: true
|
||||||
|
stdin_open: true
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
pgdata:
|
||||||
164
server/main-server/.gitignore
vendored
Normal file
164
server/main-server/.gitignore
vendored
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
# Byte-compiled / optimized / DLL files
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
.idea
|
||||||
|
|
||||||
|
# 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
|
||||||
|
*.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/
|
||||||
|
|
||||||
|
# Flet
|
||||||
|
storage/
|
||||||
2
server/main-server/poetry.toml
Normal file
2
server/main-server/poetry.toml
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
[virtualenvs]
|
||||||
|
in-project = true
|
||||||
20
server/main-server/pyproject.toml
Normal file
20
server/main-server/pyproject.toml
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
[project]
|
||||||
|
name = "sclient-server-main"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = ""
|
||||||
|
authors = [
|
||||||
|
{name = "SaddyDEAD",email = "saddydead@sonoma.su"}
|
||||||
|
]
|
||||||
|
readme = "README.md"
|
||||||
|
requires-python = ">=3.12"
|
||||||
|
dependencies = [
|
||||||
|
"fastapi (>=0.115.14,<0.116.0)",
|
||||||
|
"asyncpg (>=0.30.0,<0.31.0)",
|
||||||
|
"uvicorn (>=0.35.0,<0.36.0)",
|
||||||
|
"fastapi-users[sqlalchemy] (>=14.0.1,<15.0.0)"
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
requires = ["poetry-core>=2.0.0,<3.0.0"]
|
||||||
|
build-backend = "poetry.core.masonry.api"
|
||||||
27
server/main-server/src/auth/transport.py
Normal file
27
server/main-server/src/auth/transport.py
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import uuid
|
||||||
|
from fastapi_users import FastAPIUsers, models
|
||||||
|
from fastapi_users.authentication import (
|
||||||
|
AuthenticationBackend,
|
||||||
|
BearerTransport,
|
||||||
|
JWTStrategy,
|
||||||
|
)
|
||||||
|
from fastapi_users.jwt import SecretType
|
||||||
|
|
||||||
|
from src.database.db import Database
|
||||||
|
from src.database.user import User
|
||||||
|
|
||||||
|
class Transport:
|
||||||
|
def __init__(self, secret: SecretType, db: Database):
|
||||||
|
self.secret = secret
|
||||||
|
self.db = db
|
||||||
|
self.bearer_transport = BearerTransport(tokenUrl="auth/jwt/login")
|
||||||
|
self.auth_backend = AuthenticationBackend(
|
||||||
|
name="jwt",
|
||||||
|
transport=self.bearer_transport,
|
||||||
|
get_strategy=self.get_jwt_strategy,
|
||||||
|
)
|
||||||
|
self.fastapi_users = FastAPIUsers[User, uuid.UUID](self.db.get_user_manager, [self.auth_backend])
|
||||||
|
self.current_active_user = self.fastapi_users.current_user(active=True)
|
||||||
|
|
||||||
|
def get_jwt_strategy(self) -> JWTStrategy[models.UP, models.ID]:
|
||||||
|
return JWTStrategy(secret=self.secret, lifetime_seconds=3600)
|
||||||
30
server/main-server/src/auth/user_manager.py
Normal file
30
server/main-server/src/auth/user_manager.py
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import uuid
|
||||||
|
from typing import Optional
|
||||||
|
from fastapi import Request
|
||||||
|
from fastapi_users import UUIDIDMixin, BaseUserManager
|
||||||
|
from fastapi_users.jwt import SecretType
|
||||||
|
|
||||||
|
from src.database.user import User
|
||||||
|
|
||||||
|
SECRET: SecretType
|
||||||
|
|
||||||
|
class UserManager(UUIDIDMixin, BaseUserManager[User, uuid.UUID]):
|
||||||
|
|
||||||
|
def __init__(self, secret: SecretType):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
reset_password_token_secret = SECRET
|
||||||
|
verification_token_secret = SECRET
|
||||||
|
|
||||||
|
async def on_after_register(self, user: User, request: Optional[Request] = None):
|
||||||
|
print(f"User {user.id} has registered.")
|
||||||
|
|
||||||
|
async def on_after_forgot_password(
|
||||||
|
self, user: User, token: str, request: Optional[Request] = None
|
||||||
|
):
|
||||||
|
print(f"User {user.id} has forgot their password. Reset token: {token}")
|
||||||
|
|
||||||
|
async def on_after_request_verify(
|
||||||
|
self, user: User, token: str, request: Optional[Request] = None
|
||||||
|
):
|
||||||
|
print(f"Verification requested for user {user.id}. Verification token: {token}")
|
||||||
40
server/main-server/src/database/db.py
Normal file
40
server/main-server/src/database/db.py
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
from collections.abc import AsyncGenerator
|
||||||
|
from fastapi import Depends
|
||||||
|
from fastapi_users.db import SQLAlchemyUserDatabase
|
||||||
|
from fastapi_users.jwt import SecretType
|
||||||
|
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
|
||||||
|
|
||||||
|
from src.auth.user_manager import UserManager
|
||||||
|
from src.database.user import User, Base
|
||||||
|
|
||||||
|
|
||||||
|
class Database:
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
db_user: str,
|
||||||
|
db_pass: str,
|
||||||
|
db_host: str,
|
||||||
|
db_port: int,
|
||||||
|
db_name: str,
|
||||||
|
secret: SecretType
|
||||||
|
):
|
||||||
|
self.DATABASE_URL = f'postgresql+asyncpg://{db_user}:{db_pass}@{db_host}:{db_port}/{db_name}'
|
||||||
|
self.engine = create_async_engine(self.DATABASE_URL)
|
||||||
|
self.async_session_maker = async_sessionmaker(self.engine, expire_on_commit=False)
|
||||||
|
self.secret = secret
|
||||||
|
|
||||||
|
async def create_db_and_tables(self):
|
||||||
|
async with self.engine.begin() as conn:
|
||||||
|
await conn.run_sync(Base.metadata.create_all)
|
||||||
|
|
||||||
|
|
||||||
|
async def get_async_session(self) -> AsyncGenerator[AsyncSession, None]:
|
||||||
|
async with self.async_session_maker() as session:
|
||||||
|
yield session
|
||||||
|
|
||||||
|
|
||||||
|
async def get_user_db(self, session: AsyncSession = Depends(get_async_session)):
|
||||||
|
yield SQLAlchemyUserDatabase(session, User)
|
||||||
|
|
||||||
|
async def get_user_manager(self, user_db: SQLAlchemyUserDatabase = Depends(get_user_db)):
|
||||||
|
yield UserManager(self.secret, user_db)
|
||||||
6
server/main-server/src/database/role.py
Normal file
6
server/main-server/src/database/role.py
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
from enum import Enum
|
||||||
|
|
||||||
|
|
||||||
|
class Role(Enum):
|
||||||
|
ADMIN = 'Admin'
|
||||||
|
USER = 'User'
|
||||||
15
server/main-server/src/database/schemas.py
Normal file
15
server/main-server/src/database/schemas.py
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import uuid
|
||||||
|
|
||||||
|
from fastapi_users import schemas
|
||||||
|
|
||||||
|
|
||||||
|
class UserRead(schemas.BaseUser[uuid.UUID]):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class UserCreate(schemas.BaseUserCreate):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class UserUpdate(schemas.BaseUserUpdate):
|
||||||
|
pass
|
||||||
13
server/main-server/src/database/user.py
Normal file
13
server/main-server/src/database/user.py
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
from fastapi_users_db_sqlalchemy import SQLAlchemyBaseUserTableUUID
|
||||||
|
from sqlalchemy.orm import DeclarativeBase, Mapped
|
||||||
|
from src.database.role import Role
|
||||||
|
|
||||||
|
|
||||||
|
class Base(DeclarativeBase):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class User(SQLAlchemyBaseUserTableUUID, Base):
|
||||||
|
username: Mapped[str]
|
||||||
|
role: Mapped[Role]
|
||||||
|
vpn_server_access: Mapped[bool]
|
||||||
|
main_server_access: Mapped[bool]
|
||||||
91
server/main-server/src/main.py
Normal file
91
server/main-server/src/main.py
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
import asyncio
|
||||||
|
from fastapi import Depends, FastAPI, HTTPException, Request
|
||||||
|
from fastapi.responses import RedirectResponse
|
||||||
|
from database.db import Database
|
||||||
|
import uvicorn
|
||||||
|
from src.database.schemas import *
|
||||||
|
from src.auth.transport import Transport
|
||||||
|
from src.database.user import User
|
||||||
|
|
||||||
|
app = FastAPI(title='sclient-main-server')
|
||||||
|
|
||||||
|
### Settings
|
||||||
|
# TODO: Create .env
|
||||||
|
|
||||||
|
PORT = 7535
|
||||||
|
ADMIN_NAME = 'admin'
|
||||||
|
ADMIN_PASSWORD = 'admin'
|
||||||
|
DATABASE_USER = 'ADMIN'
|
||||||
|
DATABASE_PASS = '123123'
|
||||||
|
DATABASE_HOST = '127.0.0.1'
|
||||||
|
DATABASE_PORT = 5432
|
||||||
|
DATABASE_NAME = 'sonoma-db'
|
||||||
|
SECRET = 'SECRET'
|
||||||
|
|
||||||
|
###
|
||||||
|
|
||||||
|
db = Database(
|
||||||
|
DATABASE_USER,
|
||||||
|
DATABASE_PASS,
|
||||||
|
DATABASE_HOST,
|
||||||
|
DATABASE_PORT,
|
||||||
|
DATABASE_NAME,
|
||||||
|
SECRET
|
||||||
|
)
|
||||||
|
|
||||||
|
transport = Transport(SECRET, db)
|
||||||
|
|
||||||
|
class App:
|
||||||
|
def init(self, loop) -> None:
|
||||||
|
config = uvicorn.Config(
|
||||||
|
app,
|
||||||
|
loop=loop,
|
||||||
|
host='0.0.0.0',
|
||||||
|
port=PORT
|
||||||
|
)
|
||||||
|
server = uvicorn.Server(config)
|
||||||
|
loop.run_until_complete(server.serve())
|
||||||
|
|
||||||
|
@app.get('/')
|
||||||
|
async def docs(self: Request):
|
||||||
|
return RedirectResponse(f'{self.url}docs')
|
||||||
|
|
||||||
|
@app.get("/authenticated-route")
|
||||||
|
async def authenticated_route(user: User = Depends(transport.current_active_user)):
|
||||||
|
return {"message": f"Hello {user.email}!"}
|
||||||
|
|
||||||
|
def main():
|
||||||
|
loop = asyncio.new_event_loop()
|
||||||
|
asyncio.set_event_loop(loop)
|
||||||
|
|
||||||
|
server = App()
|
||||||
|
|
||||||
|
app.include_router(
|
||||||
|
transport.fastapi_users.get_auth_router(transport.auth_backend), prefix="/auth/jwt", tags=["auth"]
|
||||||
|
)
|
||||||
|
app.include_router(
|
||||||
|
transport.fastapi_users.get_register_router(UserRead, UserCreate),
|
||||||
|
prefix="/auth",
|
||||||
|
tags=["auth"],
|
||||||
|
)
|
||||||
|
app.include_router(
|
||||||
|
transport.fastapi_users.get_reset_password_router(),
|
||||||
|
prefix="/auth",
|
||||||
|
tags=["auth"],
|
||||||
|
)
|
||||||
|
app.include_router(
|
||||||
|
transport.fastapi_users.get_verify_router(UserRead),
|
||||||
|
prefix="/auth",
|
||||||
|
tags=["auth"],
|
||||||
|
)
|
||||||
|
app.include_router(
|
||||||
|
transport.fastapi_users.get_users_router(UserRead, UserUpdate),
|
||||||
|
prefix="/users",
|
||||||
|
tags=["users"],
|
||||||
|
)
|
||||||
|
|
||||||
|
loop.run_until_complete(db.create_db_and_tables())
|
||||||
|
server.init(loop)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user