Compare commits
9 Commits
ebb066b3b0
...
f66b6221fd
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f66b6221fd | ||
|
|
7ddba306e8 | ||
|
|
124dbd019a | ||
|
|
581e736f1b | ||
|
|
7031411b3d | ||
|
|
e95bc4d196 | ||
|
|
49af914f9b | ||
|
|
e3550fedac | ||
|
|
738fa72ec0 |
548
.gitignore
vendored
548
.gitignore
vendored
@@ -1,548 +0,0 @@
|
|||||||
# ---> Go
|
|
||||||
# If you prefer the allow list template instead of the deny list, see community template:
|
|
||||||
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
|
|
||||||
#
|
|
||||||
# Binaries for programs and plugins
|
|
||||||
*.exe
|
|
||||||
*.exe~
|
|
||||||
*.dll
|
|
||||||
*.so
|
|
||||||
*.dylib
|
|
||||||
|
|
||||||
# Test binary, built with `go test -c`
|
|
||||||
*.test
|
|
||||||
|
|
||||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
|
||||||
*.out
|
|
||||||
|
|
||||||
# Dependency directories (remove the comment below to include it)
|
|
||||||
# vendor/
|
|
||||||
|
|
||||||
# Go workspace file
|
|
||||||
go.work
|
|
||||||
go.work.sum
|
|
||||||
|
|
||||||
# env file
|
|
||||||
.env
|
|
||||||
|
|
||||||
# ---> Python
|
|
||||||
# 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
|
|
||||||
|
|
||||||
# UV
|
|
||||||
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
|
||||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
||||||
# commonly ignored for libraries.
|
|
||||||
#uv.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/latest/usage/project/#working-with-version-control
|
|
||||||
.pdm.toml
|
|
||||||
.pdm-python
|
|
||||||
.pdm-build/
|
|
||||||
|
|
||||||
# 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/
|
|
||||||
|
|
||||||
# Ruff stuff:
|
|
||||||
.ruff_cache/
|
|
||||||
|
|
||||||
# PyPI configuration file
|
|
||||||
.pypirc
|
|
||||||
|
|
||||||
# ---> Rust
|
|
||||||
# Generated by Cargo
|
|
||||||
# will have compiled files and executables
|
|
||||||
debug/
|
|
||||||
target/
|
|
||||||
|
|
||||||
# These are backup files generated by rustfmt
|
|
||||||
**/*.rs.bk
|
|
||||||
|
|
||||||
# MSVC Windows builds of rustc generate these, which store debugging information
|
|
||||||
*.pdb
|
|
||||||
|
|
||||||
# RustRover
|
|
||||||
# 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/
|
|
||||||
# ---> Node
|
|
||||||
# Logs
|
|
||||||
logs
|
|
||||||
*.log
|
|
||||||
npm-debug.log*
|
|
||||||
yarn-debug.log*
|
|
||||||
yarn-error.log*
|
|
||||||
lerna-debug.log*
|
|
||||||
.pnpm-debug.log*
|
|
||||||
|
|
||||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
|
||||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
|
||||||
|
|
||||||
# Runtime data
|
|
||||||
pids
|
|
||||||
*.pid
|
|
||||||
*.seed
|
|
||||||
*.pid.lock
|
|
||||||
|
|
||||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
|
||||||
lib-cov
|
|
||||||
|
|
||||||
# Coverage directory used by tools like istanbul
|
|
||||||
coverage
|
|
||||||
*.lcov
|
|
||||||
|
|
||||||
# nyc test coverage
|
|
||||||
.nyc_output
|
|
||||||
|
|
||||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
|
||||||
.grunt
|
|
||||||
|
|
||||||
# Bower dependency directory (https://bower.io/)
|
|
||||||
bower_components
|
|
||||||
|
|
||||||
# node-waf configuration
|
|
||||||
.lock-wscript
|
|
||||||
|
|
||||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
|
||||||
build/Release
|
|
||||||
|
|
||||||
# Dependency directories
|
|
||||||
node_modules/
|
|
||||||
jspm_packages/
|
|
||||||
|
|
||||||
# Snowpack dependency directory (https://snowpack.dev/)
|
|
||||||
web_modules/
|
|
||||||
|
|
||||||
# TypeScript cache
|
|
||||||
*.tsbuildinfo
|
|
||||||
|
|
||||||
# Optional npm cache directory
|
|
||||||
.npm
|
|
||||||
|
|
||||||
# Optional eslint cache
|
|
||||||
.eslintcache
|
|
||||||
|
|
||||||
# Optional stylelint cache
|
|
||||||
.stylelintcache
|
|
||||||
|
|
||||||
# Microbundle cache
|
|
||||||
.rpt2_cache/
|
|
||||||
.rts2_cache_cjs/
|
|
||||||
.rts2_cache_es/
|
|
||||||
.rts2_cache_umd/
|
|
||||||
|
|
||||||
# Optional REPL history
|
|
||||||
.node_repl_history
|
|
||||||
|
|
||||||
# Output of 'npm pack'
|
|
||||||
*.tgz
|
|
||||||
|
|
||||||
# Yarn Integrity file
|
|
||||||
.yarn-integrity
|
|
||||||
|
|
||||||
# dotenv environment variable files
|
|
||||||
.env
|
|
||||||
.env.development.local
|
|
||||||
.env.test.local
|
|
||||||
.env.production.local
|
|
||||||
.env.local
|
|
||||||
|
|
||||||
# parcel-bundler cache (https://parceljs.org/)
|
|
||||||
.cache
|
|
||||||
.parcel-cache
|
|
||||||
|
|
||||||
# Next.js build output
|
|
||||||
.next
|
|
||||||
out
|
|
||||||
|
|
||||||
# Nuxt.js build / generate output
|
|
||||||
.nuxt
|
|
||||||
dist
|
|
||||||
|
|
||||||
# Gatsby files
|
|
||||||
.cache/
|
|
||||||
# Comment in the public line in if your project uses Gatsby and not Next.js
|
|
||||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
|
||||||
# public
|
|
||||||
|
|
||||||
# vuepress build output
|
|
||||||
.vuepress/dist
|
|
||||||
|
|
||||||
# vuepress v2.x temp and cache directory
|
|
||||||
.temp
|
|
||||||
.cache
|
|
||||||
|
|
||||||
# vitepress build output
|
|
||||||
**/.vitepress/dist
|
|
||||||
|
|
||||||
# vitepress cache directory
|
|
||||||
**/.vitepress/cache
|
|
||||||
|
|
||||||
# Docusaurus cache and generated files
|
|
||||||
.docusaurus
|
|
||||||
|
|
||||||
# Serverless directories
|
|
||||||
.serverless/
|
|
||||||
|
|
||||||
# FuseBox cache
|
|
||||||
.fusebox/
|
|
||||||
|
|
||||||
# DynamoDB Local files
|
|
||||||
.dynamodb/
|
|
||||||
|
|
||||||
# TernJS port file
|
|
||||||
.tern-port
|
|
||||||
|
|
||||||
# Stores VSCode versions used for testing VSCode extensions
|
|
||||||
.vscode-test
|
|
||||||
|
|
||||||
# yarn v2
|
|
||||||
.yarn/cache
|
|
||||||
.yarn/unplugged
|
|
||||||
.yarn/build-state.yml
|
|
||||||
.yarn/install-state.gz
|
|
||||||
.pnp.*
|
|
||||||
|
|
||||||
# ---> Xcode
|
|
||||||
## User settings
|
|
||||||
xcuserdata/
|
|
||||||
|
|
||||||
# ---> macOS
|
|
||||||
# General
|
|
||||||
.DS_Store
|
|
||||||
.AppleDouble
|
|
||||||
.LSOverride
|
|
||||||
|
|
||||||
# Icon must end with two \r
|
|
||||||
Icon
|
|
||||||
|
|
||||||
|
|
||||||
# Thumbnails
|
|
||||||
._*
|
|
||||||
|
|
||||||
# Files that might appear in the root of a volume
|
|
||||||
.DocumentRevisions-V100
|
|
||||||
.fseventsd
|
|
||||||
.Spotlight-V100
|
|
||||||
.TemporaryItems
|
|
||||||
.Trashes
|
|
||||||
.VolumeIcon.icns
|
|
||||||
.com.apple.timemachine.donotpresent
|
|
||||||
|
|
||||||
# Directories potentially created on remote AFP share
|
|
||||||
.AppleDB
|
|
||||||
.AppleDesktop
|
|
||||||
Network Trash Folder
|
|
||||||
Temporary Items
|
|
||||||
.apdisk
|
|
||||||
|
|
||||||
# ---> Flutter
|
|
||||||
# Miscellaneous
|
|
||||||
*.class
|
|
||||||
*.lock
|
|
||||||
*.log
|
|
||||||
*.pyc
|
|
||||||
*.swp
|
|
||||||
.buildlog/
|
|
||||||
.history
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Flutter repo-specific
|
|
||||||
/bin/cache/
|
|
||||||
/bin/internal/bootstrap.bat
|
|
||||||
/bin/internal/bootstrap.sh
|
|
||||||
/bin/mingit/
|
|
||||||
/dev/benchmarks/mega_gallery/
|
|
||||||
/dev/bots/.recipe_deps
|
|
||||||
/dev/bots/android_tools/
|
|
||||||
/dev/devicelab/ABresults*.json
|
|
||||||
/dev/docs/doc/
|
|
||||||
/dev/docs/flutter.docs.zip
|
|
||||||
/dev/docs/lib/
|
|
||||||
/dev/docs/pubspec.yaml
|
|
||||||
/dev/integration_tests/**/xcuserdata
|
|
||||||
/dev/integration_tests/**/Pods
|
|
||||||
/packages/flutter/coverage/
|
|
||||||
version
|
|
||||||
analysis_benchmark.json
|
|
||||||
|
|
||||||
# packages file containing multi-root paths
|
|
||||||
.packages.generated
|
|
||||||
|
|
||||||
# Flutter/Dart/Pub related
|
|
||||||
**/doc/api/
|
|
||||||
.dart_tool/
|
|
||||||
.flutter-plugins
|
|
||||||
.flutter-plugins-dependencies
|
|
||||||
**/generated_plugin_registrant.dart
|
|
||||||
.packages
|
|
||||||
.pub-preload-cache/
|
|
||||||
.pub/
|
|
||||||
build/
|
|
||||||
flutter_*.png
|
|
||||||
linked_*.ds
|
|
||||||
unlinked.ds
|
|
||||||
unlinked_spec.ds
|
|
||||||
|
|
||||||
# Android related
|
|
||||||
**/android/**/gradle-wrapper.jar
|
|
||||||
.gradle/
|
|
||||||
**/android/captures/
|
|
||||||
**/android/gradlew
|
|
||||||
**/android/gradlew.bat
|
|
||||||
**/android/local.properties
|
|
||||||
**/android/**/GeneratedPluginRegistrant.java
|
|
||||||
**/android/key.properties
|
|
||||||
*.jks
|
|
||||||
|
|
||||||
# iOS/XCode related
|
|
||||||
**/ios/**/*.mode1v3
|
|
||||||
**/ios/**/*.mode2v3
|
|
||||||
**/ios/**/*.moved-aside
|
|
||||||
**/ios/**/*.pbxuser
|
|
||||||
**/ios/**/*.perspectivev3
|
|
||||||
**/ios/**/*sync/
|
|
||||||
**/ios/**/.sconsign.dblite
|
|
||||||
**/ios/**/.tags*
|
|
||||||
**/ios/**/.vagrant/
|
|
||||||
**/ios/**/DerivedData/
|
|
||||||
**/ios/**/Icon?
|
|
||||||
**/ios/**/Pods/
|
|
||||||
**/ios/**/.symlinks/
|
|
||||||
**/ios/**/profile
|
|
||||||
**/ios/**/xcuserdata
|
|
||||||
**/ios/.generated/
|
|
||||||
**/ios/Flutter/.last_build_id
|
|
||||||
**/ios/Flutter/App.framework
|
|
||||||
**/ios/Flutter/Flutter.framework
|
|
||||||
**/ios/Flutter/Flutter.podspec
|
|
||||||
**/ios/Flutter/Generated.xcconfig
|
|
||||||
**/ios/Flutter/ephemeral
|
|
||||||
**/ios/Flutter/app.flx
|
|
||||||
**/ios/Flutter/app.zip
|
|
||||||
**/ios/Flutter/flutter_assets/
|
|
||||||
**/ios/Flutter/flutter_export_environment.sh
|
|
||||||
**/ios/ServiceDefinitions.json
|
|
||||||
**/ios/Runner/GeneratedPluginRegistrant.*
|
|
||||||
|
|
||||||
# macOS
|
|
||||||
**/Flutter/ephemeral/
|
|
||||||
**/Pods/
|
|
||||||
**/macos/Flutter/GeneratedPluginRegistrant.swift
|
|
||||||
**/macos/Flutter/ephemeral
|
|
||||||
**/xcuserdata/
|
|
||||||
|
|
||||||
# Windows
|
|
||||||
**/windows/flutter/generated_plugin_registrant.cc
|
|
||||||
**/windows/flutter/generated_plugin_registrant.h
|
|
||||||
**/windows/flutter/generated_plugins.cmake
|
|
||||||
|
|
||||||
# Linux
|
|
||||||
**/linux/flutter/generated_plugin_registrant.cc
|
|
||||||
**/linux/flutter/generated_plugin_registrant.h
|
|
||||||
**/linux/flutter/generated_plugins.cmake
|
|
||||||
|
|
||||||
# Coverage
|
|
||||||
coverage/
|
|
||||||
|
|
||||||
# Symbols
|
|
||||||
app.*.symbols
|
|
||||||
|
|
||||||
# Exceptions to above rules.
|
|
||||||
!**/ios/**/default.mode1v3
|
|
||||||
!**/ios/**/default.mode2v3
|
|
||||||
!**/ios/**/default.pbxuser
|
|
||||||
!**/ios/**/default.perspectivev3
|
|
||||||
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
|
|
||||||
!/dev/ci/**/Gemfile.lock
|
|
||||||
# ---> Android
|
|
||||||
# Gradle files
|
|
||||||
.gradle/
|
|
||||||
build/
|
|
||||||
|
|
||||||
# Local configuration file (sdk path, etc)
|
|
||||||
local.properties
|
|
||||||
|
|
||||||
# Log/OS Files
|
|
||||||
*.log
|
|
||||||
|
|
||||||
# Android Studio generated files and folders
|
|
||||||
captures/
|
|
||||||
.externalNativeBuild/
|
|
||||||
.cxx/
|
|
||||||
*.apk
|
|
||||||
output.json
|
|
||||||
|
|
||||||
# IntelliJ
|
|
||||||
*.iml
|
|
||||||
.idea/
|
|
||||||
misc.xml
|
|
||||||
deploymentTargetDropDown.xml
|
|
||||||
render.experimental.xml
|
|
||||||
|
|
||||||
# Keystore files
|
|
||||||
*.jks
|
|
||||||
*.keystore
|
|
||||||
|
|
||||||
# Google Services (e.g. APIs or Firebase)
|
|
||||||
google-services.json
|
|
||||||
|
|
||||||
# Android Profiling
|
|
||||||
*.hprof
|
|
||||||
|
|
||||||
backend/desc.md
|
|
||||||
18
LICENSE
18
LICENSE
@@ -1,18 +0,0 @@
|
|||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2026 fish
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
|
||||||
associated documentation files (the "Software"), to deal in the Software without restriction, including
|
|
||||||
without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
|
|
||||||
following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all copies or substantial
|
|
||||||
portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
|
|
||||||
LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
|
|
||||||
EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
|
||||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
|
||||||
USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
-- 用户表初始化
|
|
||||||
CREATE TABLE IF NOT EXISTS users (
|
|
||||||
id SERIAL PRIMARY KEY,
|
|
||||||
username VARCHAR(50) UNIQUE NOT NULL,
|
|
||||||
password_hash VARCHAR(255) NOT NULL,
|
|
||||||
email VARCHAR(100) UNIQUE,
|
|
||||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
|
||||||
);
|
|
||||||
|
|
||||||
-- 插入测试用户(密码: 123456)
|
|
||||||
-- bcrypt hash: $2b$12$REwMlLDCbzR4UpL6MWnzE.AacihwpFvQhGs7vDKTwwyNMb1qBWOTm
|
|
||||||
INSERT INTO users (username, password_hash, email)
|
|
||||||
VALUES ('admin', '$2b$12$REwMlLDCbzR4UpL6MWnzE.AacihwpFvQhGs7vDKTwwyNMb1qBWOTm', 'admin@example.com')
|
|
||||||
ON CONFLICT (username) DO NOTHING;
|
|
||||||
@@ -1,185 +0,0 @@
|
|||||||
use axum::{
|
|
||||||
extract::State,
|
|
||||||
http::StatusCode,
|
|
||||||
response::Json,
|
|
||||||
routing::post,
|
|
||||||
Router,
|
|
||||||
};
|
|
||||||
use bcrypt::hash;
|
|
||||||
use chrono::Utc;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use sqlx::PgPool;
|
|
||||||
use std::env;
|
|
||||||
use std::sync::Arc;
|
|
||||||
use tracing::{info, warn};
|
|
||||||
use validator::Validate;
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
struct AppState {
|
|
||||||
db: PgPool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Validate)]
|
|
||||||
struct RegisterRequest {
|
|
||||||
#[validate(length(min = 3, max = 50))]
|
|
||||||
username: String,
|
|
||||||
#[validate(length(min = 6))]
|
|
||||||
password: String,
|
|
||||||
#[validate(email)]
|
|
||||||
email: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize)]
|
|
||||||
struct RegisterResponse {
|
|
||||||
success: bool,
|
|
||||||
user_id: Option<i32>,
|
|
||||||
message: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize)]
|
|
||||||
struct ErrorResponse {
|
|
||||||
error: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::main]
|
|
||||||
async fn main() {
|
|
||||||
tracing_subscriber::fmt::init();
|
|
||||||
|
|
||||||
info!("Starting user-register service...");
|
|
||||||
|
|
||||||
let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set");
|
|
||||||
let pool = sqlx::postgres::PgPool::connect(&database_url)
|
|
||||||
.await
|
|
||||||
.expect("Failed to connect to database");
|
|
||||||
|
|
||||||
info!("Database connected");
|
|
||||||
|
|
||||||
let state = Arc::new(AppState { db: pool });
|
|
||||||
|
|
||||||
let app = Router::new()
|
|
||||||
.route("/register", post(register_handler))
|
|
||||||
.route("/health", axum::routing::get(health_handler))
|
|
||||||
.with_state(state);
|
|
||||||
|
|
||||||
let port = env::var("SERVICE_PORT").unwrap_or_else(|_| "8080".to_string());
|
|
||||||
let listener = tokio::net::TcpListener::bind(format!("0.0.0.0:{}", port))
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
info!("User-register service listening on port {}", port);
|
|
||||||
|
|
||||||
axum::serve(listener, app).await.unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn register_handler(
|
|
||||||
State(state): State<Arc<AppState>>,
|
|
||||||
Json(payload): Json<RegisterRequest>,
|
|
||||||
) -> (StatusCode, Json<RegisterResponse>) {
|
|
||||||
info!("Registration attempt for user: {}", payload.username);
|
|
||||||
|
|
||||||
// 参数校验
|
|
||||||
if let Err(e) = payload.validate() {
|
|
||||||
return (
|
|
||||||
StatusCode::BAD_REQUEST,
|
|
||||||
Json(RegisterResponse {
|
|
||||||
success: false,
|
|
||||||
user_id: None,
|
|
||||||
message: format!("Validation error: {}", e),
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查用户名是否存在
|
|
||||||
let existing: Option<(i32,)> = sqlx::query_as("SELECT id FROM users WHERE username = $1")
|
|
||||||
.bind(&payload.username)
|
|
||||||
.fetch_optional(&state.db)
|
|
||||||
.await
|
|
||||||
.unwrap_or(None);
|
|
||||||
|
|
||||||
if existing.is_some() {
|
|
||||||
return (
|
|
||||||
StatusCode::CONFLICT,
|
|
||||||
Json(RegisterResponse {
|
|
||||||
success: false,
|
|
||||||
user_id: None,
|
|
||||||
message: "Username already exists".to_string(),
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查邮箱是否存在
|
|
||||||
let existing_email: Option<(i32,)> = sqlx::query_as("SELECT id FROM users WHERE email = $1")
|
|
||||||
.bind(&payload.email)
|
|
||||||
.fetch_optional(&state.db)
|
|
||||||
.await
|
|
||||||
.unwrap_or(None);
|
|
||||||
|
|
||||||
if existing_email.is_some() {
|
|
||||||
return (
|
|
||||||
StatusCode::CONFLICT,
|
|
||||||
Json(RegisterResponse {
|
|
||||||
success: false,
|
|
||||||
user_id: None,
|
|
||||||
message: "Email already exists".to_string(),
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 密码哈希
|
|
||||||
let password_hash = match hash(&payload.password, bcrypt::DEFAULT_COST) {
|
|
||||||
Ok(h) => h,
|
|
||||||
Err(e) => {
|
|
||||||
warn!("Password hashing failed: {}", e);
|
|
||||||
return (
|
|
||||||
StatusCode::INTERNAL_SERVER_ERROR,
|
|
||||||
Json(RegisterResponse {
|
|
||||||
success: false,
|
|
||||||
user_id: None,
|
|
||||||
message: "Internal error".to_string(),
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 插入用户
|
|
||||||
let result = sqlx::query_as::<_, (i32,)>(
|
|
||||||
"INSERT INTO users (username, password_hash, email, created_at, updated_at)
|
|
||||||
VALUES ($1, $2, $3, $4, $4)
|
|
||||||
RETURNING id"
|
|
||||||
)
|
|
||||||
.bind(&payload.username)
|
|
||||||
.bind(&password_hash)
|
|
||||||
.bind(&payload.email)
|
|
||||||
.bind(Utc::now())
|
|
||||||
.fetch_one(&state.db)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
match result {
|
|
||||||
Ok((user_id,)) => {
|
|
||||||
info!("User {} registered with id {}", payload.username, user_id);
|
|
||||||
(
|
|
||||||
StatusCode::CREATED,
|
|
||||||
Json(RegisterResponse {
|
|
||||||
success: true,
|
|
||||||
user_id: Some(user_id),
|
|
||||||
message: "User registered successfully".to_string(),
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
warn!("Registration failed: {}", e);
|
|
||||||
(
|
|
||||||
StatusCode::INTERNAL_SERVER_ERROR,
|
|
||||||
Json(RegisterResponse {
|
|
||||||
success: false,
|
|
||||||
user_id: None,
|
|
||||||
message: "Registration failed".to_string(),
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn health_handler() -> &'static str {
|
|
||||||
"OK"
|
|
||||||
}
|
|
||||||
@@ -8,7 +8,7 @@ services:
|
|||||||
container_name: user-login
|
container_name: user-login
|
||||||
environment:
|
environment:
|
||||||
- RUST_LOG=info
|
- RUST_LOG=info
|
||||||
- DATABASE_URL=postgres://postgres:postgres@user-db:5432/user_db
|
- DATABASE_URL=postgres://postgres:postgres@user-db:5432/user-db
|
||||||
- REDIS_URL=redis://user-redis:6379/0
|
- REDIS_URL=redis://user-redis:6379/0
|
||||||
- SERVICE_NAME=user-login
|
- SERVICE_NAME=user-login
|
||||||
- SERVICE_PORT=8080
|
- SERVICE_PORT=8080
|
||||||
@@ -36,7 +36,7 @@ services:
|
|||||||
container_name: user-register
|
container_name: user-register
|
||||||
environment:
|
environment:
|
||||||
- RUST_LOG=info
|
- RUST_LOG=info
|
||||||
- DATABASE_URL=postgres://postgres:postgres@user-db:5432/user_db
|
- DATABASE_URL=postgres://postgres:postgres@user-db:5432/user-db
|
||||||
- REDIS_URL=redis://user-redis:6379/0
|
- REDIS_URL=redis://user-redis:6379/0
|
||||||
- SERVICE_NAME=user-register
|
- SERVICE_NAME=user-register
|
||||||
- SERVICE_PORT=8080
|
- SERVICE_PORT=8080
|
||||||
@@ -62,9 +62,9 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
- POSTGRES_USER=postgres
|
- POSTGRES_USER=postgres
|
||||||
- POSTGRES_PASSWORD=postgres
|
- POSTGRES_PASSWORD=postgres
|
||||||
- POSTGRES_DB=user_db
|
- POSTGRES_DB=user-db
|
||||||
volumes:
|
volumes:
|
||||||
- user_postgres_data:/var/lib/postgresql/data
|
- user-postgres-data:/var/lib/postgresql/data
|
||||||
- ./migrations:/docker-entrypoint-initdb.d:ro
|
- ./migrations:/docker-entrypoint-initdb.d:ro
|
||||||
ports:
|
ports:
|
||||||
- "5432:5432"
|
- "5432:5432"
|
||||||
@@ -72,7 +72,7 @@ services:
|
|||||||
- user-network
|
- user-network
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD-SHELL", "pg_isready -U postgres -d user_db"]
|
test: ["CMD-SHELL", "pg_isready -U postgres -d user-db"]
|
||||||
interval: 10s
|
interval: 10s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 5
|
retries: 5
|
||||||
@@ -81,7 +81,7 @@ services:
|
|||||||
image: redis:8.6.2-alpine
|
image: redis:8.6.2-alpine
|
||||||
container_name: user-redis
|
container_name: user-redis
|
||||||
volumes:
|
volumes:
|
||||||
- user_redis_data:/data
|
- user-redis-data:/data
|
||||||
ports:
|
ports:
|
||||||
- "6379:6379"
|
- "6379:6379"
|
||||||
networks:
|
networks:
|
||||||
@@ -98,5 +98,7 @@ networks:
|
|||||||
driver: bridge
|
driver: bridge
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
user_postgres_data:
|
user-postgres-data:
|
||||||
user_redis_data:
|
name: user-postgres-data
|
||||||
|
user-redis-data:
|
||||||
|
name: user-redis-data
|
||||||
36
services/user-service/migrations/001_init.sql
Normal file
36
services/user-service/migrations/001_init.sql
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
-- 用户主表
|
||||||
|
CREATE TABLE IF NOT EXISTS user_main (
|
||||||
|
id UUID PRIMARY KEY,
|
||||||
|
deleted BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
|
createdate TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
modifydate TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 用户登录账号表
|
||||||
|
CREATE TABLE IF NOT EXISTS user_login_account (
|
||||||
|
id UUID PRIMARY KEY,
|
||||||
|
user_id UUID NOT NULL,
|
||||||
|
account VARCHAR(100) NOT NULL,
|
||||||
|
deleted BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
|
createdate TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
modifydate TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
CONSTRAINT fk_user_login_account_user_main FOREIGN KEY (user_id) REFERENCES user_main(id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_user_login_account_active
|
||||||
|
ON user_login_account(account)
|
||||||
|
WHERE deleted = FALSE;
|
||||||
|
|
||||||
|
-- 用户密码表
|
||||||
|
CREATE TABLE IF NOT EXISTS user_login_password (
|
||||||
|
id UUID PRIMARY KEY,
|
||||||
|
user_id UUID NOT NULL,
|
||||||
|
password VARCHAR(255) NOT NULL,
|
||||||
|
deleted BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
|
createdate TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
modifydate TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
CONSTRAINT fk_user_login_password_user_main FOREIGN KEY (user_id) REFERENCES user_main(id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_user_login_password_user_id
|
||||||
|
ON user_login_password(user_id);
|
||||||
@@ -14,7 +14,10 @@ serde = { version = "1.0", features = ["derive"] }
|
|||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
|
|
||||||
# 数据库
|
# 数据库
|
||||||
sqlx = { version = "0.8", features = ["runtime-tokio", "tls-rustls", "postgres", "chrono"] }
|
sqlx = { version = "0.8", features = ["runtime-tokio", "tls-rustls", "postgres", "chrono", "uuid"] }
|
||||||
|
|
||||||
|
# UUID
|
||||||
|
uuid = { version = "1", features = ["v7", "serde"] }
|
||||||
|
|
||||||
# Redis
|
# Redis
|
||||||
redis = { version = "0.29", features = ["tokio-comp"] }
|
redis = { version = "0.29", features = ["tokio-comp"] }
|
||||||
@@ -90,9 +90,12 @@ async fn login_handler(
|
|||||||
) -> (StatusCode, Json<LoginResponse>) {
|
) -> (StatusCode, Json<LoginResponse>) {
|
||||||
info!("Login attempt for user: {}", payload.username);
|
info!("Login attempt for user: {}", payload.username);
|
||||||
|
|
||||||
// 查询用户
|
// 查询用户账号与密码
|
||||||
let user: Option<(String,)> = sqlx::query_as(
|
let user: Option<(String,)> = sqlx::query_as(
|
||||||
"SELECT password_hash FROM users WHERE username = $1"
|
"SELECT p.password \
|
||||||
|
FROM user_login_account a \
|
||||||
|
JOIN user_login_password p ON a.user_id = p.user_id \
|
||||||
|
WHERE a.account = $1 AND a.deleted = FALSE AND p.deleted = FALSE"
|
||||||
)
|
)
|
||||||
.bind(&payload.username)
|
.bind(&payload.username)
|
||||||
.fetch_optional(&state.db)
|
.fetch_optional(&state.db)
|
||||||
@@ -11,7 +11,10 @@ tower = "0.5"
|
|||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
|
|
||||||
sqlx = { version = "0.8", features = ["runtime-tokio", "tls-rustls", "postgres", "chrono"] }
|
sqlx = { version = "0.8", features = ["runtime-tokio", "tls-rustls", "postgres", "chrono", "uuid"] }
|
||||||
|
|
||||||
|
# UUID
|
||||||
|
uuid = { version = "1", features = ["v7", "serde"] }
|
||||||
redis = { version = "0.29", features = ["tokio-comp"] }
|
redis = { version = "0.29", features = ["tokio-comp"] }
|
||||||
|
|
||||||
bcrypt = "0.17"
|
bcrypt = "0.17"
|
||||||
252
services/user-service/user-register/src/main.rs
Normal file
252
services/user-service/user-register/src/main.rs
Normal file
@@ -0,0 +1,252 @@
|
|||||||
|
use axum::{
|
||||||
|
extract::State,
|
||||||
|
http::StatusCode,
|
||||||
|
response::Json,
|
||||||
|
routing::{get, post},
|
||||||
|
Router,
|
||||||
|
};
|
||||||
|
use bcrypt::hash;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use sqlx::PgPool;
|
||||||
|
use std::env;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use tracing::{info, warn};
|
||||||
|
use uuid::Uuid;
|
||||||
|
use validator::Validate;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct AppState {
|
||||||
|
db: PgPool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Validate)]
|
||||||
|
struct RegisterRequest {
|
||||||
|
#[validate(length(min = 3, max = 50))]
|
||||||
|
username: String,
|
||||||
|
#[validate(length(min = 6))]
|
||||||
|
password: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct ApiRequest<T> {
|
||||||
|
device: i32,
|
||||||
|
language: i32,
|
||||||
|
data: T,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
struct ApiResponse<T> {
|
||||||
|
success: bool,
|
||||||
|
message: String,
|
||||||
|
data: Option<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
struct RegisterData {
|
||||||
|
user_id: Uuid,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
tracing_subscriber::fmt::init();
|
||||||
|
|
||||||
|
info!("Starting user-register service...");
|
||||||
|
|
||||||
|
let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set");
|
||||||
|
let pool = sqlx::postgres::PgPool::connect(&database_url)
|
||||||
|
.await
|
||||||
|
.expect("Failed to connect to database");
|
||||||
|
|
||||||
|
info!("Database connected");
|
||||||
|
|
||||||
|
let state = Arc::new(AppState { db: pool });
|
||||||
|
|
||||||
|
let app = Router::new()
|
||||||
|
.route("/register", post(register_handler))
|
||||||
|
.route("/health", get(health_handler))
|
||||||
|
.with_state(state);
|
||||||
|
|
||||||
|
let port = env::var("SERVICE_PORT").unwrap_or_else(|_| "8080".to_string());
|
||||||
|
let listener = tokio::net::TcpListener::bind(format!("0.0.0.0:{}", port))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
info!("User-register service listening on port {}", port);
|
||||||
|
|
||||||
|
axum::serve(listener, app).await.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn register_handler(
|
||||||
|
State(state): State<Arc<AppState>>,
|
||||||
|
Json(req): Json<ApiRequest<RegisterRequest>>,
|
||||||
|
) -> (StatusCode, Json<ApiResponse<RegisterData>>) {
|
||||||
|
info!(
|
||||||
|
"Registration attempt for user: {}, device: {}, language: {}",
|
||||||
|
req.data.username, req.device, req.language
|
||||||
|
);
|
||||||
|
|
||||||
|
// 参数校验
|
||||||
|
if let Err(e) = req.data.validate() {
|
||||||
|
return (
|
||||||
|
StatusCode::BAD_REQUEST,
|
||||||
|
Json(ApiResponse {
|
||||||
|
success: false,
|
||||||
|
message: format!("Validation error: {}", e),
|
||||||
|
data: None,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查账号是否已存在
|
||||||
|
let existing: Option<(Uuid,)> = sqlx::query_as(
|
||||||
|
"SELECT id FROM user_login_account WHERE account = $1 AND deleted = FALSE"
|
||||||
|
)
|
||||||
|
.bind(&req.data.username)
|
||||||
|
.fetch_optional(&state.db)
|
||||||
|
.await
|
||||||
|
.unwrap_or(None);
|
||||||
|
|
||||||
|
if existing.is_some() {
|
||||||
|
return (
|
||||||
|
StatusCode::CONFLICT,
|
||||||
|
Json(ApiResponse {
|
||||||
|
success: false,
|
||||||
|
message: "Username already exists".to_string(),
|
||||||
|
data: None,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 密码哈希
|
||||||
|
let password_hash = match hash(&req.data.password, bcrypt::DEFAULT_COST) {
|
||||||
|
Ok(h) => h,
|
||||||
|
Err(e) => {
|
||||||
|
warn!("Password hashing failed: {}", e);
|
||||||
|
return (
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
Json(ApiResponse {
|
||||||
|
success: false,
|
||||||
|
message: "Internal error".to_string(),
|
||||||
|
data: None,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 插入用户(主从表事务)
|
||||||
|
let mut tx = match state.db.begin().await {
|
||||||
|
Ok(t) => t,
|
||||||
|
Err(e) => {
|
||||||
|
warn!("Transaction start failed: {}", e);
|
||||||
|
return (
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
Json(ApiResponse {
|
||||||
|
success: false,
|
||||||
|
message: "Internal error".to_string(),
|
||||||
|
data: None,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let user_id = Uuid::now_v7();
|
||||||
|
|
||||||
|
if let Err(e) = sqlx::query(
|
||||||
|
"INSERT INTO user_main (id) VALUES ($1)"
|
||||||
|
)
|
||||||
|
.bind(user_id)
|
||||||
|
.execute(&mut *tx)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
warn!("Insert user_main failed: {}", e);
|
||||||
|
let _ = tx.rollback().await;
|
||||||
|
return (
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
Json(ApiResponse {
|
||||||
|
success: false,
|
||||||
|
message: "Registration failed".to_string(),
|
||||||
|
data: None,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let account_id = Uuid::now_v7();
|
||||||
|
if let Err(e) = sqlx::query(
|
||||||
|
"INSERT INTO user_login_account (id, user_id, account) VALUES ($1, $2, $3)"
|
||||||
|
)
|
||||||
|
.bind(account_id)
|
||||||
|
.bind(user_id)
|
||||||
|
.bind(&req.data.username)
|
||||||
|
.execute(&mut *tx)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
warn!("Insert user_login_account failed: {}", e);
|
||||||
|
let _ = tx.rollback().await;
|
||||||
|
return (
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
Json(ApiResponse {
|
||||||
|
success: false,
|
||||||
|
message: "Registration failed".to_string(),
|
||||||
|
data: None,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let password_id = Uuid::now_v7();
|
||||||
|
if let Err(e) = sqlx::query(
|
||||||
|
"INSERT INTO user_login_password (id, user_id, password) VALUES ($1, $2, $3)"
|
||||||
|
)
|
||||||
|
.bind(password_id)
|
||||||
|
.bind(user_id)
|
||||||
|
.bind(&password_hash)
|
||||||
|
.execute(&mut *tx)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
warn!("Insert user_login_password failed: {}", e);
|
||||||
|
let _ = tx.rollback().await;
|
||||||
|
return (
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
Json(ApiResponse {
|
||||||
|
success: false,
|
||||||
|
message: "Registration failed".to_string(),
|
||||||
|
data: None,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
match tx.commit().await {
|
||||||
|
Ok(()) => {
|
||||||
|
info!("User {} registered with id {}", req.data.username, user_id);
|
||||||
|
(
|
||||||
|
StatusCode::CREATED,
|
||||||
|
Json(ApiResponse {
|
||||||
|
success: true,
|
||||||
|
message: "User registered successfully".to_string(),
|
||||||
|
data: Some(RegisterData { user_id }),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
warn!("Registration failed: {}", e);
|
||||||
|
(
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
Json(ApiResponse {
|
||||||
|
success: false,
|
||||||
|
message: "Registration failed".to_string(),
|
||||||
|
data: None,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn health_handler() -> (StatusCode, Json<ApiResponse<()>>) {
|
||||||
|
(
|
||||||
|
StatusCode::OK,
|
||||||
|
Json(ApiResponse {
|
||||||
|
success: true,
|
||||||
|
message: "OK".to_string(),
|
||||||
|
data: None,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user