From 2f17288c2db40b4b87ec138a73ced8ece61a394f Mon Sep 17 00:00:00 2001 From: Tohru <65994850+Tohrusky@users.noreply.github.com> Date: Thu, 11 Jul 2024 20:25:05 +0800 Subject: [PATCH] Initial commit (#1) --- .dockerignore | 34 ++ .github/workflows/CI-test.yml | 60 ++++ .github/workflows/docker.yml | 28 ++ .github/workflows/golangci-lint.yml | 29 ++ .github/workflows/release.yml | 37 ++ .gitignore | 120 +++++++ .golangci.yml | 35 ++ .goreleaser.yaml | 58 ++++ .pre-commit-config.yaml | 23 ++ Dockerfile | 23 ++ Makefile | 27 ++ README.md | 2 +- cmd/cmd.go | 18 + cmd/gen/main.go | 67 ++++ cmd/web.go | 49 +++ conf/nuxbt.yml | 41 +++ dal/model/user.gen.go | 35 ++ dal/model/user_role.gen.go | 27 ++ dal/query/gen.go | 111 ++++++ dal/query/user.gen.go | 428 ++++++++++++++++++++++++ dal/query/user_role.gen.go | 396 ++++++++++++++++++++++ go.mod | 110 ++++++ go.sum | 267 +++++++++++++++ internal/middleware/cache/cache.go | 34 ++ internal/middleware/cache/ip_limiter.go | 45 +++ internal/middleware/jwt/auth.go | 39 +++ internal/middleware/jwt/jwt.go | 84 +++++ internal/middleware/logger/logger.go | 99 ++++++ internal/router/api/v1/api.go | 42 +++ internal/router/init.go | 61 ++++ internal/service/user/dao.go | 40 +++ internal/service/user/login.go | 44 +++ internal/service/user/profile.go | 62 ++++ internal/service/user/register.go | 82 +++++ main.go | 23 ++ module/cache/redis.go | 116 +++++++ module/config/config.go | 134 ++++++++ module/config/generate.go | 49 +++ module/config/type.go | 47 +++ module/db/db.go | 75 +++++ module/log/log.go | 45 +++ module/oss/oss.go | 84 +++++ module/util/gin.go | 59 ++++ module/util/print.go | 44 +++ module/util/random.go | 22 ++ module/util/random_test.go | 19 ++ scripts/README.md | 19 ++ scripts/db.sql | 29 ++ 48 files changed, 3421 insertions(+), 1 deletion(-) create mode 100644 .dockerignore create mode 100644 .github/workflows/CI-test.yml create mode 100644 .github/workflows/docker.yml create mode 100644 .github/workflows/golangci-lint.yml create mode 100644 .github/workflows/release.yml create mode 100644 .gitignore create mode 100644 .golangci.yml create mode 100644 .goreleaser.yaml create mode 100644 .pre-commit-config.yaml create mode 100644 Dockerfile create mode 100644 Makefile create mode 100644 cmd/cmd.go create mode 100644 cmd/gen/main.go create mode 100644 cmd/web.go create mode 100644 conf/nuxbt.yml create mode 100644 dal/model/user.gen.go create mode 100644 dal/model/user_role.gen.go create mode 100644 dal/query/gen.go create mode 100644 dal/query/user.gen.go create mode 100644 dal/query/user_role.gen.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 internal/middleware/cache/cache.go create mode 100644 internal/middleware/cache/ip_limiter.go create mode 100644 internal/middleware/jwt/auth.go create mode 100644 internal/middleware/jwt/jwt.go create mode 100644 internal/middleware/logger/logger.go create mode 100644 internal/router/api/v1/api.go create mode 100644 internal/router/init.go create mode 100644 internal/service/user/dao.go create mode 100644 internal/service/user/login.go create mode 100644 internal/service/user/profile.go create mode 100644 internal/service/user/register.go create mode 100644 main.go create mode 100644 module/cache/redis.go create mode 100644 module/config/config.go create mode 100644 module/config/generate.go create mode 100644 module/config/type.go create mode 100644 module/db/db.go create mode 100644 module/log/log.go create mode 100644 module/oss/oss.go create mode 100644 module/util/gin.go create mode 100644 module/util/print.go create mode 100644 module/util/random.go create mode 100644 module/util/random_test.go create mode 100644 scripts/README.md create mode 100644 scripts/db.sql diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..b9de05e --- /dev/null +++ b/.dockerignore @@ -0,0 +1,34 @@ +# Include any files or directories that you don't want to be copied to your +# container here (e.g., local build artifacts, temporary files, etc.). +# +# For more help, visit the .dockerignore file reference guide at +# https://docs.docker.com/engine/reference/builder/#dockerignore-file + +**/.DS_Store +**/.classpath +**/.dockerignore +**/.env +**/.git +**/.gitignore +**/.project +**/.settings +**/.toolstarget +**/.vs +**/.vscode +**/*.*proj.user +**/*.dbmdl +**/*.jfm +**/bin +**/charts +**/docker-compose* +**/compose* +**/Dockerfile* +**/node_modules +**/npm-debug.log +**/obj +**/secrets.dev.yaml +**/values.dev.yaml +LICENSE +README.md +docs +apk diff --git a/.github/workflows/CI-test.yml b/.github/workflows/CI-test.yml new file mode 100644 index 0000000..8d68e0b --- /dev/null +++ b/.github/workflows/CI-test.yml @@ -0,0 +1,60 @@ +name: CI-test + +on: + push: + branches: + - main + paths-ignore: + - "**.md" + - LICENSE + pull_request: + paths-ignore: + - "**.md" + - LICENSE + workflow_dispatch: + +jobs: + unit-test: + strategy: + matrix: + os-version: ["ubuntu-latest"] + + runs-on: ${{ matrix.os-version }} + + services: + db: + image: mysql:8.4.1 + env: + MYSQL_ROOT_PASSWORD: 123456 + MYSQL_DATABASE: nuxbt + ports: + - "5432:3306" + redis: + image: redis + options: >- # wait until redis has started + --health-cmd "redis-cli ping" + --health-interval 5s + --health-timeout 3s + --health-retries 10 + ports: + - 6379:6379 + oss: + image: bitnami/minio:latest + env: + MINIO_ACCESS_KEY: ChYm7ufIwNAOzq6PQPCA + MINIO_SECRET_KEY: udicP52IwRbmo2hf6lFvjUS7NP5BhlAdsGNIuDE5 + MINIO_DEFAULT_BUCKETS: nuxbt:public + ports: + - "9000:9000" + + steps: + - uses: actions/checkout@v3 + + - name: Setup Go + uses: actions/setup-go@v4 + with: + go-version: "1.21" + + - name: Test + run: | + make test diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml new file mode 100644 index 0000000..e2be7a1 --- /dev/null +++ b/.github/workflows/docker.yml @@ -0,0 +1,28 @@ +name: docker build and push + +on: + workflow_dispatch: + +jobs: + docker: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + - name: Login to Docker Hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Build and push + uses: docker/build-push-action@v4 + with: + context: . + platforms: linux/amd64,linux/arm64 + push: true + tags: lychee0/nuxbt:latest diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml new file mode 100644 index 0000000..4fc57e7 --- /dev/null +++ b/.github/workflows/golangci-lint.yml @@ -0,0 +1,29 @@ +name: golangci-lint +on: + push: + branches: + - main + pull_request: + branches: + - main + +permissions: + contents: read + pull-requests: read + +jobs: + golangci: + name: lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-go@v4 + with: + go-version: "1.21" + cache: false + - name: golangci-lint + uses: golangci/golangci-lint-action@v3 + with: + version: latest + args: --verbose + only-new-issues: true diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..1839d76 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,37 @@ +name: release + +on: + workflow_dispatch: + +permissions: + contents: write + +jobs: + goreleaser: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + - run: git fetch --force --tags + + - uses: actions/setup-go@v4 + with: + go-version: "1.21" + cache: false + + - name: Import GPG key + id: import_gpg + uses: crazy-max/ghaction-import-gpg@v4 + with: + gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} + passphrase: ${{ secrets.PASSPHRASE }} + + - name: Run GoReleaser + uses: goreleaser/goreleaser-action@v4 + with: + version: latest + args: release --clean + env: + GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} + GPG_FINGERPRINT: ${{ steps.import_gpg.outputs.fingerprint }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8d63a33 --- /dev/null +++ b/.gitignore @@ -0,0 +1,120 @@ +# 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 + +# Go workspace file +go.work + +# JetBrains IDEs +.idea/ + +# CMake +cmake-build-*/ + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# VSCode +.vscode/ + +# Local History for Visual Studio Code +.history/ + +# Built Visual Studio Code Extensions +*.vsix + +# Linux +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +###### Mac +# 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 + +###### Windows +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# Air +tmp diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..c41ff71 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,35 @@ +linters: + enable-all: false + fast: false + enable: + - errcheck + - bodyclose + - dogsled + - dupl + - exportloopref + - exhaustive + - goconst + - gocritic + - gocyclo + - gofmt + - goimports + - goprintffuncname + - gosimple + - govet + - ineffassign + - lll + - misspell + - nakedret + - noctx + - nolintlint + - staticcheck + - typecheck + - unconvert + - unparam + - whitespace + - gofumpt + disable: + - unused + +run: + timeout: 3m diff --git a/.goreleaser.yaml b/.goreleaser.yaml new file mode 100644 index 0000000..dce2bb9 --- /dev/null +++ b/.goreleaser.yaml @@ -0,0 +1,58 @@ +# This is an example .goreleaser.yml file with some sensible defaults. +# Make sure to check the documentation at https://goreleaser.com +before: + hooks: + # You may remove this if you don't use go modules. + - go mod tidy + # you may remove this if you don't need go generate + - go generate ./... +builds: + - env: + - CGO_ENABLED=0 + goos: + - linux + - windows + - darwin + +archives: + - format: tar.gz + # this name template makes the OS and Arch compatible with the results of uname. + name_template: >- + {{ .ProjectName }}_ + {{- title .Os }}_ + {{- if eq .Arch "amd64" }}x86_64 + {{- else if eq .Arch "386" }}i386 + {{- else }}{{ .Arch }}{{ end }} + {{- if .Arm }}v{{ .Arm }}{{ end }} + # use zip for windows archives + # format_overrides: + # - goos: windows + # format: zip +checksum: + name_template: "checksums.txt" + +signs: + - artifacts: checksum + args: + [ + "--batch", + "-u", + "{{ .Env.GPG_FINGERPRINT }}", + "--output", + "${signature}", + "--detach-sign", + "${artifact}", + ] + +snapshot: + name_template: "{{ incpatch .Version }}-next" +changelog: + sort: asc + filters: + exclude: + - "^docs:" + - "^test:" +# The lines beneath this are called `modelines`. See `:help modeline` +# Feel free to remove those if you don't want/use them. +# yaml-language-server: $schema=https://goreleaser.com/static/schema.json +# vim: set ts=2 sw=2 tw=0 fo=cnqoj diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..b49de83 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,23 @@ +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.5.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-json + - id: check-yaml + - id: check-xml + - id: check-toml + + # autofix json, yaml, markdown... + - repo: https://github.com/pre-commit/mirrors-prettier + rev: v3.1.0 + hooks: + - id: prettier + + # autofix toml + - repo: https://github.com/macisamuele/language-formatters-pre-commit-hooks + rev: v2.12.0 + hooks: + - id: pretty-format-toml + args: [--autofix] diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..0c5333d --- /dev/null +++ b/Dockerfile @@ -0,0 +1,23 @@ +FROM golang:1.21.12-alpine3.20 AS builder + +ENV GO111MODULE=on \ + CGO_ENABLED=0 + +WORKDIR /build + +COPY . . + +RUN go mod download + +RUN go build -o nuxbt . + +FROM alpine:3.20 AS runner + +WORKDIR /app + +COPY ./conf/nuxbt.yml /app/conf/ +COPY --from=builder /build/nuxbt /app + +EXPOSE 8080 + +ENTRYPOINT ["/app/nuxbt", "server"] diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..a97c09b --- /dev/null +++ b/Makefile @@ -0,0 +1,27 @@ +GO ?= go + +.DEFAULT_GOAL := default + +TAGS ?= + +.PHONY: tidy +tidy: ## go mod tidy + ${GO} mod tidy + +.PHONY: build +build: ## build binary file + ${GO} build -o nuxbt . + +.PNONY: gen +gen: ## generate CURD code + ${GO} run ./cmd/gen/main.go + +.PHONY: test +test: tidy ## go test + ${GO} test ./... + +.PHONY: lint +lint: ## go lint + golangci-lint run + pre-commit install # pip install pre-commit + pre-commit run --all-files diff --git a/README.md b/README.md index ebaa396..d584ad6 100644 --- a/README.md +++ b/README.md @@ -1 +1 @@ -# NuxBT-Backend \ No newline at end of file +# NuxBT-Backend diff --git a/cmd/cmd.go b/cmd/cmd.go new file mode 100644 index 0000000..a8afbe0 --- /dev/null +++ b/cmd/cmd.go @@ -0,0 +1,18 @@ +package cmd + +import ( + "github.com/urfave/cli/v2" +) + +func NewApp() *cli.App { + app := cli.NewApp() + app.EnableBashCompletion = true + + // 子命令集 + subCmdWithConfig := []*cli.Command{ + CmdWeb, + } + + app.Commands = append(app.Commands, subCmdWithConfig...) + return app +} diff --git a/cmd/gen/main.go b/cmd/gen/main.go new file mode 100644 index 0000000..fbb44ca --- /dev/null +++ b/cmd/gen/main.go @@ -0,0 +1,67 @@ +package main + +import ( + "github.com/TensoRaws/NuxBT-Backend/module/config" + "github.com/TensoRaws/NuxBT-Backend/module/log" + "gorm.io/driver/mysql" + "gorm.io/driver/postgres" + "gorm.io/gen" + "gorm.io/gorm" +) + +func ConnectDB(dbType, dsn string) (db *gorm.DB) { + var err error + + log.Logger.Debugf("DSN: %v", dsn) + + switch dbType { + case "mysql": + db, err = gorm.Open(mysql.Open(dsn), &gorm.Config{}) + case "postgres": + db, err = gorm.Open(postgres.Open(dsn), &gorm.Config{}) + default: + db, err = gorm.Open(mysql.Open(dsn), &gorm.Config{}) + } + + if err != nil { + log.Logger.Fatalf("connect db fail: %v", err) + } + + return db +} + +func Init() { + g := gen.NewGenerator(gen.Config{ + OutPath: "dal/query", + ModelPkgPath: "model", + Mode: gen.WithDefaultQuery | gen.WithoutContext | gen.WithQueryInterface, + + WithUnitTest: false, + + FieldNullable: false, + FieldCoverable: true, + FieldWithIndexTag: true, + FieldWithTypeTag: true, + }) + + dbType, dsn, err := config.GenerateDSN() + if err != nil { + log.Logger.Error(err) + return + } + DB := ConnectDB(dbType, dsn) + + g.UseDB(DB) // reuse your gorm db + + // Generate basic type-safe DAO API for struct `model.User` following conventions + g.ApplyBasic(g.GenerateAllTable()...) + + // Generate the code + g.Execute() +} + +func main() { + config.Init() + log.Init() + Init() +} diff --git a/cmd/web.go b/cmd/web.go new file mode 100644 index 0000000..a628d4c --- /dev/null +++ b/cmd/web.go @@ -0,0 +1,49 @@ +package cmd + +import ( + "fmt" + + "github.com/TensoRaws/NuxBT-Backend/internal/router" + "github.com/urfave/cli/v2" + + "github.com/TensoRaws/NuxBT-Backend/internal/middleware/cache" + "github.com/TensoRaws/NuxBT-Backend/module/config" + "github.com/TensoRaws/NuxBT-Backend/module/db" + "github.com/TensoRaws/NuxBT-Backend/module/log" + "github.com/TensoRaws/NuxBT-Backend/module/oss" +) + +// CmdWeb api 子命令 +var CmdWeb = &cli.Command{ + Name: "server", + Usage: "Start NuxBT api server", + Description: `Star NuxBT api server`, + Action: runWeb, + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "port", + Aliases: []string{"p"}, + Value: "3000", + Usage: "Temporary port number to prevent conflict", + }, + }, +} + +func runWeb(ctx *cli.Context) error { + defer func() { + for k := range cache.Clients { + err := cache.Clients[k].C.Close() + if err != nil { + fmt.Printf("close redis: %v", err) + return + } + } + }() + config.Init() + log.Init() + db.Init() + oss.Init() + cache.Init() + router.Init() + return nil +} diff --git a/conf/nuxbt.yml b/conf/nuxbt.yml new file mode 100644 index 0000000..f0590fc --- /dev/null +++ b/conf/nuxbt.yml @@ -0,0 +1,41 @@ +server: + port: 8080 + mode: prod + allowRegister: true + useInvitationCode: false + requestLimit: 50 # 50 times per minute + +jwt: + timeout: 60 # 1 hour + key: nuxbt + +log: + level: debug + mode: + - console + - file + +db: + type: mysql # mysql, postgres + host: 127.0.0.1 + port: 5432 + username: root + password: 123456 + database: nuxbt + ssl: false + +redis: + host: 127.0.0.1 + port: 6379 + password: + poolSize: 1000 + +oss: + type: minio # minio, cos + endpoint: 127.0.0.1:9000 + accessKey: ChYm7ufIwNAOzq6PQPCA + secretKey: udicP52IwRbmo2hf6lFvjUS7NP5BhlAdsGNIuDE5 + region: local + bucket: nuxbt + ssl: false + hostnameImmutable: true diff --git a/dal/model/user.gen.go b/dal/model/user.gen.go new file mode 100644 index 0000000..d1cb1e3 --- /dev/null +++ b/dal/model/user.gen.go @@ -0,0 +1,35 @@ +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. + +package model + +import ( + "time" + + "gorm.io/gorm" +) + +const TableNameUser = "user" + +// User mapped from table +type User struct { + UserID int32 `gorm:"column:user_id;type:int;primaryKey;autoIncrement:true" json:"user_id"` + Username string `gorm:"column:username;type:varchar(255);not null;uniqueIndex:username,priority:1" json:"username"` + Email string `gorm:"column:email;type:varchar(255);not null;uniqueIndex:email,priority:1" json:"email"` + Password string `gorm:"column:password;type:varchar(255);not null" json:"password"` + Private bool `gorm:"column:private;type:tinyint(1);not null" json:"private"` + Experience int32 `gorm:"column:experience;type:int" json:"experience"` + Inviter int32 `gorm:"column:inviter;type:int;not null" json:"inviter"` + CreatedAt *time.Time `gorm:"column:created_at;type:datetime;not null;default:CURRENT_TIMESTAMP" json:"created_at"` + LastActive time.Time `gorm:"column:last_active;type:datetime" json:"last_active"` + Avatar string `gorm:"column:avatar;type:varchar(255)" json:"avatar"` + Signature string `gorm:"column:signature;type:text" json:"signature"` + Background string `gorm:"column:background;type:varchar(255)" json:"background"` + DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;type:datetime" json:"deleted_at"` +} + +// TableName User's table name +func (*User) TableName() string { + return TableNameUser +} diff --git a/dal/model/user_role.gen.go b/dal/model/user_role.gen.go new file mode 100644 index 0000000..51f8254 --- /dev/null +++ b/dal/model/user_role.gen.go @@ -0,0 +1,27 @@ +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. + +package model + +import ( + "time" + + "gorm.io/gorm" +) + +const TableNameUserRole = "user_role" + +// UserRole mapped from table +type UserRole struct { + RoleID int32 `gorm:"column:role_id;type:int;primaryKey;autoIncrement:true" json:"role_id"` + UserID int32 `gorm:"column:user_id;type:int;not null;uniqueIndex:uk_user_role,priority:1" json:"user_id"` + Role string `gorm:"column:role;type:varchar(255);not null;uniqueIndex:uk_user_role,priority:2" json:"role"` + CreatedAt *time.Time `gorm:"column:created_at;type:datetime;not null;default:CURRENT_TIMESTAMP" json:"created_at"` + DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;type:datetime" json:"deleted_at"` +} + +// TableName UserRole's table name +func (*UserRole) TableName() string { + return TableNameUserRole +} diff --git a/dal/query/gen.go b/dal/query/gen.go new file mode 100644 index 0000000..9d43a7a --- /dev/null +++ b/dal/query/gen.go @@ -0,0 +1,111 @@ +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. + +package query + +import ( + "context" + "database/sql" + + "gorm.io/gorm" + + "gorm.io/gen" + + "gorm.io/plugin/dbresolver" +) + +var ( + Q = new(Query) + User *user + UserRole *userRole +) + +func SetDefault(db *gorm.DB, opts ...gen.DOOption) { + *Q = *Use(db, opts...) + User = &Q.User + UserRole = &Q.UserRole +} + +func Use(db *gorm.DB, opts ...gen.DOOption) *Query { + return &Query{ + db: db, + User: newUser(db, opts...), + UserRole: newUserRole(db, opts...), + } +} + +type Query struct { + db *gorm.DB + + User user + UserRole userRole +} + +func (q *Query) Available() bool { return q.db != nil } + +func (q *Query) clone(db *gorm.DB) *Query { + return &Query{ + db: db, + User: q.User.clone(db), + UserRole: q.UserRole.clone(db), + } +} + +func (q *Query) ReadDB() *Query { + return q.ReplaceDB(q.db.Clauses(dbresolver.Read)) +} + +func (q *Query) WriteDB() *Query { + return q.ReplaceDB(q.db.Clauses(dbresolver.Write)) +} + +func (q *Query) ReplaceDB(db *gorm.DB) *Query { + return &Query{ + db: db, + User: q.User.replaceDB(db), + UserRole: q.UserRole.replaceDB(db), + } +} + +type queryCtx struct { + User IUserDo + UserRole IUserRoleDo +} + +func (q *Query) WithContext(ctx context.Context) *queryCtx { + return &queryCtx{ + User: q.User.WithContext(ctx), + UserRole: q.UserRole.WithContext(ctx), + } +} + +func (q *Query) Transaction(fc func(tx *Query) error, opts ...*sql.TxOptions) error { + return q.db.Transaction(func(tx *gorm.DB) error { return fc(q.clone(tx)) }, opts...) +} + +func (q *Query) Begin(opts ...*sql.TxOptions) *QueryTx { + tx := q.db.Begin(opts...) + return &QueryTx{Query: q.clone(tx), Error: tx.Error} +} + +type QueryTx struct { + *Query + Error error +} + +func (q *QueryTx) Commit() error { + return q.db.Commit().Error +} + +func (q *QueryTx) Rollback() error { + return q.db.Rollback().Error +} + +func (q *QueryTx) SavePoint(name string) error { + return q.db.SavePoint(name).Error +} + +func (q *QueryTx) RollbackTo(name string) error { + return q.db.RollbackTo(name).Error +} diff --git a/dal/query/user.gen.go b/dal/query/user.gen.go new file mode 100644 index 0000000..bbba730 --- /dev/null +++ b/dal/query/user.gen.go @@ -0,0 +1,428 @@ +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. + +package query + +import ( + "context" + + "gorm.io/gorm" + "gorm.io/gorm/clause" + "gorm.io/gorm/schema" + + "gorm.io/gen" + "gorm.io/gen/field" + + "gorm.io/plugin/dbresolver" + + "github.com/TensoRaws/NuxBT-Backend/dal/model" +) + +func newUser(db *gorm.DB, opts ...gen.DOOption) user { + _user := user{} + + _user.userDo.UseDB(db, opts...) + _user.userDo.UseModel(&model.User{}) + + tableName := _user.userDo.TableName() + _user.ALL = field.NewAsterisk(tableName) + _user.UserID = field.NewInt32(tableName, "user_id") + _user.Username = field.NewString(tableName, "username") + _user.Email = field.NewString(tableName, "email") + _user.Password = field.NewString(tableName, "password") + _user.Private = field.NewBool(tableName, "private") + _user.Experience = field.NewInt32(tableName, "experience") + _user.Inviter = field.NewInt32(tableName, "inviter") + _user.CreatedAt = field.NewTime(tableName, "created_at") + _user.LastActive = field.NewTime(tableName, "last_active") + _user.Avatar = field.NewString(tableName, "avatar") + _user.Signature = field.NewString(tableName, "signature") + _user.Background = field.NewString(tableName, "background") + _user.DeletedAt = field.NewField(tableName, "deleted_at") + + _user.fillFieldMap() + + return _user +} + +type user struct { + userDo + + ALL field.Asterisk + UserID field.Int32 + Username field.String + Email field.String + Password field.String + Private field.Bool + Experience field.Int32 + Inviter field.Int32 + CreatedAt field.Time + LastActive field.Time + Avatar field.String + Signature field.String + Background field.String + DeletedAt field.Field + + fieldMap map[string]field.Expr +} + +func (u user) Table(newTableName string) *user { + u.userDo.UseTable(newTableName) + return u.updateTableName(newTableName) +} + +func (u user) As(alias string) *user { + u.userDo.DO = *(u.userDo.As(alias).(*gen.DO)) + return u.updateTableName(alias) +} + +func (u *user) updateTableName(table string) *user { + u.ALL = field.NewAsterisk(table) + u.UserID = field.NewInt32(table, "user_id") + u.Username = field.NewString(table, "username") + u.Email = field.NewString(table, "email") + u.Password = field.NewString(table, "password") + u.Private = field.NewBool(table, "private") + u.Experience = field.NewInt32(table, "experience") + u.Inviter = field.NewInt32(table, "inviter") + u.CreatedAt = field.NewTime(table, "created_at") + u.LastActive = field.NewTime(table, "last_active") + u.Avatar = field.NewString(table, "avatar") + u.Signature = field.NewString(table, "signature") + u.Background = field.NewString(table, "background") + u.DeletedAt = field.NewField(table, "deleted_at") + + u.fillFieldMap() + + return u +} + +func (u *user) GetFieldByName(fieldName string) (field.OrderExpr, bool) { + _f, ok := u.fieldMap[fieldName] + if !ok || _f == nil { + return nil, false + } + _oe, ok := _f.(field.OrderExpr) + return _oe, ok +} + +func (u *user) fillFieldMap() { + u.fieldMap = make(map[string]field.Expr, 13) + u.fieldMap["user_id"] = u.UserID + u.fieldMap["username"] = u.Username + u.fieldMap["email"] = u.Email + u.fieldMap["password"] = u.Password + u.fieldMap["private"] = u.Private + u.fieldMap["experience"] = u.Experience + u.fieldMap["inviter"] = u.Inviter + u.fieldMap["created_at"] = u.CreatedAt + u.fieldMap["last_active"] = u.LastActive + u.fieldMap["avatar"] = u.Avatar + u.fieldMap["signature"] = u.Signature + u.fieldMap["background"] = u.Background + u.fieldMap["deleted_at"] = u.DeletedAt +} + +func (u user) clone(db *gorm.DB) user { + u.userDo.ReplaceConnPool(db.Statement.ConnPool) + return u +} + +func (u user) replaceDB(db *gorm.DB) user { + u.userDo.ReplaceDB(db) + return u +} + +type userDo struct{ gen.DO } + +type IUserDo interface { + gen.SubQuery + Debug() IUserDo + WithContext(ctx context.Context) IUserDo + WithResult(fc func(tx gen.Dao)) gen.ResultInfo + ReplaceDB(db *gorm.DB) + ReadDB() IUserDo + WriteDB() IUserDo + As(alias string) gen.Dao + Session(config *gorm.Session) IUserDo + Columns(cols ...field.Expr) gen.Columns + Clauses(conds ...clause.Expression) IUserDo + Not(conds ...gen.Condition) IUserDo + Or(conds ...gen.Condition) IUserDo + Select(conds ...field.Expr) IUserDo + Where(conds ...gen.Condition) IUserDo + Order(conds ...field.Expr) IUserDo + Distinct(cols ...field.Expr) IUserDo + Omit(cols ...field.Expr) IUserDo + Join(table schema.Tabler, on ...field.Expr) IUserDo + LeftJoin(table schema.Tabler, on ...field.Expr) IUserDo + RightJoin(table schema.Tabler, on ...field.Expr) IUserDo + Group(cols ...field.Expr) IUserDo + Having(conds ...gen.Condition) IUserDo + Limit(limit int) IUserDo + Offset(offset int) IUserDo + Count() (count int64, err error) + Scopes(funcs ...func(gen.Dao) gen.Dao) IUserDo + Unscoped() IUserDo + Create(values ...*model.User) error + CreateInBatches(values []*model.User, batchSize int) error + Save(values ...*model.User) error + First() (*model.User, error) + Take() (*model.User, error) + Last() (*model.User, error) + Find() ([]*model.User, error) + FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*model.User, err error) + FindInBatches(result *[]*model.User, batchSize int, fc func(tx gen.Dao, batch int) error) error + Pluck(column field.Expr, dest interface{}) error + Delete(...*model.User) (info gen.ResultInfo, err error) + Update(column field.Expr, value interface{}) (info gen.ResultInfo, err error) + UpdateSimple(columns ...field.AssignExpr) (info gen.ResultInfo, err error) + Updates(value interface{}) (info gen.ResultInfo, err error) + UpdateColumn(column field.Expr, value interface{}) (info gen.ResultInfo, err error) + UpdateColumnSimple(columns ...field.AssignExpr) (info gen.ResultInfo, err error) + UpdateColumns(value interface{}) (info gen.ResultInfo, err error) + UpdateFrom(q gen.SubQuery) gen.Dao + Attrs(attrs ...field.AssignExpr) IUserDo + Assign(attrs ...field.AssignExpr) IUserDo + Joins(fields ...field.RelationField) IUserDo + Preload(fields ...field.RelationField) IUserDo + FirstOrInit() (*model.User, error) + FirstOrCreate() (*model.User, error) + FindByPage(offset int, limit int) (result []*model.User, count int64, err error) + ScanByPage(result interface{}, offset int, limit int) (count int64, err error) + Scan(result interface{}) (err error) + Returning(value interface{}, columns ...string) IUserDo + UnderlyingDB() *gorm.DB + schema.Tabler +} + +func (u userDo) Debug() IUserDo { + return u.withDO(u.DO.Debug()) +} + +func (u userDo) WithContext(ctx context.Context) IUserDo { + return u.withDO(u.DO.WithContext(ctx)) +} + +func (u userDo) ReadDB() IUserDo { + return u.Clauses(dbresolver.Read) +} + +func (u userDo) WriteDB() IUserDo { + return u.Clauses(dbresolver.Write) +} + +func (u userDo) Session(config *gorm.Session) IUserDo { + return u.withDO(u.DO.Session(config)) +} + +func (u userDo) Clauses(conds ...clause.Expression) IUserDo { + return u.withDO(u.DO.Clauses(conds...)) +} + +func (u userDo) Returning(value interface{}, columns ...string) IUserDo { + return u.withDO(u.DO.Returning(value, columns...)) +} + +func (u userDo) Not(conds ...gen.Condition) IUserDo { + return u.withDO(u.DO.Not(conds...)) +} + +func (u userDo) Or(conds ...gen.Condition) IUserDo { + return u.withDO(u.DO.Or(conds...)) +} + +func (u userDo) Select(conds ...field.Expr) IUserDo { + return u.withDO(u.DO.Select(conds...)) +} + +func (u userDo) Where(conds ...gen.Condition) IUserDo { + return u.withDO(u.DO.Where(conds...)) +} + +func (u userDo) Order(conds ...field.Expr) IUserDo { + return u.withDO(u.DO.Order(conds...)) +} + +func (u userDo) Distinct(cols ...field.Expr) IUserDo { + return u.withDO(u.DO.Distinct(cols...)) +} + +func (u userDo) Omit(cols ...field.Expr) IUserDo { + return u.withDO(u.DO.Omit(cols...)) +} + +func (u userDo) Join(table schema.Tabler, on ...field.Expr) IUserDo { + return u.withDO(u.DO.Join(table, on...)) +} + +func (u userDo) LeftJoin(table schema.Tabler, on ...field.Expr) IUserDo { + return u.withDO(u.DO.LeftJoin(table, on...)) +} + +func (u userDo) RightJoin(table schema.Tabler, on ...field.Expr) IUserDo { + return u.withDO(u.DO.RightJoin(table, on...)) +} + +func (u userDo) Group(cols ...field.Expr) IUserDo { + return u.withDO(u.DO.Group(cols...)) +} + +func (u userDo) Having(conds ...gen.Condition) IUserDo { + return u.withDO(u.DO.Having(conds...)) +} + +func (u userDo) Limit(limit int) IUserDo { + return u.withDO(u.DO.Limit(limit)) +} + +func (u userDo) Offset(offset int) IUserDo { + return u.withDO(u.DO.Offset(offset)) +} + +func (u userDo) Scopes(funcs ...func(gen.Dao) gen.Dao) IUserDo { + return u.withDO(u.DO.Scopes(funcs...)) +} + +func (u userDo) Unscoped() IUserDo { + return u.withDO(u.DO.Unscoped()) +} + +func (u userDo) Create(values ...*model.User) error { + if len(values) == 0 { + return nil + } + return u.DO.Create(values) +} + +func (u userDo) CreateInBatches(values []*model.User, batchSize int) error { + return u.DO.CreateInBatches(values, batchSize) +} + +// Save : !!! underlying implementation is different with GORM +// The method is equivalent to executing the statement: db.Clauses(clause.OnConflict{UpdateAll: true}).Create(values) +func (u userDo) Save(values ...*model.User) error { + if len(values) == 0 { + return nil + } + return u.DO.Save(values) +} + +func (u userDo) First() (*model.User, error) { + if result, err := u.DO.First(); err != nil { + return nil, err + } else { + return result.(*model.User), nil + } +} + +func (u userDo) Take() (*model.User, error) { + if result, err := u.DO.Take(); err != nil { + return nil, err + } else { + return result.(*model.User), nil + } +} + +func (u userDo) Last() (*model.User, error) { + if result, err := u.DO.Last(); err != nil { + return nil, err + } else { + return result.(*model.User), nil + } +} + +func (u userDo) Find() ([]*model.User, error) { + result, err := u.DO.Find() + return result.([]*model.User), err +} + +func (u userDo) FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*model.User, err error) { + buf := make([]*model.User, 0, batchSize) + err = u.DO.FindInBatches(&buf, batchSize, func(tx gen.Dao, batch int) error { + defer func() { results = append(results, buf...) }() + return fc(tx, batch) + }) + return results, err +} + +func (u userDo) FindInBatches(result *[]*model.User, batchSize int, fc func(tx gen.Dao, batch int) error) error { + return u.DO.FindInBatches(result, batchSize, fc) +} + +func (u userDo) Attrs(attrs ...field.AssignExpr) IUserDo { + return u.withDO(u.DO.Attrs(attrs...)) +} + +func (u userDo) Assign(attrs ...field.AssignExpr) IUserDo { + return u.withDO(u.DO.Assign(attrs...)) +} + +func (u userDo) Joins(fields ...field.RelationField) IUserDo { + for _, _f := range fields { + u = *u.withDO(u.DO.Joins(_f)) + } + return &u +} + +func (u userDo) Preload(fields ...field.RelationField) IUserDo { + for _, _f := range fields { + u = *u.withDO(u.DO.Preload(_f)) + } + return &u +} + +func (u userDo) FirstOrInit() (*model.User, error) { + if result, err := u.DO.FirstOrInit(); err != nil { + return nil, err + } else { + return result.(*model.User), nil + } +} + +func (u userDo) FirstOrCreate() (*model.User, error) { + if result, err := u.DO.FirstOrCreate(); err != nil { + return nil, err + } else { + return result.(*model.User), nil + } +} + +func (u userDo) FindByPage(offset int, limit int) (result []*model.User, count int64, err error) { + result, err = u.Offset(offset).Limit(limit).Find() + if err != nil { + return + } + + if size := len(result); 0 < limit && 0 < size && size < limit { + count = int64(size + offset) + return + } + + count, err = u.Offset(-1).Limit(-1).Count() + return +} + +func (u userDo) ScanByPage(result interface{}, offset int, limit int) (count int64, err error) { + count, err = u.Count() + if err != nil { + return + } + + err = u.Offset(offset).Limit(limit).Scan(result) + return +} + +func (u userDo) Scan(result interface{}) (err error) { + return u.DO.Scan(result) +} + +func (u userDo) Delete(models ...*model.User) (result gen.ResultInfo, err error) { + return u.DO.Delete(models) +} + +func (u *userDo) withDO(do gen.Dao) *userDo { + u.DO = *do.(*gen.DO) + return u +} diff --git a/dal/query/user_role.gen.go b/dal/query/user_role.gen.go new file mode 100644 index 0000000..c23f27d --- /dev/null +++ b/dal/query/user_role.gen.go @@ -0,0 +1,396 @@ +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. + +package query + +import ( + "context" + + "gorm.io/gorm" + "gorm.io/gorm/clause" + "gorm.io/gorm/schema" + + "gorm.io/gen" + "gorm.io/gen/field" + + "gorm.io/plugin/dbresolver" + + "github.com/TensoRaws/NuxBT-Backend/dal/model" +) + +func newUserRole(db *gorm.DB, opts ...gen.DOOption) userRole { + _userRole := userRole{} + + _userRole.userRoleDo.UseDB(db, opts...) + _userRole.userRoleDo.UseModel(&model.UserRole{}) + + tableName := _userRole.userRoleDo.TableName() + _userRole.ALL = field.NewAsterisk(tableName) + _userRole.RoleID = field.NewInt32(tableName, "role_id") + _userRole.UserID = field.NewInt32(tableName, "user_id") + _userRole.Role = field.NewString(tableName, "role") + _userRole.CreatedAt = field.NewTime(tableName, "created_at") + _userRole.DeletedAt = field.NewField(tableName, "deleted_at") + + _userRole.fillFieldMap() + + return _userRole +} + +type userRole struct { + userRoleDo + + ALL field.Asterisk + RoleID field.Int32 + UserID field.Int32 + Role field.String + CreatedAt field.Time + DeletedAt field.Field + + fieldMap map[string]field.Expr +} + +func (u userRole) Table(newTableName string) *userRole { + u.userRoleDo.UseTable(newTableName) + return u.updateTableName(newTableName) +} + +func (u userRole) As(alias string) *userRole { + u.userRoleDo.DO = *(u.userRoleDo.As(alias).(*gen.DO)) + return u.updateTableName(alias) +} + +func (u *userRole) updateTableName(table string) *userRole { + u.ALL = field.NewAsterisk(table) + u.RoleID = field.NewInt32(table, "role_id") + u.UserID = field.NewInt32(table, "user_id") + u.Role = field.NewString(table, "role") + u.CreatedAt = field.NewTime(table, "created_at") + u.DeletedAt = field.NewField(table, "deleted_at") + + u.fillFieldMap() + + return u +} + +func (u *userRole) GetFieldByName(fieldName string) (field.OrderExpr, bool) { + _f, ok := u.fieldMap[fieldName] + if !ok || _f == nil { + return nil, false + } + _oe, ok := _f.(field.OrderExpr) + return _oe, ok +} + +func (u *userRole) fillFieldMap() { + u.fieldMap = make(map[string]field.Expr, 5) + u.fieldMap["role_id"] = u.RoleID + u.fieldMap["user_id"] = u.UserID + u.fieldMap["role"] = u.Role + u.fieldMap["created_at"] = u.CreatedAt + u.fieldMap["deleted_at"] = u.DeletedAt +} + +func (u userRole) clone(db *gorm.DB) userRole { + u.userRoleDo.ReplaceConnPool(db.Statement.ConnPool) + return u +} + +func (u userRole) replaceDB(db *gorm.DB) userRole { + u.userRoleDo.ReplaceDB(db) + return u +} + +type userRoleDo struct{ gen.DO } + +type IUserRoleDo interface { + gen.SubQuery + Debug() IUserRoleDo + WithContext(ctx context.Context) IUserRoleDo + WithResult(fc func(tx gen.Dao)) gen.ResultInfo + ReplaceDB(db *gorm.DB) + ReadDB() IUserRoleDo + WriteDB() IUserRoleDo + As(alias string) gen.Dao + Session(config *gorm.Session) IUserRoleDo + Columns(cols ...field.Expr) gen.Columns + Clauses(conds ...clause.Expression) IUserRoleDo + Not(conds ...gen.Condition) IUserRoleDo + Or(conds ...gen.Condition) IUserRoleDo + Select(conds ...field.Expr) IUserRoleDo + Where(conds ...gen.Condition) IUserRoleDo + Order(conds ...field.Expr) IUserRoleDo + Distinct(cols ...field.Expr) IUserRoleDo + Omit(cols ...field.Expr) IUserRoleDo + Join(table schema.Tabler, on ...field.Expr) IUserRoleDo + LeftJoin(table schema.Tabler, on ...field.Expr) IUserRoleDo + RightJoin(table schema.Tabler, on ...field.Expr) IUserRoleDo + Group(cols ...field.Expr) IUserRoleDo + Having(conds ...gen.Condition) IUserRoleDo + Limit(limit int) IUserRoleDo + Offset(offset int) IUserRoleDo + Count() (count int64, err error) + Scopes(funcs ...func(gen.Dao) gen.Dao) IUserRoleDo + Unscoped() IUserRoleDo + Create(values ...*model.UserRole) error + CreateInBatches(values []*model.UserRole, batchSize int) error + Save(values ...*model.UserRole) error + First() (*model.UserRole, error) + Take() (*model.UserRole, error) + Last() (*model.UserRole, error) + Find() ([]*model.UserRole, error) + FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*model.UserRole, err error) + FindInBatches(result *[]*model.UserRole, batchSize int, fc func(tx gen.Dao, batch int) error) error + Pluck(column field.Expr, dest interface{}) error + Delete(...*model.UserRole) (info gen.ResultInfo, err error) + Update(column field.Expr, value interface{}) (info gen.ResultInfo, err error) + UpdateSimple(columns ...field.AssignExpr) (info gen.ResultInfo, err error) + Updates(value interface{}) (info gen.ResultInfo, err error) + UpdateColumn(column field.Expr, value interface{}) (info gen.ResultInfo, err error) + UpdateColumnSimple(columns ...field.AssignExpr) (info gen.ResultInfo, err error) + UpdateColumns(value interface{}) (info gen.ResultInfo, err error) + UpdateFrom(q gen.SubQuery) gen.Dao + Attrs(attrs ...field.AssignExpr) IUserRoleDo + Assign(attrs ...field.AssignExpr) IUserRoleDo + Joins(fields ...field.RelationField) IUserRoleDo + Preload(fields ...field.RelationField) IUserRoleDo + FirstOrInit() (*model.UserRole, error) + FirstOrCreate() (*model.UserRole, error) + FindByPage(offset int, limit int) (result []*model.UserRole, count int64, err error) + ScanByPage(result interface{}, offset int, limit int) (count int64, err error) + Scan(result interface{}) (err error) + Returning(value interface{}, columns ...string) IUserRoleDo + UnderlyingDB() *gorm.DB + schema.Tabler +} + +func (u userRoleDo) Debug() IUserRoleDo { + return u.withDO(u.DO.Debug()) +} + +func (u userRoleDo) WithContext(ctx context.Context) IUserRoleDo { + return u.withDO(u.DO.WithContext(ctx)) +} + +func (u userRoleDo) ReadDB() IUserRoleDo { + return u.Clauses(dbresolver.Read) +} + +func (u userRoleDo) WriteDB() IUserRoleDo { + return u.Clauses(dbresolver.Write) +} + +func (u userRoleDo) Session(config *gorm.Session) IUserRoleDo { + return u.withDO(u.DO.Session(config)) +} + +func (u userRoleDo) Clauses(conds ...clause.Expression) IUserRoleDo { + return u.withDO(u.DO.Clauses(conds...)) +} + +func (u userRoleDo) Returning(value interface{}, columns ...string) IUserRoleDo { + return u.withDO(u.DO.Returning(value, columns...)) +} + +func (u userRoleDo) Not(conds ...gen.Condition) IUserRoleDo { + return u.withDO(u.DO.Not(conds...)) +} + +func (u userRoleDo) Or(conds ...gen.Condition) IUserRoleDo { + return u.withDO(u.DO.Or(conds...)) +} + +func (u userRoleDo) Select(conds ...field.Expr) IUserRoleDo { + return u.withDO(u.DO.Select(conds...)) +} + +func (u userRoleDo) Where(conds ...gen.Condition) IUserRoleDo { + return u.withDO(u.DO.Where(conds...)) +} + +func (u userRoleDo) Order(conds ...field.Expr) IUserRoleDo { + return u.withDO(u.DO.Order(conds...)) +} + +func (u userRoleDo) Distinct(cols ...field.Expr) IUserRoleDo { + return u.withDO(u.DO.Distinct(cols...)) +} + +func (u userRoleDo) Omit(cols ...field.Expr) IUserRoleDo { + return u.withDO(u.DO.Omit(cols...)) +} + +func (u userRoleDo) Join(table schema.Tabler, on ...field.Expr) IUserRoleDo { + return u.withDO(u.DO.Join(table, on...)) +} + +func (u userRoleDo) LeftJoin(table schema.Tabler, on ...field.Expr) IUserRoleDo { + return u.withDO(u.DO.LeftJoin(table, on...)) +} + +func (u userRoleDo) RightJoin(table schema.Tabler, on ...field.Expr) IUserRoleDo { + return u.withDO(u.DO.RightJoin(table, on...)) +} + +func (u userRoleDo) Group(cols ...field.Expr) IUserRoleDo { + return u.withDO(u.DO.Group(cols...)) +} + +func (u userRoleDo) Having(conds ...gen.Condition) IUserRoleDo { + return u.withDO(u.DO.Having(conds...)) +} + +func (u userRoleDo) Limit(limit int) IUserRoleDo { + return u.withDO(u.DO.Limit(limit)) +} + +func (u userRoleDo) Offset(offset int) IUserRoleDo { + return u.withDO(u.DO.Offset(offset)) +} + +func (u userRoleDo) Scopes(funcs ...func(gen.Dao) gen.Dao) IUserRoleDo { + return u.withDO(u.DO.Scopes(funcs...)) +} + +func (u userRoleDo) Unscoped() IUserRoleDo { + return u.withDO(u.DO.Unscoped()) +} + +func (u userRoleDo) Create(values ...*model.UserRole) error { + if len(values) == 0 { + return nil + } + return u.DO.Create(values) +} + +func (u userRoleDo) CreateInBatches(values []*model.UserRole, batchSize int) error { + return u.DO.CreateInBatches(values, batchSize) +} + +// Save : !!! underlying implementation is different with GORM +// The method is equivalent to executing the statement: db.Clauses(clause.OnConflict{UpdateAll: true}).Create(values) +func (u userRoleDo) Save(values ...*model.UserRole) error { + if len(values) == 0 { + return nil + } + return u.DO.Save(values) +} + +func (u userRoleDo) First() (*model.UserRole, error) { + if result, err := u.DO.First(); err != nil { + return nil, err + } else { + return result.(*model.UserRole), nil + } +} + +func (u userRoleDo) Take() (*model.UserRole, error) { + if result, err := u.DO.Take(); err != nil { + return nil, err + } else { + return result.(*model.UserRole), nil + } +} + +func (u userRoleDo) Last() (*model.UserRole, error) { + if result, err := u.DO.Last(); err != nil { + return nil, err + } else { + return result.(*model.UserRole), nil + } +} + +func (u userRoleDo) Find() ([]*model.UserRole, error) { + result, err := u.DO.Find() + return result.([]*model.UserRole), err +} + +func (u userRoleDo) FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*model.UserRole, err error) { + buf := make([]*model.UserRole, 0, batchSize) + err = u.DO.FindInBatches(&buf, batchSize, func(tx gen.Dao, batch int) error { + defer func() { results = append(results, buf...) }() + return fc(tx, batch) + }) + return results, err +} + +func (u userRoleDo) FindInBatches(result *[]*model.UserRole, batchSize int, fc func(tx gen.Dao, batch int) error) error { + return u.DO.FindInBatches(result, batchSize, fc) +} + +func (u userRoleDo) Attrs(attrs ...field.AssignExpr) IUserRoleDo { + return u.withDO(u.DO.Attrs(attrs...)) +} + +func (u userRoleDo) Assign(attrs ...field.AssignExpr) IUserRoleDo { + return u.withDO(u.DO.Assign(attrs...)) +} + +func (u userRoleDo) Joins(fields ...field.RelationField) IUserRoleDo { + for _, _f := range fields { + u = *u.withDO(u.DO.Joins(_f)) + } + return &u +} + +func (u userRoleDo) Preload(fields ...field.RelationField) IUserRoleDo { + for _, _f := range fields { + u = *u.withDO(u.DO.Preload(_f)) + } + return &u +} + +func (u userRoleDo) FirstOrInit() (*model.UserRole, error) { + if result, err := u.DO.FirstOrInit(); err != nil { + return nil, err + } else { + return result.(*model.UserRole), nil + } +} + +func (u userRoleDo) FirstOrCreate() (*model.UserRole, error) { + if result, err := u.DO.FirstOrCreate(); err != nil { + return nil, err + } else { + return result.(*model.UserRole), nil + } +} + +func (u userRoleDo) FindByPage(offset int, limit int) (result []*model.UserRole, count int64, err error) { + result, err = u.Offset(offset).Limit(limit).Find() + if err != nil { + return + } + + if size := len(result); 0 < limit && 0 < size && size < limit { + count = int64(size + offset) + return + } + + count, err = u.Offset(-1).Limit(-1).Count() + return +} + +func (u userRoleDo) ScanByPage(result interface{}, offset int, limit int) (count int64, err error) { + count, err = u.Count() + if err != nil { + return + } + + err = u.Offset(offset).Limit(limit).Scan(result) + return +} + +func (u userRoleDo) Scan(result interface{}) (err error) { + return u.DO.Scan(result) +} + +func (u userRoleDo) Delete(models ...*model.UserRole) (result gen.ResultInfo, err error) { + return u.DO.Delete(models) +} + +func (u *userRoleDo) withDO(do gen.Dao) *userRoleDo { + u.DO = *do.(*gen.DO) + return u +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..e21eafb --- /dev/null +++ b/go.mod @@ -0,0 +1,110 @@ +module github.com/TensoRaws/NuxBT-Backend + +go 1.21 + +require ( + github.com/bytedance/sonic v1.11.9 + github.com/charmbracelet/log v0.4.0 + github.com/eleven26/goss/v4 v4.0.1 + github.com/fsnotify/fsnotify v1.7.0 + github.com/gin-gonic/gin v1.10.0 + github.com/golang-jwt/jwt/v5 v5.2.1 + github.com/redis/go-redis/v9 v9.5.3 + github.com/spf13/viper v1.19.0 + github.com/stretchr/testify v1.9.0 + github.com/urfave/cli/v2 v2.27.2 + golang.org/x/crypto v0.25.0 + gorm.io/driver/mysql v1.5.7 + gorm.io/driver/postgres v1.5.9 + gorm.io/gen v0.3.26 + gorm.io/gorm v1.25.10 + gorm.io/plugin/dbresolver v1.5.2 +) + +require ( + filippo.io/edwards25519 v1.1.0 // indirect + github.com/aws/aws-sdk-go-v2 v1.20.0 // indirect + github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.11 // indirect + github.com/aws/aws-sdk-go-v2/config v1.18.32 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.13.31 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.7 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.37 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.31 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.3.38 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.1.0 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.12 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.32 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.31 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.15.0 // indirect + github.com/aws/aws-sdk-go-v2/service/s3 v1.38.1 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.13.1 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.15.1 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.21.1 // indirect + github.com/aws/smithy-go v1.14.0 // indirect + github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect + github.com/bytedance/sonic/loader v0.1.1 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/charmbracelet/lipgloss v0.11.0 // indirect + github.com/charmbracelet/x/ansi v0.1.2 // indirect + github.com/cloudwego/base64x v0.1.4 // indirect + github.com/cloudwego/iasm v0.2.0 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + github.com/gabriel-vasile/mimetype v1.4.4 // indirect + github.com/gin-contrib/sse v0.1.0 // indirect + github.com/go-logfmt/logfmt v0.6.0 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.22.0 // indirect + github.com/go-sql-driver/mysql v1.8.1 // indirect + github.com/goccy/go-json v0.10.3 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 // indirect + github.com/jackc/pgx/v5 v5.5.5 // indirect + github.com/jackc/puddle/v2 v2.2.1 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.2.8 // indirect + github.com/leodido/go-urn v1.4.0 // indirect + github.com/lucasb-eyer/go-colorful v1.2.0 // indirect + github.com/magiconair/properties v1.8.7 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-runewidth v0.0.15 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/muesli/termenv v0.15.2 // indirect + github.com/pelletier/go-toml/v2 v2.2.2 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/sagikazarmark/locafero v0.6.0 // indirect + github.com/sagikazarmark/slog-shim v0.1.0 // indirect + github.com/sourcegraph/conc v0.3.0 // indirect + github.com/spf13/afero v1.11.0 // indirect + github.com/spf13/cast v1.6.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/subosito/gotenv v1.6.0 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.2.12 // indirect + github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect + go.uber.org/multierr v1.11.0 // indirect + golang.org/x/arch v0.8.0 // indirect + golang.org/x/exp v0.0.0-20240707233637-46b078467d37 // indirect + golang.org/x/mod v0.19.0 // indirect + golang.org/x/net v0.27.0 // indirect + golang.org/x/sync v0.7.0 // indirect + golang.org/x/sys v0.22.0 // indirect + golang.org/x/text v0.16.0 // indirect + golang.org/x/tools v0.23.0 // indirect + google.golang.org/protobuf v1.34.2 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + gorm.io/datatypes v1.2.1 // indirect + gorm.io/driver/sqlite v1.5.6 // indirect + gorm.io/hints v1.1.2 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..e237057 --- /dev/null +++ b/go.sum @@ -0,0 +1,267 @@ +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/aws/aws-sdk-go-v2 v1.20.0 h1:INUDpYLt4oiPOJl0XwZDK2OVAVf0Rzo+MGVTv9f+gy8= +github.com/aws/aws-sdk-go-v2 v1.20.0/go.mod h1:uWOr0m0jDsiWw8nnXiqZ+YG6LdvAlGYDLLf2NmHZoy4= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.11 h1:/MS8AzqYNAhhRNalOmxUvYs8VEbNGifTnzhPFdcRQkQ= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.11/go.mod h1:va22++AdXht4ccO3kH2SHkHHYvZ2G9Utz+CXKmm2CaU= +github.com/aws/aws-sdk-go-v2/config v1.18.32 h1:tqEOvkbTxwEV7hToRcJ1xZRjcATqwDVsWbAscgRKyNI= +github.com/aws/aws-sdk-go-v2/config v1.18.32/go.mod h1:U3ZF0fQRRA4gnbn9GGvOWLoT2EzzZfAWeKwnVrm1rDc= +github.com/aws/aws-sdk-go-v2/credentials v1.13.31 h1:vJyON3lG7R8VOErpJJBclBADiWTwzcwdkQpTKx8D2sk= +github.com/aws/aws-sdk-go-v2/credentials v1.13.31/go.mod h1:T4sESjBtY2lNxLgkIASmeP57b5j7hTQqCbqG0tWnxC4= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.7 h1:X3H6+SU21x+76LRglk21dFRgMTJMa5QcpW+SqUf5BBg= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.7/go.mod h1:3we0V09SwcJBzNlnyovrR2wWJhWmVdqAsmVs4uronv8= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.37 h1:zr/gxAZkMcvP71ZhQOcvdm8ReLjFgIXnIn0fw5AM7mo= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.37/go.mod h1:Pdn4j43v49Kk6+82spO3Tu5gSeQXRsxo56ePPQAvFiA= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.31 h1:0HCMIkAkVY9KMgueD8tf4bRTUanzEYvhw7KkPXIMpO0= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.31/go.mod h1:fTJDMe8LOFYtqiFFFeHA+SVMAwqLhoq0kcInYoLa9Js= +github.com/aws/aws-sdk-go-v2/internal/ini v1.3.38 h1:+i1DOFrW3YZ3apE45tCal9+aDKK6kNEbW6Ib7e1nFxE= +github.com/aws/aws-sdk-go-v2/internal/ini v1.3.38/go.mod h1:1/jLp0OgOaWIetycOmycW+vYTYgTZFPttJQRgsI1PoU= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.1.0 h1:U5yySdwt2HPo/pnQec04DImLzWORbeWML1fJiLkKruI= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.1.0/go.mod h1:EhC/83j8/hL/UB1WmExo3gkElaja/KlmZM/gl1rTfjM= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.12 h1:uAiiHnWihGP2rVp64fHwzLDrswGjEjsPszwRYMiYQPU= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.12/go.mod h1:fUTHpOXqRQpXvEpDPSa3zxCc2fnpW6YnBoba+eQr+Bg= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.32 h1:kvN1jPHr9UffqqG3bSgZ8tx4+1zKVHz/Ktw/BwW6hX8= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.32/go.mod h1:QmMEM7es84EUkbYWcpnkx8i5EW2uERPfrTFeOch128Y= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.31 h1:auGDJ0aLZahF5SPvkJ6WcUuX7iQ7kyl2MamV7Tm8QBk= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.31/go.mod h1:3+lloe3sZuBQw1aBc5MyndvodzQlyqCZ7x1QPDHaWP4= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.15.0 h1:Wgjft9X4W5pMeuqgPCHIQtbZ87wsgom7S5F8obreg+c= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.15.0/go.mod h1:FWNzS4+zcWAP05IF7TDYTY1ysZAzIvogxWaDT9p8fsA= +github.com/aws/aws-sdk-go-v2/service/s3 v1.38.1 h1:mTgFVlfQT8gikc5+/HwD8UL9jnUro5MGv8n/VEYF12I= +github.com/aws/aws-sdk-go-v2/service/s3 v1.38.1/go.mod h1:6SOWLiobcZZshbmECRTADIRYliPL0etqFSigauQEeT0= +github.com/aws/aws-sdk-go-v2/service/sso v1.13.1 h1:DSNpSbfEgFXRV+IfEcKE5kTbqxm+MeF5WgyeRlsLnHY= +github.com/aws/aws-sdk-go-v2/service/sso v1.13.1/go.mod h1:TC9BubuFMVScIU+TLKamO6VZiYTkYoEHqlSQwAe2omw= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.15.1 h1:hd0SKLMdOL/Sl6Z0np1PX9LeH2gqNtBe0MhTedA8MGI= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.15.1/go.mod h1:XO/VcyoQ8nKyKfFW/3DMsRQXsfh/052tHTWmg3xBXRg= +github.com/aws/aws-sdk-go-v2/service/sts v1.21.1 h1:pAOJj+80tC8sPVgSDHzMYD6KLWsaLQ1kZw31PTeORbs= +github.com/aws/aws-sdk-go-v2/service/sts v1.21.1/go.mod h1:G8SbvL0rFk4WOJroU8tKBczhsbhj2p/YY7qeJezJ3CI= +github.com/aws/smithy-go v1.14.0 h1:+X90sB94fizKjDmwb4vyl2cTTPXTE5E2G/1mjByb0io= +github.com/aws/smithy-go v1.14.0/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA= +github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= +github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= +github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= +github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= +github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= +github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= +github.com/bytedance/sonic v1.11.9 h1:LFHENlIY/SLzDWverzdOvgMztTxcfcF+cqNsz9pK5zg= +github.com/bytedance/sonic v1.11.9/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= +github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM= +github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/charmbracelet/lipgloss v0.11.0 h1:UoAcbQ6Qml8hDwSWs0Y1cB5TEQuZkDPH/ZqwWWYTG4g= +github.com/charmbracelet/lipgloss v0.11.0/go.mod h1:1UdRTH9gYgpcdNN5oBtjbu/IzNKtzVtb7sqN1t9LNn8= +github.com/charmbracelet/log v0.4.0 h1:G9bQAcx8rWA2T3pWvx7YtPTPwgqpk7D68BX21IRW8ZM= +github.com/charmbracelet/log v0.4.0/go.mod h1:63bXt/djrizTec0l11H20t8FDSvA4CRZJ1KH22MdptM= +github.com/charmbracelet/x/ansi v0.1.2 h1:6+LR39uG8DE6zAmbu023YlqjJHkYXDF1z36ZwzO4xZY= +github.com/charmbracelet/x/ansi v0.1.2/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw= +github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= +github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= +github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= +github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= +github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4= +github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/eleven26/goss/v4 v4.0.1 h1:kNbmuUaN+zwhjoroKf2tHUdsUDdqIpH/GVcvgJIG7g0= +github.com/eleven26/goss/v4 v4.0.1/go.mod h1:Te8DpKmsPkt2m0MC7hEOw+/wTbfNQFlYt6Z8WB+1DHQ= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/gabriel-vasile/mimetype v1.4.4 h1:QjV6pZ7/XZ7ryI2KuyeEDE8wnh7fHP9YnQy+R0LnH8I= +github.com/gabriel-vasile/mimetype v1.4.4/go.mod h1:JwLei5XPtWdGiMFB5Pjle1oEeoSeEuJfJE+TtfvdB/s= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= +github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= +github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= +github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.22.0 h1:k6HsTZ0sTnROkhS//R0O+55JgM8C4Bx7ia+JlgcnOao= +github.com/go-playground/validator/v10 v10.22.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= +github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= +github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= +github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= +github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= +github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= +github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA= +github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= +github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A= +github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 h1:L0QtFUgDarD7Fpv9jeVMgy/+Ec0mtnmYuImjTz6dtDA= +github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw= +github.com/jackc/pgx/v5 v5.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A= +github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= +github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= +github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM= +github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= +github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= +github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= +github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= +github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= +github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/microsoft/go-mssqldb v0.17.0 h1:Fto83dMZPnYv1Zwx5vHHxpNraeEaUlQ/hhHLgZiaenE= +github.com/microsoft/go-mssqldb v0.17.0/go.mod h1:OkoNGhGEs8EZqchVTtochlXruEhEOaO4S0d2sB5aeGQ= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= +github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= +github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= +github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/redis/go-redis/v9 v9.5.3 h1:fOAp1/uJG+ZtcITgZOfYFmTKPE7n4Vclj1wZFgRciUU= +github.com/redis/go-redis/v9 v9.5.3/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sagikazarmark/locafero v0.6.0 h1:ON7AQg37yzcRPU69mt7gwhFEBwxI6P9T4Qu3N51bwOk= +github.com/sagikazarmark/locafero v0.6.0/go.mod h1:77OmuIc6VTraTXKXIs/uvUxKGUXjE1GbemJYHqdNjX0= +github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= +github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= +github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= +github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= +github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= +github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= +github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= +github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= +github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= +github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +github.com/urfave/cli/v2 v2.27.2 h1:6e0H+AkS+zDckwPCUrZkKX38mRaau4nL2uipkJpbkcI= +github.com/urfave/cli/v2 v2.27.2/go.mod h1:g0+79LmHHATl7DAcHO99smiR/T7uGLw84w8Y42x+4eM= +github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= +github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc= +golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= +golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= +golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/exp v0.0.0-20240707233637-46b078467d37 h1:uLDX+AfeFCct3a2C7uIWBKMJIR3CJMhcgfrUAqjRK6w= +golang.org/x/exp v0.0.0-20240707233637-46b078467d37/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= +golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= +golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= +golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= +golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg= +golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/datatypes v1.2.1 h1:r+g0bk4LPCW2v4+Ls7aeNgGme7JYdNDQ2VtvlNUfBh0= +gorm.io/datatypes v1.2.1/go.mod h1:hYK6OTb/1x+m96PgoZZq10UXJ6RvEBb9kRDQ2yyhzGs= +gorm.io/driver/mysql v1.5.6/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM= +gorm.io/driver/mysql v1.5.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo= +gorm.io/driver/mysql v1.5.7/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM= +gorm.io/driver/postgres v1.5.9 h1:DkegyItji119OlcaLjqN11kHoUgZ/j13E0jkJZgD6A8= +gorm.io/driver/postgres v1.5.9/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI= +gorm.io/driver/sqlite v1.5.0/go.mod h1:kDMDfntV9u/vuMmz8APHtHF0b4nyBB7sfCieC6G8k8I= +gorm.io/driver/sqlite v1.5.6 h1:fO/X46qn5NUEEOZtnjJRWRzZMe8nqJiQ9E+0hi+hKQE= +gorm.io/driver/sqlite v1.5.6/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4= +gorm.io/driver/sqlserver v1.4.1 h1:t4r4r6Jam5E6ejqP7N82qAJIJAht27EGT41HyPfXRw0= +gorm.io/driver/sqlserver v1.4.1/go.mod h1:DJ4P+MeZbc5rvY58PnmN1Lnyvb5gw5NPzGshHDnJLig= +gorm.io/gen v0.3.26 h1:sFf1j7vNStimPRRAtH4zz5NiHM+1dr6eA9aaRdplyhY= +gorm.io/gen v0.3.26/go.mod h1:a5lq5y3w4g5LMxBcw0wnO6tYUCdNutWODq5LrIt75LE= +gorm.io/gorm v1.24.7-0.20230306060331-85eaf9eeda11/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= +gorm.io/gorm v1.25.0/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= +gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= +gorm.io/gorm v1.25.10 h1:dQpO+33KalOA+aFYGlK+EfxcI5MbO7EP2yYygwh9h+s= +gorm.io/gorm v1.25.10/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= +gorm.io/hints v1.1.2 h1:b5j0kwk5p4+3BtDtYqqfY+ATSxjj+6ptPgVveuynn9o= +gorm.io/hints v1.1.2/go.mod h1:/ARdpUHAtyEMCh5NNi3tI7FsGh+Cj/MIUlvNxCNCFWg= +gorm.io/plugin/dbresolver v1.5.2 h1:Iut7lW4TXNoVs++I+ra3zxjSxTRj4ocIeFEVp4lLhII= +gorm.io/plugin/dbresolver v1.5.2/go.mod h1:jPh59GOQbO7v7v28ZKZPd45tr+u3vyT+8tHdfdfOWcU= +nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/internal/middleware/cache/cache.go b/internal/middleware/cache/cache.go new file mode 100644 index 0000000..0a964dc --- /dev/null +++ b/internal/middleware/cache/cache.go @@ -0,0 +1,34 @@ +package cache + +import ( + "bytes" + "sync" + + "github.com/TensoRaws/NuxBT-Backend/module/cache" + "github.com/gin-gonic/gin" +) + +var once sync.Once + +var Clients = map[cache.RDB]*cache.Client{ + cache.IPLimit: {}, + cache.User: {}, +} + +type responseWriter struct { + gin.ResponseWriter + b *bytes.Buffer +} + +func (w responseWriter) Write(b []byte) (int, error) { + // 向一个bytes.buffer中写一份数据来为获取body使用 + w.b.Write(b) + // 完成gin.Context.Writer.Write()原有功能 + return w.ResponseWriter.Write(b) +} + +func Init() { + once.Do(func() { + cache.NewRedisClients(Clients) + }) +} diff --git a/internal/middleware/cache/ip_limiter.go b/internal/middleware/cache/ip_limiter.go new file mode 100644 index 0000000..bef2c7b --- /dev/null +++ b/internal/middleware/cache/ip_limiter.go @@ -0,0 +1,45 @@ +package cache + +import ( + "fmt" + "time" + + "github.com/TensoRaws/NuxBT-Backend/module/cache" + "github.com/TensoRaws/NuxBT-Backend/module/log" + "github.com/TensoRaws/NuxBT-Backend/module/util" + "github.com/gin-gonic/gin" + "github.com/redis/go-redis/v9" +) + +func NewRateLimiter(redisClient *cache.Client, key string, limit int, slidingWindow time.Duration) gin.HandlerFunc { + _, err := redisClient.Ping().Result() + if err != nil { + panic(fmt.Sprint("error init redis", err.Error())) + } + + return func(c *gin.Context) { + now := time.Now().UnixNano() + log.Logger.Infof("-------------------> path: %v", c.Request.URL.Path) + userCntKey := fmt.Sprint(c.ClientIP(), ":", key, ":", c.Request.URL.Path) + + _, err := redisClient.ZRemRangeByScore(userCntKey, + "0", + fmt.Sprint(now-(slidingWindow.Nanoseconds()))).Result() + if err != nil { + log.Logger.Error(err) + return + } + + reqs, _ := redisClient.ZRange(userCntKey, 0, -1).Result() + + if len(reqs) >= limit { + util.AbortWithMsg(c, "Too many request...") + log.Logger.Warnf("------------------> too many request, key: %v", userCntKey) + return + } + + c.Next() + redisClient.ZAddNX(userCntKey, redis.Z{Score: float64(now), Member: float64(now)}) + redisClient.Expire(userCntKey, slidingWindow) + } +} diff --git a/internal/middleware/jwt/auth.go b/internal/middleware/jwt/auth.go new file mode 100644 index 0000000..d3a1ca5 --- /dev/null +++ b/internal/middleware/jwt/auth.go @@ -0,0 +1,39 @@ +package jwt + +import ( + "github.com/TensoRaws/NuxBT-Backend/module/log" + "github.com/TensoRaws/NuxBT-Backend/module/util" + "github.com/gin-gonic/gin" +) + +// RequireAuth 鉴权中间件 +// 如果用户携带的 token 验证通过,将 user_id 存入上下文中然后执行下一个 Handler +func RequireAuth() gin.HandlerFunc { + return func(c *gin.Context) { + // 从输入的 url 中查询 token 值 + token := c.Query("token") + if len(token) == 0 { + // 从输入的表单中查询 token 值 + token = c.PostForm("token") + } + + if len(token) == 0 { + util.AbortWithMsg(c, "JSON WEB TOKEN IS NULL") + return + } + + log.Logger.Info("Get token successfully") + // auth = [[header][cliams][signature]] + // 解析 token + claims, err := ParseToken(token) + if err != nil { + util.AbortWithMsg(c, "TOKEN IS INVALID, Please Log In") + return + } + + userID := claims.ID + c.Set("user_id", userID) + // 放行 + c.Next() + } +} diff --git a/internal/middleware/jwt/jwt.go b/internal/middleware/jwt/jwt.go new file mode 100644 index 0000000..4b228dc --- /dev/null +++ b/internal/middleware/jwt/jwt.go @@ -0,0 +1,84 @@ +package jwt + +import ( + "errors" + "fmt" + "strconv" + "time" + + "github.com/TensoRaws/NuxBT-Backend/dal/model" + "github.com/TensoRaws/NuxBT-Backend/module/config" + "github.com/TensoRaws/NuxBT-Backend/module/log" + "github.com/golang-jwt/jwt/v5" +) + +var ( + TokenExpiredDuration time.Duration + mySigningKey []byte +) + +// GetJWTTokenExpiredDuration 根据配置文件获取 jwt 的过期时间 +func GetJWTTokenExpiredDuration() time.Duration { + if TokenExpiredDuration != 0 { + return TokenExpiredDuration + } + TokenExpiredDuration = time.Minute * time.Duration(config.JwtConfig.Timeout) + return TokenExpiredDuration +} + +// GetJWTSigningKey 根据配置文件获取 jwt 的签名密钥 +func GetJWTSigningKey() []byte { + if len(mySigningKey) != 0 { + return mySigningKey + } + mySigningKey = []byte(config.JwtConfig.Key) + return mySigningKey +} + +// GenerateToken 生成 jwt(json web token) +func GenerateToken(u *model.User) string { + userID := strconv.FormatInt(int64(u.UserID), 10) + claims := jwt.RegisteredClaims{ + ExpiresAt: jwt.NewNumericDate(time.Now().Add(GetJWTTokenExpiredDuration())), + NotBefore: jwt.NewNumericDate(time.Now()), + Issuer: "TensoRaws", + IssuedAt: jwt.NewNumericDate(time.Now()), + Subject: "token", + ID: userID, // jwt 中保存合法用户的 ID + } + + // 使用指定的签名算法创建用于签名的字符串对象,使用 json 序列化和 base64Url 编码生成 jwt 的 1、2 部分 + token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + // 以上面生成 token 作为签名值,使用 secret 进行签名获取签名值 + // 将 token 和生成的签名值使用 '.' 拼接后就生成了 jwt + // 这里一定要使用字节切片 + tokenStr, err := token.SignedString(GetJWTSigningKey()) + if err != nil { + log.Logger.Info(err) + return "" + } + return tokenStr +} + +// ParseToken 负责解析客户端 Header 中包含的 jwt,解析成功返回用户的 Claims(包含了用户的信息) +func ParseToken(tokenString string) (*jwt.RegisteredClaims, error) { + // 使用匿名函数先去查询服务器签名时使用的私钥,然后调用签名的验证算法进行验证 + // 验证通过后,将 tokenString 进行反编码并反序列化到 jwt.Token 结构体相应字段 + token, err := jwt.ParseWithClaims(tokenString, &jwt.RegisteredClaims{}, func(token *jwt.Token) (interface{}, error) { + if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { + return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) + } + return mySigningKey, nil + }) + if err != nil { + log.Logger.Info(err) + } + + // 对空接口类型值进行类型断言 + // 如果类型断言成功并且 token 的有效位为 true(ParseWithClaims 方法调用成功后会将 Vaild 设置为 true) + if cliams, ok := token.Claims.(*jwt.RegisteredClaims); ok && token.Valid { + return cliams, nil + } + + return nil, errors.New("invalid token") +} diff --git a/internal/middleware/logger/logger.go b/internal/middleware/logger/logger.go new file mode 100644 index 0000000..4ce689d --- /dev/null +++ b/internal/middleware/logger/logger.go @@ -0,0 +1,99 @@ +package logger + +import ( + "bytes" + "io" + "strconv" + "strings" + "time" + + "github.com/TensoRaws/NuxBT-Backend/module/log" + "github.com/TensoRaws/NuxBT-Backend/module/util" + "github.com/gin-gonic/gin" +) + +// LogLayout 日志layout +type LogLayout struct { + RequestMethod string // 请求方法字段 + StatusCode int // 状态码 + Time time.Time + Metadata map[string]interface{} // 存储自定义原数据 + Path string // 访问路径 + Query string // 携带query + Body string // 携带body数据 + IP string // ip地址 + UserAgent string // 代理 + Error string // 错误 + Cost time.Duration // 花费时间 + Source string // 来源 +} + +type Logger struct { + // Filter 用户自定义过滤 + Filter func(c *gin.Context) bool + // FilterKeyword 关键字过滤(key) + FilterKeyword func(layout *LogLayout) bool + // AuthProcess 鉴权处理 + AuthProcess func(c *gin.Context, layout *LogLayout) + // 日志处理 + Print func(LogLayout) + // Source 服务唯一标识 + Source string +} + +func (l Logger) SetLoggerMiddleware() gin.HandlerFunc { + return func(c *gin.Context) { + start := time.Now() + path := c.Request.URL.Path + query := c.Request.URL.RawQuery + var body []byte + if l.Filter != nil && !l.Filter(c) { + body, _ = c.GetRawData() + c.Request.Body = io.NopCloser(bytes.NewBuffer(body)) + } + // 放行 + c.Next() + + cost := time.Since(start) + layout := LogLayout{ + RequestMethod: c.Request.Method, + StatusCode: c.Writer.Status(), + Time: time.Now(), + Path: path, + Query: query, + IP: c.ClientIP(), + UserAgent: c.Request.UserAgent(), + Error: strings.TrimRight(c.Errors.ByType(gin.ErrorTypePrivate).String(), "\n"), + Cost: cost, + Source: l.Source, + } + + if l.Filter != nil && !l.Filter(c) { + layout.Body = string(body) + } + if l.AuthProcess != nil { + // 处理鉴权需要的信息 + l.AuthProcess(c, &layout) + } + if l.FilterKeyword != nil { + // 自行判断key/value 脱敏等 + l.FilterKeyword(&layout) + } + // 自行处理日志 + l.Print(layout) + } +} + +func DefaultLogger() gin.HandlerFunc { + return Logger{ + Print: func(layout LogLayout) { + StatusMessage := layout.RequestMethod + ": " + strconv.Itoa(layout.StatusCode) + if layout.Error == "" { + log.Logger.Info(util.HighlightString(util.GREEN, StatusMessage) + " - " + util.StructToString(layout)) + } else { + log.Logger.Error(util.HighlightString(util.RED, StatusMessage) + " - " + util.StructToString(layout)) + } + }, + Source: "NuxBT-Backend", + }.SetLoggerMiddleware() +} diff --git a/internal/router/api/v1/api.go b/internal/router/api/v1/api.go new file mode 100644 index 0000000..33e6a1d --- /dev/null +++ b/internal/router/api/v1/api.go @@ -0,0 +1,42 @@ +package v1 + +import ( + "net/http" + "time" + + middleware_cache "github.com/TensoRaws/NuxBT-Backend/internal/middleware/cache" + "github.com/TensoRaws/NuxBT-Backend/internal/middleware/jwt" + "github.com/TensoRaws/NuxBT-Backend/internal/middleware/logger" + user_service "github.com/TensoRaws/NuxBT-Backend/internal/service/user" + "github.com/TensoRaws/NuxBT-Backend/module/cache" + "github.com/TensoRaws/NuxBT-Backend/module/config" + "github.com/gin-gonic/gin" +) + +func NewAPI() *gin.Engine { + r := gin.New() + r.Use(logger.DefaultLogger(), gin.Recovery()) // 日志中间件 + r.Use(middleware_cache.NewRateLimiter( + middleware_cache.Clients[cache.IPLimit], "general", config.ServerConfig.RequestLimit, 60*time.Second)) + + r.GET("/", func(c *gin.Context) { + c.JSON(http.StatusOK, gin.H{ + "msg": "NuxBT-Backend", + }) + }) + + api := r.Group("/api/v1/") + { + user := api.Group("user/") + { + // 用户注册 + user.POST("register", user_service.Register) + // 用户登录 + user.POST("login", user_service.Login) + // 用户信息 + user.GET("profile/me", jwt.RequireAuth(), user_service.ProfileMe) + } + } + + return r +} diff --git a/internal/router/init.go b/internal/router/init.go new file mode 100644 index 0000000..06fe21f --- /dev/null +++ b/internal/router/init.go @@ -0,0 +1,61 @@ +package router + +import ( + "context" + "errors" + "fmt" + "net/http" + "os/signal" + "syscall" + "time" + + v1 "github.com/TensoRaws/NuxBT-Backend/internal/router/api/v1" + "github.com/TensoRaws/NuxBT-Backend/module/config" + "github.com/TensoRaws/NuxBT-Backend/module/log" + "github.com/gin-gonic/gin" +) + +func Init() { + // Create context that listens for the interrupt signal from the OS. + ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) + defer stop() + + var mode string + if config.ServerConfig.Mode != "prod" { + mode = gin.DebugMode + } else { + mode = gin.ReleaseMode + } + log.Logger.Debugf("gin mode: %v", mode) + gin.SetMode(mode) + + e := v1.NewAPI() + log.Logger.Debugf("server port: %v", config.ServerConfig.Port) + srv := &http.Server{ + Addr: fmt.Sprintf(":%v", config.ServerConfig.Port), + Handler: e, + } + + // Initializing the server in a goroutine so that + // it won't block the graceful shutdown handling below + go func() { + if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { + log.Logger.Fatalf("listen: %s", err) + } + }() + + // Listen for the interrupt signal. + <-ctx.Done() + + // Restore default behavior on the interrupt signal and notify user of shutdown. + stop() + log.Logger.Info("shutting down gracefully, press Ctrl+C again to force") + + // The context is used to inform the server it has 5 seconds to finish + // the request it is currently handling + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + if err := srv.Shutdown(ctx); err != nil { + log.Logger.Fatal("Server forced to shutdown: ", err) + } +} diff --git a/internal/service/user/dao.go b/internal/service/user/dao.go new file mode 100644 index 0000000..126dc9b --- /dev/null +++ b/internal/service/user/dao.go @@ -0,0 +1,40 @@ +package user + +import ( + "github.com/TensoRaws/NuxBT-Backend/dal/model" + "github.com/TensoRaws/NuxBT-Backend/dal/query" +) + +// CreateUser 新建用户 +func CreateUser(user *model.User) (err error) { + q := query.User + err = q.Create(user) + return err +} + +// GetUserByEmail 根据 email 获取用户 +func GetUserByEmail(email string) (user *model.User, err error) { + q := query.User + user, err = q.Where(q.Email.Eq(email)).First() + return user, err +} + +// GetUserByID 根据 userID 获取用户 +func GetUserByID(userID int32) (user *model.User, err error) { + q := query.User + user, err = q.Where(q.UserID.Eq(userID)).First() + return user, err +} + +// GetUserRolesByID 根据 userID 获取用户角色列表 +func GetUserRolesByID(userID int32) (roles []string, err error) { + q := query.UserRole + user, err := q.Where(q.UserID.Eq(userID)).Find() + if err != nil { + return nil, err + } + for _, v := range user { + roles = append(roles, v.Role) + } + return roles, nil +} diff --git a/internal/service/user/login.go b/internal/service/user/login.go new file mode 100644 index 0000000..8bcd944 --- /dev/null +++ b/internal/service/user/login.go @@ -0,0 +1,44 @@ +package user + +import ( + "github.com/TensoRaws/NuxBT-Backend/internal/middleware/jwt" + "github.com/TensoRaws/NuxBT-Backend/module/util" + "github.com/gin-gonic/gin" + "golang.org/x/crypto/bcrypt" +) + +type LoginRequest struct { + Email string `form:"email" binding:"required,email"` + Password string `form:"password" binding:"required"` +} + +// Login 用户登录 (POST /login) +func Login(c *gin.Context) { + var req LoginRequest + if err := c.ShouldBindQuery(&req); err != nil { + util.AbortWithMsg(c, "invalid request") + return + } + + // GORM 查询 + user, err := GetUserByEmail(req.Email) + if err != nil { + util.AbortWithMsg(c, "User not found") + return + } + + // verify password + err = bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(req.Password)) + if err == nil { + // 注册之后的下次登录成功,才会为其生成 token + token := jwt.GenerateToken(user) + // 打印相应信息和用户信息以及生成的 token 值 + util.OKWithData(c, map[string]interface{}{ + "user_id": user.UserID, + "token": token, + }) + } else { + util.AbortWithMsg(c, "Invalid Username or Password") + return + } +} diff --git a/internal/service/user/profile.go b/internal/service/user/profile.go new file mode 100644 index 0000000..1fe9d62 --- /dev/null +++ b/internal/service/user/profile.go @@ -0,0 +1,62 @@ +package user + +import ( + "strconv" + + "github.com/TensoRaws/NuxBT-Backend/module/log" + "github.com/TensoRaws/NuxBT-Backend/module/util" + "github.com/gin-gonic/gin" +) + +type ProfileMeResponse struct { + Avatar string `json:"avatar"` + Background string `json:"background"` + CreatedAt string `json:"created_at"` + Email string `json:"email"` + Experience string `json:"experience"` + Inviter string `json:"inviter"` + LastActive string `json:"last_active"` + Private bool `json:"private"` + Roles []string `json:"roles"` + Signature string `json:"signature"` + UserID string `json:"user_id"` + Username string `json:"username"` +} + +// ProfileMe 获取用户自己的信息 (GET /profile/me) +func ProfileMe(c *gin.Context) { + userID, err := util.GetUserIDFromGinContext(c) + if err != nil { + util.AbortWithMsg(c, "Please login first") + return + } + + user, err := GetUserByID(int32(userID)) + if err != nil { + util.AbortWithMsg(c, "User not found") + return + } + + roles, err := GetUserRolesByID(int32(userID)) + if err != nil { + log.Logger.Info("Failed to get user roles: " + err.Error()) + roles = []string{} + } + + util.OKWithDataStruct(c, ProfileMeResponse{ + Avatar: user.Avatar, + Background: user.Background, + CreatedAt: user.CreatedAt.Format("2006-01-02 15:04:05"), + Email: user.Email, + Experience: strconv.Itoa(int(user.Experience)), + Inviter: strconv.Itoa(int(user.Inviter)), + LastActive: user.LastActive.Format("2006-01-02 15:04:05"), + Private: user.Private, + Roles: roles, + Signature: user.Signature, + UserID: strconv.Itoa(int(user.UserID)), + Username: user.Username, + }) + + log.Logger.Info("get user profile success: " + util.StructToString(user)) +} diff --git a/internal/service/user/register.go b/internal/service/user/register.go new file mode 100644 index 0000000..83379f7 --- /dev/null +++ b/internal/service/user/register.go @@ -0,0 +1,82 @@ +package user + +import ( + "strconv" + "time" + + "github.com/TensoRaws/NuxBT-Backend/dal/model" + "github.com/TensoRaws/NuxBT-Backend/module/config" + "github.com/TensoRaws/NuxBT-Backend/module/log" + "github.com/TensoRaws/NuxBT-Backend/module/util" + "github.com/gin-gonic/gin" + "golang.org/x/crypto/bcrypt" +) + +// RegisterRequest Query binding 需要打 form 标签 +type RegisterRequest struct { + Username string `form:"username" binding:"required"` + Password string `form:"password" binding:"required"` + Email string `form:"email" binding:"required,email"` + InvitationCode *string `form:"invitation_code" binding:"omitempty"` +} + +type RegisterDataResponse struct { + Email string `json:"email"` + UserID string `json:"user_id"` + Username string `json:"username"` +} + +// Register 注册 (POST /register) +func Register(c *gin.Context) { + var req RegisterRequest + if err := c.ShouldBindQuery(&req); err != nil { + util.AbortWithMsg(c, "invalid request") + return + } + + // 无邀请码注册,检查是否允许无邀请码注册 + if req.InvitationCode == nil || *req.InvitationCode == "" { + if config.ServerConfig.UseInvitationCode { + util.AbortWithMsg(c, "invitation code is required") + return + } + } else { + // 有邀请码注册,检查邀请码是否有效 + // do something + // 未实现 + // OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO + log.Logger.Info("invitation code: ", *req.InvitationCode) + } + password, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost) + if err != nil { + util.AbortWithMsg(c, "failed to hash password") + log.Logger.Error("failed to hash password: " + err.Error()) + return + } + // 注册 + err = CreateUser(&model.User{ + Username: req.Username, + Email: req.Email, + Password: string(password), + LastActive: time.Now(), + }) + if err != nil { + util.AbortWithMsg(c, "failed to register: ") + log.Logger.Error("failed to register: " + err.Error()) + return + } + + user, err := GetUserByEmail(req.Email) + if err != nil { + util.AbortWithMsg(c, "failed to get user by email") + log.Logger.Error("failed to get user by email: " + err.Error()) + return + } + + util.OKWithDataStruct(c, RegisterDataResponse{ + Email: user.Email, + UserID: strconv.FormatInt(int64(user.UserID), 10), + Username: user.Username, + }) + log.Logger.Info("register success: " + util.StructToString(user)) +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..f3034fc --- /dev/null +++ b/main.go @@ -0,0 +1,23 @@ +package main + +import ( + "log" + "os" + + "github.com/TensoRaws/NuxBT-Backend/cmd" +) + +const version = "v0.0.1" + +func main() { + app := cmd.NewApp() + app.Name = "NuxBT-Backend" + app.Usage = "NuxBT Backend Server" + app.Description = "A simple backend server for NuxBT" + app.Version = version + + err := app.Run(os.Args) + if err != nil { + log.Printf("Failed to run with %s: %v\\n", os.Args, err) + } +} diff --git a/module/cache/redis.go b/module/cache/redis.go new file mode 100644 index 0000000..dd73815 --- /dev/null +++ b/module/cache/redis.go @@ -0,0 +1,116 @@ +package cache + +import ( + "context" + "fmt" + "time" + + "github.com/TensoRaws/NuxBT-Backend/module/config" + "github.com/redis/go-redis/v9" +) + +type RDB uint8 + +const ( + IPLimit RDB = iota + User +) + +type Client struct { + C *redis.Client + Ctx context.Context +} + +func NewRedisClients(clients map[RDB]*Client) { + for k := range clients { + r := redis.NewClient(&redis.Options{ + Addr: fmt.Sprintf("%v:%v", config.RedisConfig.Host, config.RedisConfig.Port), + Password: fmt.Sprintf("%v", config.RedisConfig.Password), + DB: int(k) + 1, + PoolSize: config.RedisConfig.PoolSize, + }) + ctx := context.Background() + clients[k] = &Client{C: r, Ctx: ctx} + } +} + +func NewRedisClient(n int) *Client { + ctx := context.Background() + r := redis.NewClient(&redis.Options{ + Addr: fmt.Sprintf("%v:%v", config.RedisConfig.Host, config.RedisConfig.Port), + Password: fmt.Sprintf("%v", config.RedisConfig.Password), + DB: n, + }) + return &Client{C: r, Ctx: ctx} +} + +// 封装常用接口 + +// ClientGetName returns the name of the connection. +func (c Client) ClientGetName() *redis.StringCmd { + return c.C.ClientGetName(c.Ctx) +} + +func (c Client) Echo(message interface{}) *redis.StringCmd { + return c.C.Echo(c.Ctx, message) +} + +func (c Client) Ping() *redis.StatusCmd { + return c.C.Ping(c.Ctx) +} + +func (c Client) Del(keys ...string) *redis.IntCmd { + return c.C.Del(c.Ctx, keys...) +} + +func (c Client) Unlink(keys ...string) *redis.IntCmd { + return c.C.Unlink(c.Ctx, keys...) +} + +func (c Client) Dump(key string) *redis.StringCmd { + return c.C.Dump(c.Ctx, key) +} + +func (c Client) Exists(keys ...string) *redis.IntCmd { + return c.C.Exists(c.Ctx, keys...) +} + +func (c Client) Expire(key string, expiration time.Duration) *redis.BoolCmd { + return c.C.Expire(c.Ctx, key, expiration) +} + +func (c Client) ExpireNX(key string, expiration time.Duration) *redis.BoolCmd { + return c.C.ExpireNX(c.Ctx, key, expiration) +} + +func (c Client) ExpireXX(key string, expiration time.Duration) *redis.BoolCmd { + return c.C.ExpireXX(c.Ctx, key, expiration) +} + +func (c Client) ExpireGT(key string, expiration time.Duration) *redis.BoolCmd { + return c.C.ExpireGT(c.Ctx, key, expiration) +} + +func (c Client) ExpireLT(key string, expiration time.Duration) *redis.BoolCmd { + return c.C.ExpireLT(c.Ctx, key, expiration) +} + +func (c Client) ExpireAt(key string, tm time.Time) *redis.BoolCmd { + return c.C.ExpireAt(c.Ctx, key, tm) +} + +func (c Client) ExpireTime(key string) *redis.DurationCmd { + return c.C.ExpireTime(c.Ctx, key) +} + +func (c Client) ZRemRangeByScore(key, min, max string) *redis.IntCmd { + return c.C.ZRemRangeByScore(c.Ctx, key, min, max) +} + +func (c Client) ZRange(key string, start, stop int64) *redis.StringSliceCmd { + return c.C.ZRange(c.Ctx, key, start, stop) +} + +func (c Client) ZAddNX(key string, members ...redis.Z) *redis.IntCmd { + return c.C.ZAddNX(c.Ctx, key, members...) +} diff --git a/module/config/config.go b/module/config/config.go new file mode 100644 index 0000000..79a643b --- /dev/null +++ b/module/config/config.go @@ -0,0 +1,134 @@ +package config + +import ( + "errors" + "fmt" + "log" + "strings" + "sync" + + "github.com/fsnotify/fsnotify" + "github.com/spf13/viper" +) + +var ( + config *viper.Viper + once sync.Once +) + +var ( + ServerConfig Server + JwtConfig Jwt + LogConfig Log + DBConfig DB + RedisConfig Redis + OSSConfig OSS + OSS_PREFIX string +) + +func Init() { + once.Do(func() { + initialize() + }) +} + +func initialize() { + config = viper.New() + + config.SetConfigName("nuxbt") + config.AddConfigPath("./conf/") + config.AddConfigPath("./") + config.AddConfigPath("$HOME/.nuxbt/") + config.AddConfigPath("/etc/nuxbt/") + config.SetConfigType("yml") + + config.AutomaticEnv() + config.SetEnvPrefix("NUXBT") + replacer := strings.NewReplacer(".", "_") + config.SetEnvKeyReplacer(replacer) + + config.WatchConfig() + config.OnConfigChange(func(e fsnotify.Event) { + // 配置文件发生变更之后会调用的回调函数 + fmt.Println("Config file changed:", e.Name) + }) + + if err := config.ReadInConfig(); err != nil { + var configFileNotFoundError viper.ConfigFileNotFoundError + if errors.As(err, &configFileNotFoundError) { + // 配置文件未找到错误 + fmt.Println("config file not found use default config") + config.SetDefault("server", map[string]interface{}{ + "port": 8080, + "mode": "prod", + "allowRegister": true, + "useInvitationCode": false, + "requestLimit": 50, + }) + + config.SetDefault("jwt", map[string]interface{}{ + "timeout": 60, + "key": "nuxbt", + }) + + config.SetDefault("log", map[string]interface{}{ + "level": "debug", + "mode": []string{"console", "file"}, + }) + + config.SetDefault("db", map[string]interface{}{ + "type": "mysql", + "host": "127.0.0.1", + "port": 5432, + "username": "root", + "password": "123456", + "database": "nuxbt", + "ssl": false, + }) + + config.SetDefault("redis", map[string]interface{}{ + "host": "127.0.0.1", + "port": 6379, + "password": "123456", + "poolSize": 10, + }) + } + } + + err := config.UnmarshalKey("server", &ServerConfig) + if err != nil { + log.Fatalf("unable to decode into server struct, %v", err) + } + err = config.UnmarshalKey("jwt", &JwtConfig) + if err != nil { + log.Fatalf("unable to decode into jwt struct, %v", err) + } + err = config.UnmarshalKey("log", &LogConfig) + if err != nil { + log.Fatalf("unable to decode into log struct, %v", err) + } + err = config.UnmarshalKey("db", &DBConfig) + if err != nil { + log.Fatalf("unable to decode into db struct, %v", err) + } + err = config.UnmarshalKey("redis", &RedisConfig) + if err != nil { + log.Fatalf("unable to decode into redis struct, %v", err) + } + err = config.UnmarshalKey("oss", &OSSConfig) + if err != nil { + log.Fatalf("unable to decode into oss struct, %v", err) + } + + OSS_PREFIX = GenerateOSSPrefix() + fmt.Printf("OSS TYPE: %v", config.GetString("oss.type")) + fmt.Printf(" OSS PREFIX: %v\n", OSS_PREFIX) +} + +func Get(key string) interface{} { + return config.Get(key) +} + +func GetString(key string) string { + return config.GetString(key) +} diff --git a/module/config/generate.go b/module/config/generate.go new file mode 100644 index 0000000..9df5e9e --- /dev/null +++ b/module/config/generate.go @@ -0,0 +1,49 @@ +package config + +import ( + "fmt" +) + +// GenerateDSN 根据数据库类型生成相应的 DSN 字符串, 并返回数据库类型和 DSN 字符串 +func GenerateDSN() (string, string, error) { + var dsn string + + switch DBConfig.Type { + case "mysql": + dsn = fmt.Sprintf("%v:%v@tcp(%v:%v)/%v?charset=utf8mb4&parseTime=True&loc=Local", + DBConfig.Username, DBConfig.Password, DBConfig.Host, DBConfig.Port, DBConfig.Database) + case "postgres": + dsn = fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=allow", + DBConfig.Host, DBConfig.Port, DBConfig.Username, DBConfig.Password, DBConfig.Database) + + default: // 默认使用 mysql + dsn = fmt.Sprintf("%v:%v@tcp(%v:%v)/%v"+ + "?charset=utf8mb4&parseTime=True&loc=Local", + DBConfig.Username, DBConfig.Password, DBConfig.Host, DBConfig.Port, DBConfig.Database) + } + + return DBConfig.Type, dsn, nil +} + +// GenerateOSSPrefix 生成 OSS 对象存储的前缀 +func GenerateOSSPrefix() string { + var protocol string + + if OSSConfig.UseSSL { + protocol = "https://" + } else { + protocol = "http://" + } + + switch OSSConfig.Type { + case "minio": + OSS_PREFIX = fmt.Sprintf("%v%v/%v/", protocol, OSSConfig.Endpoint, OSSConfig.Bucket) + case "cos": + OSS_PREFIX = fmt.Sprintf("https://%v.%v/", OSSConfig.Bucket, OSSConfig.Endpoint) + default: + // 默认使用 minio + OSS_PREFIX = fmt.Sprintf("%v%v/%v/", protocol, OSSConfig.Endpoint, OSSConfig.Bucket) + } + + return OSS_PREFIX +} diff --git a/module/config/type.go b/module/config/type.go new file mode 100644 index 0000000..038daea --- /dev/null +++ b/module/config/type.go @@ -0,0 +1,47 @@ +package config + +type Server struct { + Port int `yaml:"port"` + Mode string `yaml:"mode"` + AllowResgister bool `yaml:"allowResgister"` + UseInvitationCode bool `yaml:"useInvitationCode"` + RequestLimit int `yaml:"requestLimit"` +} + +type Jwt struct { + Timeout int `yaml:"timeout"` + Key string `yaml:"key"` +} + +type Log struct { + Level string `yaml:"level"` + Mode []string `yaml:"mode"` +} + +type DB struct { + Type string `yaml:"type"` + Host string `yaml:"host"` + Port int `yaml:"port"` + Username string `yaml:"username"` + Password string `yaml:"password"` + Database string `yaml:"database"` + SSL bool `yaml:"ssl"` +} + +type Redis struct { + Host string `yaml:"host"` + Port int `yaml:"port"` + Password string `yaml:"password"` + PoolSize int `yaml:"poolSize"` +} + +type OSS struct { + Type string `yaml:"type"` + Endpoint string `yaml:"endpoint"` + AccessKey string `yaml:"accessKey"` + SecretKey string `yaml:"secretKey"` + Region string `yaml:"region"` + Bucket string `yaml:"bucket"` + UseSSL bool `yaml:"ssl"` + HostnameImmutable bool `yaml:"hostnameImmutable"` +} diff --git a/module/db/db.go b/module/db/db.go new file mode 100644 index 0000000..de5a969 --- /dev/null +++ b/module/db/db.go @@ -0,0 +1,75 @@ +package db + +import ( + "sync" + + "github.com/TensoRaws/NuxBT-Backend/dal/model" + "github.com/TensoRaws/NuxBT-Backend/dal/query" + "github.com/TensoRaws/NuxBT-Backend/module/config" + "github.com/TensoRaws/NuxBT-Backend/module/log" + "gorm.io/driver/mysql" + "gorm.io/driver/postgres" + "gorm.io/gorm" + "gorm.io/gorm/schema" +) + +var ( + DB *gorm.DB + once sync.Once +) + +func Init() { + once.Do(func() { + initialize() + }) +} + +func initialize() { + dbType, dsn, err := config.GenerateDSN() + if err != nil { + log.Logger.Error(err) + return + } + + cfg := gorm.Config{ + DisableForeignKeyConstraintWhenMigrating: true, + NamingStrategy: schema.NamingStrategy{ + SingularTable: true, + }, + } + + DB = ConnectDB(dbType, dsn, &cfg) + + err = DB.AutoMigrate( + model.User{}, + model.UserRole{}, + ) + if err != nil { + log.Logger.Error(err) + return + } + query.SetDefault(DB) + log.Logger.Debugf("Set query default database") +} + +func ConnectDB(dbType, dsn string, config *gorm.Config) (db *gorm.DB) { + var err error + + log.Logger.Debugf("DBType: %v", dbType) + log.Logger.Debugf("DSN: %v", dsn) + + switch dbType { + case "mysql": + db, err = gorm.Open(mysql.Open(dsn), config) + case "postgres": + db, err = gorm.Open(postgres.Open(dsn), config) + default: + db, err = gorm.Open(mysql.Open(dsn), config) + } + + if err != nil { + log.Logger.Fatalf("connect db fail: %v", err) + } + + return db +} diff --git a/module/log/log.go b/module/log/log.go new file mode 100644 index 0000000..4e979ed --- /dev/null +++ b/module/log/log.go @@ -0,0 +1,45 @@ +package log + +import ( + "os" + "sync" + "time" + + "github.com/TensoRaws/NuxBT-Backend/module/config" + "github.com/charmbracelet/log" +) + +var ( + Logger *log.Logger + once sync.Once +) + +func Init() { + once.Do(func() { + initialize() + }) +} + +func initialize() { + Logger = log.NewWithOptions(os.Stderr, log.Options{ + ReportCaller: true, + ReportTimestamp: true, + TimeFormat: time.RFC3339Nano, + Prefix: "NuxBT-Backend", + }) + + level := config.LogConfig.Level + switch level { + case "debug": + Logger.SetLevel(log.DebugLevel) + case "info": + Logger.SetLevel(log.InfoLevel) + case "warn": + Logger.SetLevel(log.WarnLevel) + case "error": + Logger.SetLevel(log.ErrorLevel) + default: + Logger.SetLevel(log.DebugLevel) + } + Logger.Debugf("log level: %v", level) +} diff --git a/module/oss/oss.go b/module/oss/oss.go new file mode 100644 index 0000000..028adfb --- /dev/null +++ b/module/oss/oss.go @@ -0,0 +1,84 @@ +package oss + +import ( + "context" + "io" + "sync" + + "github.com/TensoRaws/NuxBT-Backend/module/config" + "github.com/TensoRaws/NuxBT-Backend/module/log" + "github.com/eleven26/goss/v4" +) + +var ( + oss *goss.Goss + err error + once sync.Once +) + +func Init() { + once.Do(func() { + initialize() + }) +} + +func initialize() { + cfg := &goss.Config{ + Endpoint: config.OSSConfig.Endpoint, + AccessKey: config.OSSConfig.AccessKey, + SecretKey: config.OSSConfig.SecretKey, + Region: config.OSSConfig.Region, + Bucket: config.OSSConfig.Bucket, + UseSsl: &config.OSSConfig.UseSSL, + HostnameImmutable: &config.OSSConfig.HostnameImmutable, + } + oss, err = goss.New(goss.WithConfig(cfg)) + if err != nil { + log.Logger.Errorf("init goss faild: %v", err) + } +} + +// Put saves the content read from r to the key of oss. +func Put(key string, r io.Reader) error { + return oss.Put(context.TODO(), key, r) +} + +// PutFromFile saves the file pointed to by the `localPath` to the oss key. +func PutFromFile(key string, localPath string) error { + return oss.PutFromFile(context.TODO(), key, localPath) +} + +// Get gets the file pointed to by key. +func Get(key string) (io.ReadCloser, error) { + return oss.Get(context.TODO(), key) +} + +// GetString gets the file pointed to by key and returns a string. +func GetString(key string) (string, error) { + return oss.GetString(context.TODO(), key) +} + +// GetBytes gets the file pointed to by key and returns a byte array. +func GetBytes(key string) ([]byte, error) { + return oss.GetBytes(context.TODO(), key) +} + +// GetToFile saves the file pointed to by key to the localPath. +func GetToFile(key string, localPath string) error { + return oss.GetToFile(context.TODO(), key, localPath) +} + +// Delete the file pointed to by key. +func Delete(key string) error { + return oss.Delete(context.TODO(), key) +} + +// Exists determines whether the file exists. +func Exists(key string) (bool, error) { + return oss.Exists(context.TODO(), key) +} + +// Size fet the file size. +func Size(key string) (int64, error) { + return oss.Size(context.TODO(), key) +} diff --git a/module/util/gin.go b/module/util/gin.go new file mode 100644 index 0000000..898336c --- /dev/null +++ b/module/util/gin.go @@ -0,0 +1,59 @@ +package util + +import ( + "fmt" + "net/http" + "strconv" + + "github.com/gin-gonic/gin" +) + +// GetUserIDFromGinContext 从 RequireAuth 处读取 user_id +func GetUserIDFromGinContext(c *gin.Context) (int64, error) { + userIDstr := c.GetString("user_id") + // 未登录 + if len(userIDstr) == 0 { + return -1, fmt.Errorf("user_id is null") + } + // 已登录 + userID, err := strconv.ParseInt(userIDstr, 10, 64) + + return userID, err +} + +// OKWithMsg 返回成功信息 +func OKWithMsg(c *gin.Context, ok string) { + resp := map[string]interface{}{ + "success": true, + "message": ok, + } + c.JSON(http.StatusOK, resp) +} + +// OKWithData 返回成功信息,携带自定义数据 +func OKWithData(c *gin.Context, data map[string]interface{}) { + resp := map[string]interface{}{ + "success": true, + "message": "ok", + "data": data, + } + c.JSON(http.StatusOK, resp) +} + +// OKWithDataStruct 返回成功信息,携带自定义数据(结构体) +func OKWithDataStruct(c *gin.Context, data interface{}) { + resp := map[string]interface{}{ + "success": true, + "message": "ok", + "data": data, + } + c.JSON(http.StatusOK, resp) +} + +func AbortWithMsg(c *gin.Context, msg string) { + resp := map[string]interface{}{ + "success": false, + "message": msg, + } + c.AbortWithStatusJSON(http.StatusOK, resp) +} diff --git a/module/util/print.go b/module/util/print.go new file mode 100644 index 0000000..231e047 --- /dev/null +++ b/module/util/print.go @@ -0,0 +1,44 @@ +package util + +import "github.com/bytedance/sonic" + +type Color string + +// 高亮颜色map +var colorMap = map[Color]string{ + "green": "\033[97;42m", + "white": "\033[90;47m", + "yellow": "\033[90;43m", + "red": "\033[97;41m", + "blue": "\033[97;44m", + "magenta": "\033[97;45m", + "cyan": "\033[97;46m", + "reset": "\033[0m", +} + +const ( + GREEN Color = "green" + WHITE Color = "white" + YELLOW Color = "yellow" + RED Color = "red" + BLUE Color = "blue" + MAGENTA Color = "magenta" + CYAN Color = "cyan" + RESET Color = "reset" +) + +// HighlightString 高亮字符串 +func HighlightString(color Color, str string) string { + // 判断是否存在颜色,不存在返回绿色 + if _, ok := colorMap[color]; !ok { + return colorMap["green"] + str + colorMap["reset"] + } + return colorMap[color] + str + colorMap["reset"] +} + +// StructToString 结构体转字符串 +func StructToString(s interface{}) string { + // v, _ := json.Marshal(s) + v, _ := sonic.Marshal(s) + return string(v) +} diff --git a/module/util/random.go b/module/util/random.go new file mode 100644 index 0000000..e095443 --- /dev/null +++ b/module/util/random.go @@ -0,0 +1,22 @@ +package util + +import ( + "crypto/rand" + "math/big" +) + +// GetRandomString 生成随机字符串,不为空则成功生成 +func GetRandomString(length int) string { + const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + var result []byte + + for i := 0; i < length; i++ { + num, err := rand.Int(rand.Reader, big.NewInt(int64(len(letters)))) + if err != nil { + return "" + } + result = append(result, letters[num.Int64()]) + } + + return string(result) +} diff --git a/module/util/random_test.go b/module/util/random_test.go new file mode 100644 index 0000000..be61d5f --- /dev/null +++ b/module/util/random_test.go @@ -0,0 +1,19 @@ +package util + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestGetRandomString(t *testing.T) { + a := assert.New(t) + a.Equal(10, len(GetRandomString(10))) + a.Equal(20, len(GetRandomString(20))) + // 生成100长度字符串,测试是否有重复 + for i := 0; i < 100; i++ { + for j := i + 1; j < 100; j++ { + a.NotEqual(GetRandomString(100), GetRandomString(100)) + } + } +} diff --git a/scripts/README.md b/scripts/README.md new file mode 100644 index 0000000..5502936 --- /dev/null +++ b/scripts/README.md @@ -0,0 +1,19 @@ +# scripts + +MySQL + +```bash +docker run --name some-mysql -e MYSQL_ROOT_PASSWORD=123456 -e MYSQL_DATABASE=nuxbt -e TZ=Asia/Shanghai -p 5432:3306 -d mysql:8.4.1 +``` + +Redis + +```bash +docker run --name some-redis -d --health-cmd "redis-cli ping" --health-interval 5s --health-timeout 3s --health-retries 10 -p 6379:6379 redis +``` + +Minio + +```bash +docker run --name some-minio -d -e MINIO_ACCESS_KEY=ChYm7ufIwNAOzq6PQPCA -e MINIO_SECRET_KEY=udicP52IwRbmo2hf6lFvjUS7NP5BhlAdsGNIuDE5 -e MINIO_DEFAULT_BUCKETS=nuxbt:public -p 9000:9000 bitnami/minio:latest +``` diff --git a/scripts/db.sql b/scripts/db.sql new file mode 100644 index 0000000..493353f --- /dev/null +++ b/scripts/db.sql @@ -0,0 +1,29 @@ +-- 创建用户表 +DROP TABLE IF EXISTS `user`; +CREATE TABLE `user` ( + `user_id` INT NOT NULL AUTO_INCREMENT, -- 使用 AUTO_INCREMENT 作为自增主键 + `username` VARCHAR(255) NOT NULL UNIQUE, -- 用户名 + `email` VARCHAR(255) NOT NULL UNIQUE, -- 用户邮箱 + `password` VARCHAR(255) NOT NULL, -- 用户密码 + `private` BOOLEAN NOT NULL DEFAULT false, -- 是否私密,默认为 false + `experience` INT DEFAULT 0, -- 用户经验值,默认为 0 + `inviter` INT NOT NULL DEFAULT 0, -- 邀请人ID + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, -- 账户创建时间 + `last_active` DATETIME, -- 最后活跃时间 + `avatar` VARCHAR(255), -- 用户头像链接 + `signature` TEXT, -- 用户签名 + `background` VARCHAR(255), -- 用户背景图片链接 + `deleted_at` DATETIME, -- 账户删除时间 + PRIMARY KEY (`user_id`) +); + +-- 创建用户角色表 +CREATE TABLE `user_role` ( + `role_id` INT NOT NULL AUTO_INCREMENT, -- 角色ID,作为自增主键 + `user_id` INT NOT NULL, -- 用户ID + `role` VARCHAR(255) NOT NULL, -- 角色名称 + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, -- 创建时间 + `deleted_at` DATETIME, -- 软删除时间戳 + PRIMARY KEY (`role_id`), + UNIQUE KEY `uk_user_role` (`user_id`, `role`) +);