commit 3e8f494cb979cdc2ed42961e45bbb98196f94a03 Author: Daniel Date: Thu Jun 19 10:33:58 2025 +0800 first commit diff --git a/.devcontainer/.env b/.devcontainer/.env new file mode 100644 index 0000000..cdbe3af --- /dev/null +++ b/.devcontainer/.env @@ -0,0 +1,5 @@ + +POSTGRES_DB=ginadmin +POSTGRES_USER=postgres +POSTGRES_PASSWORD=123456 +DATABASE_URL=postgres://postgres:123456@db:5432/ginadmin diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 0000000..98a92eb --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,13 @@ +FROM mcr.microsoft.com/devcontainers/go:1.22-bookworm + +ARG APP=haibei + +# Set CGO_CFLAGS to enable large file support +ENV CGO_CFLAGS "-D_LARGEFILE64_SOURCE" + +RUN go install github.com/google/wire/cmd/wire@latest \ + && go install github.com/swaggo/swag/cmd/swag@latest \ + && go install github.com/gin-admin/gin-admin-cli/v10@latest \ + && chown -R vscode /go + + diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..e9125c1 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,9 @@ +{ + "name": "ginadmin", + "dockerComposeFile": "docker-compose.yml", + "service": "app", + "workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}", + "forwardPorts": [ + 8040 + ] +} diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml new file mode 100644 index 0000000..3cc3038 --- /dev/null +++ b/.devcontainer/docker-compose.yml @@ -0,0 +1,42 @@ +version: "3" + +services: + app: + build: + context: . + dockerfile: Dockerfile + command: sleep infinity + networks: + - db + - redis + volumes: + - ../..:/workspaces:cached + env_file: + - .env + + db: + image: postgres:15.3-alpine + restart: unless-stopped + ports: + - 5432:5432 + networks: + - db + volumes: + - postgres-data:/var/lib/postgresql/data + env_file: + - .env + + redis: + image: redis:latest + restart: unless-stopped + ports: + - 6379:6379 + networks: + - redis + +volumes: + postgres-data: + +networks: + db: + redis: \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..443a5df --- /dev/null +++ b/.gitignore @@ -0,0 +1,32 @@ +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, build with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +*.DS_Store +/haibei +/haibei_linux_amd64 +/haibei.lock +/release +/data +/internal/test/data +tmp +/vendor +/configs/gen_rbac_policy.csv +/configs/gen_rbac_policy.csv.bak +/configs/rbac_policy.csv.bak +/test/data +/internal/swagger/v3/.openapi-generator +/internal/swagger/v3/.openapi-generator-ignore + +# IDE configs +.idea +.vscode \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..a82b33a --- /dev/null +++ b/Dockerfile @@ -0,0 +1,28 @@ +FROM golang:alpine as builder + +ARG APP=haibei +ARG VERSION=v1.0.0 +ARG RELEASE_TAG=$(VERSION) + +# Install the required packages +RUN apk add --no-cache gcc musl-dev sqlite-dev + +# Set CGO_CFLAGS to enable large file support +ENV CGO_CFLAGS "-D_LARGEFILE64_SOURCE" + +ENV GOPROXY="https://goproxy.cn" + +WORKDIR /go/src/${APP} +COPY . . + +# Build the application +RUN go build -ldflags "-w -s -X main.VERSION=${RELEASE_TAG}" -o ./${APP} . + +FROM alpine +ARG APP=haibei +WORKDIR /go/src/${APP} +COPY --from=builder /go/src/${APP}/${APP} /usr/bin/ +# COPY --from=builder /go/src/${APP}/configs /usr/bin/configs +# COPY --from=builder /go/src/${APP}/dist /usr/bin/dist +ENTRYPOINT ["haibei", "start", "-d", "/usr/bin/configs", "-c", "prod", "-s", "/usr/bin/dist"] +EXPOSE 8040 diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..f22f05e --- /dev/null +++ b/Makefile @@ -0,0 +1,51 @@ +.PHONY: start build + +NOW = $(shell date -u '+%Y%m%d%I%M%S') + +RELEASE_VERSION = v1.0.0 + +APP = haibei +SERVER_BIN = ${APP} +GIT_COUNT = $(shell git rev-list --all --count) +GIT_HASH = $(shell git rev-parse --short HEAD) +RELEASE_TAG = $(RELEASE_VERSION).$(GIT_COUNT).$(GIT_HASH) + +CONFIG_DIR = ./configs +CONFIG_FILES = dev +STATIC_DIR = ./build/dist +START_ARGS = -d $(CONFIG_DIR) -c $(CONFIG_FILES) -s $(STATIC_DIR) + +all: start + +start: + @go run -ldflags "-X main.VERSION=$(RELEASE_TAG)" main.go start $(START_ARGS) + +build: + @go build -ldflags "-w -s -X main.VERSION=$(RELEASE_TAG)" -o $(SERVER_BIN) + +build-linux: + CGO_ENABLED=1 GOOS=linux GOARCH=amd64 CC="zig cc -target x86_64-linux-musl" CXX="zig c++ -target x86_64-linux-musl" CGO_CFLAGS="-D_LARGEFILE64_SOURCE" go build -ldflags "-w -s -X main.VERSION=$(RELEASE_TAG)" -o $(SERVER_BIN)_linux_amd64 + +# go install github.com/google/wire/cmd/wire@latest +wire: + @wire gen ./internal/wirex + +# go install github.com/swaggo/swag/cmd/swag@latest +swagger: + @swag init --parseDependency --generalInfo ./main.go --output ./internal/swagger + +# https://github.com/OpenAPITools/openapi-generator +openapi: + docker run --rm -v ${PWD}:/local openapitools/openapi-generator-cli generate -i /local/internal/swagger/swagger.yaml -g openapi -o /local/internal/swagger/v3 + +clean: + rm -rf data $(SERVER_BIN) + +serve: build + ./$(SERVER_BIN) start $(START_ARGS) + +serve-d: build + ./$(SERVER_BIN) start $(START_ARGS) -d + +stop: + ./$(SERVER_BIN) stop \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..80f4fff --- /dev/null +++ b/README.md @@ -0,0 +1,28 @@ +# haibei + +> 中海小程序 + +## Quick Start + +```bash +make start +``` + +## Build + +```bash +make build +``` + +## Generate wire inject files + +```bash +make wire +``` + +## Generate swagger documents + +```bash +make swagger +``` + diff --git a/README_EN.md b/README_EN.md new file mode 100644 index 0000000..c20dbfa --- /dev/null +++ b/README_EN.md @@ -0,0 +1,250 @@ +# [Gin](https://github.com/gin-gonic/gin)-Admin + +> A lightweight, flexible, elegant and full-featured RBAC scaffolding based on Golang + Gin + GORM 2.0 + Casbin 2.0 + Wire DI. + +English | [中文](README.md) + +[![LICENSE](https://img.shields.io/github/license/LyricTian/gin-admin.svg)](https://github.com/LyricTian/gin-admin/blob/main/LICENSE) +[![Language](https://img.shields.io/badge/Language-Go-blue.svg)](https://golang.org/) +[![Go Report Card](https://goreportcard.com/badge/github.com/LyricTian/gin-admin)](https://goreportcard.com/report/github.com/LyricTian/gin-admin) +[![GitHub release](https://img.shields.io/github/tag/LyricTian/gin-admin.svg?label=release)](https://github.com/LyricTian/gin-admin/releases) +[![GitHub release date](https://img.shields.io/github/release-date/LyricTian/gin-admin.svg)](https://github.com/LyricTian/gin-admin/releases) +[![GoDoc](https://img.shields.io/badge/Godoc-reference-blue.svg)](https://godoc.org/github.com/LyricTian/gin-admin) + +## Features + +- :scroll: Elegant implementation of `RESTful API`, using interface-based programming paradigm to make your API design more professional and standardized +- :house: Adopts clear and concise modular architecture, making code structure clear at a glance, maintenance and upgrades more effortless +- :rocket: Based on high-performance `GIN` framework, integrating rich and practical middleware (authentication, CORS, logging, rate limiting, tracing, permission control, fault tolerance, compression, etc.), helping you quickly build enterprise-level applications +- :closed_lock_with_key: Integrates industry-leading `Casbin` permission framework, flexible and precise RBAC permission control makes security protection rock solid +- :page_facing_up: Based on powerful `GORM 2.0` ORM framework, elegantly handles database operations, greatly improving development efficiency +- :electric_plug: Innovatively adopts `WIRE` dependency injection, revolutionarily simplifies module dependency relationships, making code more elegant and decoupled +- :memo: Based on high-performance `Zap` logging framework, coupled with Context tracing, making system running status clear and transparent, problem troubleshooting nowhere to hide +- :key: Integrates time-tested `JWT` authentication mechanism, making user identity verification more secure and reliable +- :microscope: Automatically integrates `Swagger` API documentation, real-time API documentation updates, making development and debugging easier - [Online Demo](https://demo.ginadmin.top/swagger/index.html) +- :wrench: Complete unit testing system, based on `testify` framework to ensure system quality, leaving no place for bugs to hide +- :100: Adopts stateless design, supports horizontal scaling, paired with Redis to implement dynamic permission management, letting your system easily handle high concurrency +- :hammer: Developer's blessing! Powerful scaffolding tool [gin-admin-cli](https://github.com/gin-admin/gin-admin-cli), making your development work twice as efficient + +![demo](./demo.png) +![swagger](./swagger.png) + +## Frontend Projects + +- [Frontend project based on Ant Design React](https://github.com/gin-admin/gin-admin-frontend) +- [Frontend project based on Vue.js](https://github.com/gin-admin/gin-admin-vue) + +## Install Dependencies + +- [Go](https://golang.org/) 1.19+ +- [Wire](github.com/google/wire) `go install github.com/google/wire/cmd/wire@latest` +- [Swag](github.com/swaggo/swag) `go install github.com/swaggo/swag/cmd/swag@latest` +- [GIN-ADMIN-CLI](https://github.com/gin-admin/gin-admin-cli) `go install github.com/gin-admin/gin-admin-cli/v10@latest` + +## Quick Start + +### Create a New Project + +> You can view detailed command instructions via `gin-admin-cli help new` + +```bash +gin-admin-cli new -d ~/go/src --name testapp --desc 'A test API service based on golang.' --pkg 'github.com/xxx/testapp' --git-url https://gitee.com/lyric/gin-admin.git +``` + +### Start the Service + +> You can switch to Chinese menu by changing `MenuFile = "menu_cn.json"` in the `configs/dev/server.toml` configuration file + +```bash +cd ~/go/src/testapp + +make start +# or +go run main.go start +``` + +### Compile the Service + +```bash +make build +# or +go build -ldflags "-w -s -X main.VERSION=v1.0.0" -o testapp +``` + +### Generate Docker Image + +```bash +docker build -f ./Dockerfile -t testapp:v1.0.0 . +``` + +### Generate Codes + +> You can view detailed command instructions via `gin-admin-cli help gen` + +#### Prepare Configuration File `dictionary.yaml` + +```yaml +- name: Dictionary + comment: Dictionary management + disable_pagination: true + fill_gorm_commit: true + fill_router_prefix: true + tpl_type: "tree" + fields: + - name: Code + type: string + comment: Code of dictionary (unique for same parent) + gorm_tag: "size:32;" + form: + binding_tag: "required,max=32" + - name: Name + type: string + comment: Display name of dictionary + gorm_tag: "size:128;index" + query: + name: LikeName + in_query: true + form_tag: name + op: LIKE + form: + binding_tag: "required,max=128" + - name: Description + type: string + comment: Details about dictionary + gorm_tag: "size:1024" + form: {} + - name: Sequence + type: int + comment: Sequence for sorting + gorm_tag: "index;" + order: DESC + form: {} + - name: Status + type: string + comment: Status of dictionary (disabled, enabled) + gorm_tag: "size:20;index" + query: {} + form: + binding_tag: "required,oneof=disabled enabled" +``` + +```bash +gin-admin-cli gen -d . -m SYS -c dictionary.yaml +``` + +### Delete Function Module + +> You can view detailed command instructions via `gin-admin-cli help remove` + +```bash +gin-admin-cli rm -d . -m CMS --structs Article +``` + +### Generate Swagger Documentation + +> You can generate Swagger documentation automatically via [Swag](github.com/swaggo/swag) + +```bash +make swagger +# or +swag init --parseDependency --generalInfo ./main.go --output ./internal/swagger +``` + +### Generate Dependency Injection Code + +> Dependency injection itself is used to solve the initial process of layer dependency among various modules, and you can generate dependency injection code automatically via [Wire](github.com/google/wire) to simplify the dependency injection process. + +```bash +make wire +# or +wire gen ./internal/wirex +``` + +## Project Structure Overview + +```text +├── cmd (Command line definition directory) +│ ├── start.go (Start command) +│ ├── stop.go (Stop command) +│ └── version.go (Version command) +├── configs +│ ├── dev +│ │ ├── logging.toml (Logging configuration file) +│ │ ├── middleware.toml (Middleware configuration file) +│ │ └── server.toml (Service configuration file) +│ ├── menu.json (Initialization menu file) +│ └── rbac_model.conf (Casbin RBAC model configuration file) +├── internal +│ ├── bootstrap (Initialization directory) +│ │ ├── bootstrap.go (Initialization) +│ │ ├── http.go (HTTP service) +│ │ └── logger.go (Logging service) +│ ├── config (Configuration file directory) +│ │ ├── config.go (Configuration file initialization) +│ │ ├── consts.go (Constant definition) +│ │ ├── middleware.go (Middleware configuration) +│ │ └── parse.go (Configuration file parsing) +│ ├── mods +│ │ ├── rbac (RBAC module) +│ │ │ ├── api (API layer) +│ │ │ ├── biz (Business logic layer) +│ │ │ ├── dal (Data access layer) +│ │ │ ├── schema (Data model layer) +│ │ │ ├── casbin.go (Casbin initialization) +│ │ │ ├── main.go (RBAC module entry) +│ │ │ └── wire.go (RBAC dependency injection initialization) +│ │ └── mods.go +│ ├── utility +│ │ └── prom +│ │ └── prom.go (Prometheus monitoring, used for integration with prometheus) +│ └── wirex (Dependency injection directory, contains the definition and initialization of dependency groups) +│ ├── injector.go +│ ├── wire.go +│ └── wire_gen.go +├── pkg (Public package directory) +│ ├── cachex (Cache package) +│ ├── crypto (Encryption package) +│ │ ├── aes (AES encryption) +│ │ ├── hash (Hash encryption) +│ │ └── rand (Random number) +│ ├── encoding (Encoding package) +│ │ ├── json (JSON encoding) +│ │ ├── toml (TOML encoding) +│ │ └── yaml (YAML encoding) +│ ├── errors (Error handling package) +│ ├── gormx (Gorm extension package) +│ ├── jwtx (JWT package) +│ ├── logging (Logging package) +│ ├── mail (Mail package) +│ ├── middleware (Middleware package) +│ ├── oss (Object storage package) +│ ├── promx (Prometheus package) +│ └── util (Utility package) +├── test (Unit test directory) +│ ├── menu_test.go +│ ├── role_test.go +│ ├── test.go +│ └── user_test.go +├── Dockerfile +├── Makefile +├── README.md +├── go.mod +├── go.sum +└── main.go (Entry file) +``` + +## License + +Copyright (c) 2023 Lyric + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/cmd/start.go b/cmd/start.go new file mode 100644 index 0000000..aa56e2f --- /dev/null +++ b/cmd/start.go @@ -0,0 +1,105 @@ +package cmd + +import ( + "context" + "fmt" + "github.guxuan/haibei/internal/config" + "os" + "os/exec" + "path/filepath" + "strings" + + "github.com/urfave/cli/v2" + "github.guxuan/haibei/internal/bootstrap" +) + +// The function defines a CLI command to start a server with various flags and options, including the +// ability to run as a daemon. +func StartCmd() *cli.Command { + return &cli.Command{ + Name: "start", + Usage: "Start server", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "workdir", + Aliases: []string{"d"}, + Usage: "Working directory", + DefaultText: "configs", + Value: "configs", + }, + &cli.StringFlag{ + Name: "config", + Aliases: []string{"c"}, + Usage: "Runtime configuration files or directory (relative to workdir, multiple separated by commas)", + DefaultText: "dev", + Value: "dev", + }, + &cli.StringFlag{ + Name: "static", + Aliases: []string{"s"}, + Usage: "Static files directory", + }, + &cli.BoolFlag{ + Name: "daemon", + Usage: "Run as a daemon", + }, + }, + Action: func(c *cli.Context) error { + workDir := c.String("workdir") + staticDir := c.String("static") + configs := c.String("config") + + if c.Bool("daemon") { + bin, err := filepath.Abs(os.Args[0]) + if err != nil { + fmt.Printf("failed to get absolute path for command: %s \n", err.Error()) + return err + } + + args := []string{"start"} + args = append(args, "-d", workDir) + args = append(args, "-c", configs) + args = append(args, "-s", staticDir) + fmt.Printf("execute command: %s %s \n", bin, strings.Join(args, " ")) + command := exec.Command(bin, args...) + + // Redirect stdout and stderr to log file + stdLogFile := fmt.Sprintf("%s.log", c.App.Name) + file, err := os.OpenFile(stdLogFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) + if err != nil { + fmt.Printf("failed to open log file: %s \n", err.Error()) + return err + } + defer file.Close() + + command.Stdout = file + command.Stderr = file + + err = command.Start() + if err != nil { + fmt.Printf("failed to start daemon thread: %s \n", err.Error()) + return err + } + + // Don't wait for the command to finish + // The main process will exit, allowing the daemon to run independently + fmt.Printf("Service %s daemon thread started successfully\n", config.C.General.AppName) + + pid := command.Process.Pid + _ = os.WriteFile(fmt.Sprintf("%s.lock", c.App.Name), []byte(fmt.Sprintf("%d", pid)), 0666) + fmt.Printf("service %s daemon thread started with pid %d \n", config.C.General.AppName, pid) + os.Exit(0) + } + + err := bootstrap.Run(context.Background(), bootstrap.RunConfig{ + WorkDir: workDir, + Configs: configs, + StaticDir: staticDir, + }) + if err != nil { + panic(err) + } + return nil + }, + } +} diff --git a/cmd/stop.go b/cmd/stop.go new file mode 100644 index 0000000..866b236 --- /dev/null +++ b/cmd/stop.go @@ -0,0 +1,40 @@ +package cmd + +import ( + "fmt" + "os" + "os/exec" + + "github.com/urfave/cli/v2" +) + +// The function defines a CLI command to stop a server by reading a lock file, killing the process with +// the corresponding PID, and removing the lock file. +func StopCmd() *cli.Command { + return &cli.Command{ + Name: "stop", + Usage: "stop server", + Action: func(c *cli.Context) error { + appName := c.App.Name + lockFile := fmt.Sprintf("%s.lock", appName) + pid, err := os.ReadFile(lockFile) + if err != nil { + return err + } + + command := exec.Command("kill", string(pid)) + err = command.Start() + if err != nil { + return err + } + + err = os.Remove(lockFile) + if err != nil { + return fmt.Errorf("can't remove %s.lock. %s", appName, err.Error()) + } + + fmt.Printf("service %s stopped \n", appName) + return nil + }, + } +} diff --git a/cmd/version.go b/cmd/version.go new file mode 100644 index 0000000..605715b --- /dev/null +++ b/cmd/version.go @@ -0,0 +1,19 @@ +package cmd + +import ( + "fmt" + + "github.com/urfave/cli/v2" +) + +// This function creates a CLI command that prints the version number. +func VersionCmd(v string) *cli.Command { + return &cli.Command{ + Name: "version", + Usage: "Show version", + Action: func(_ *cli.Context) error { + fmt.Println(v) + return nil + }, + } +} diff --git a/configs/dev/logging.toml b/configs/dev/logging.toml new file mode 100644 index 0000000..8e68e94 --- /dev/null +++ b/configs/dev/logging.toml @@ -0,0 +1,26 @@ +[Logger] +Debug = true +Level = "debug" # debug/info/warn/error/dpanic/panic/fatal +CallerSkip = 1 + +[Logger.File] +Enable = false +Path = "./haibei.log" +MaxBackups = 20 # Files +MaxSize = 64 # MB + +[[Logger.Hooks]] +Enable = true +Level = "info" +Type = "gorm" # gorm +MaxBuffer = 1024 +MaxThread = 2 + +[Logger.Hooks.Options] +Debug = "false" +DBType = "mysql" # sqlite3/mysql/postgres +DSN = "haibei:haibei@tcp(115.239.217.220:3306)/haibei?charset=utf8mb4&parseTime=True&loc=Local" +MaxOpenConns = "16" +MaxIdleConns = "4" +MaxLifetime = "86400" +MaxIdleTime = "7200" diff --git a/configs/dev/middleware.toml b/configs/dev/middleware.toml new file mode 100644 index 0000000..635d39e --- /dev/null +++ b/configs/dev/middleware.toml @@ -0,0 +1,75 @@ +[Middleware] + +[Middleware.Recovery] +Skip = 3 + +[Middleware.CORS] +Enable = true +AllowOrigins = ["*"] +AllowMethods = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"] +AllowHeaders = ["*"] +MaxAge = 86400 +AllowWildcard = true +AllowWebSockets = true +AllowFiles = true + +[Middleware.Trace] +RequestHeaderKey = "X-Request-Id" +ResponseTraceKey = "X-Trace-Id" + +[Middleware.Logger] +MaxOutputRequestBodyLen = 4096 # bytes +MaxOutputResponseBodyLen = 4096 # bytes + +[Middleware.CopyBody] +MaxContentLen = 134217728 # 128MB + +[Middleware.Auth] +Disable = false +SkippedPathPrefixes = ["/api/v1/captcha/", "/api/v1/login","/api/v1/apps/login"] +SigningMethod = "HS512" # HS256/HS384/HS512 +SigningKey = "XnEsT0S@" # Secret key +OldSigningKey = "" # Old secret key (For change secret key) +Expired = 86400 # seconds + +[Middleware.Auth.Store] +Type = "badger" # memory/badger/redis +Delimiter = ":" + +[Middleware.Auth.Store.Memory] +CleanupInterval = 60 # seconds + +[Middleware.Auth.Store.Badger] +Path = "data/auth" + +[Middleware.Auth.Store.Redis] +Addr = "115.239.217.220:6379" # If empty, then use the same configuration as Storage.Cache.Redis +Username = "" +Password = "123456" +DB = 2 + +[Middleware.RateLimiter] +Enable = false +Period = 10 # seconds +MaxRequestsPerIP = 1000 +MaxRequestsPerUser = 500 + +[Middleware.RateLimiter.Store] +Type = "redis" # memory/redis + +[Middleware.RateLimiter.Store.Memory] +Expiration = 3600 +CleanupInterval = 60 + +[Middleware.RateLimiter.Store.Redis] +Addr = "115.239.217.220:6379" # If empty, then use the same configuration as Storage.Cache.Redis +Username = "" +Password = "123456" +DB = 10 +[Middleware.Casbin] +Disable = true +SkippedPathPrefixes = ["/api/v1/captcha/", "/api/v1/login", "/api/v1/current/"] +LoadThread = 2 +AutoLoadInterval = 3 # seconds +ModelFile = "rbac_model.conf" +GenPolicyFile = "gen_rbac_policy.csv" diff --git a/configs/dev/server.toml b/configs/dev/server.toml new file mode 100644 index 0000000..aa78b44 --- /dev/null +++ b/configs/dev/server.toml @@ -0,0 +1,85 @@ +[General] +AppName = "haibei" +Version = "v10.1.0" +Debug = true +PprofAddr = "" # Pprof monitor address, "localhost:6060" +DisableSwagger = false +DisablePrintConfig = false +DefaultLoginPwd = "6351623c8cef86fefabfa7da046fc619" # MD5("abc-123") +MenuFile = "menu_cn.json" # Or use "menu_cn.json" +DenyOperateMenu = false + +[General.HTTP] +Addr = ":8070" +ShutdownTimeout = 10 +ReadTimeout = 60 +WriteTimeout = 60 +IdleTimeout = 10 +CertFile = "" +KeyFile = "" + +[General.Root] # Super Administrator Account +ID = "root" +Username = "admin" +Password = "6351623c8cef86fefabfa7da046fc619" # MD5("abc-123") +Name = "Admin" + +[Storage] + +[Storage.Cache] +Type = "redis" # memory/badger/redis +Delimiter = ":" + +[Storage.Cache.Memory] +CleanupInterval = 60 + +[Storage.Cache.Badger] +Path = "data/cache" +[Storage.Cache.Redis] +Addr = "115.239.217.220:6379" +Username = "" +Password = "123456" +DB = 0 +[Storage.DB] +Debug = true +Type = "mysql" # sqlite3/mysql/postgres +# SQLite3 DSN +#DSN = "data/haibei.db" +# MySQL DSN +DSN = "haibei:haibei@tcp(115.239.217.220:3306)/haibei?charset=utf8mb4&parseTime=True&loc=Local" +# PostgreSQL DSN +# DSN = "host=db user=postgres password=123456 dbname=haibei port=5432 sslmode=disable TimeZone=Asia/Shanghai" +MaxLifetime = 86400 +MaxIdleTime = 3600 +MaxOpenConns = 100 +MaxIdleConns = 50 +TablePrefix = "" +AutoMigrate = true +[Util] +[Util.Captcha] +Length = 4 +Width = 400 +Height = 160 +CacheType = "memory" # memory/redis + +[Util.Captcha.Redis] +Addr = "115.239.217.220:6379" # If empty, then use the same configuration as Storage.Cache.Redis +Username = "" +Password = "123456" +DB = 1 +KeyPrefix = "captcha:" + +[Util.Prometheus] +Enable = false +Port = 9100 +BasicUsername = "admin" +BasicPassword = "admin" +LogApis = [] # Log APIs, e.g. ["/api/v1/users"] +LogMethods = [] # Log HTTP methods, e.g. ["GET"] +DefaultCollect = true + +[Dictionary] +UserCacheExp = 4 # hours +[FileConfig] +UploadDir = "./uploads" +StaticPrefix = "/static" diff --git a/configs/menu.json b/configs/menu.json new file mode 100644 index 0000000..1e458c2 --- /dev/null +++ b/configs/menu.json @@ -0,0 +1,240 @@ +[ + { + "code": "home", + "name": "Home", + "sequence": 90, + "type": "page", + "path": "/home", + "status": "enabled" + }, + { + "code": "system", + "name": "System", + "sequence": 10, + "type": "page", + "path": "/system", + "status": "enabled", + "children": [ + { + "code": "menu", + "name": "Menu", + "sequence": 90, + "type": "page", + "path": "/system/menu", + "status": "enabled", + "children": [ + { + "code": "add", + "name": "Add", + "sequence": 9, + "type": "button", + "status": "enabled", + "resources": [ + { + "method": "POST", + "path": "/api/v1/menus" + } + ] + }, + { + "code": "edit", + "name": "Edit", + "sequence": 8, + "type": "button", + "status": "enabled", + "resources": [ + { + "method": "PUT", + "path": "/api/v1/menus/{id}" + } + ] + }, + { + "code": "delete", + "name": "Delete", + "sequence": 7, + "type": "button", + "status": "enabled", + "resources": [ + { + "method": "DELETE", + "path": "/api/v1/menus/{id}" + } + ] + }, + { + "code": "search", + "name": "Search", + "sequence": 6, + "type": "button", + "status": "enabled" + } + ], + "resources": [ + { + "method": "GET", + "path": "/api/v1/menus" + }, + { + "method": "GET", + "path": "/api/v1/menus/{id}" + } + ] + }, + { + "code": "role", + "name": "Role", + "sequence": 80, + "type": "page", + "path": "/system/role", + "status": "enabled", + "children": [ + { + "code": "add", + "name": "Add", + "sequence": 9, + "type": "button", + "status": "enabled", + "resources": [ + { + "method": "POST", + "path": "/api/v1/roles" + } + ] + }, + { + "code": "edit", + "name": "Edit", + "sequence": 8, + "type": "button", + "status": "enabled", + "resources": [ + { + "method": "PUT", + "path": "/api/v1/roles/{id}" + } + ] + }, + { + "code": "delete", + "name": "Delete", + "sequence": 7, + "type": "button", + "status": "enabled", + "resources": [ + { + "method": "DELETE", + "path": "/api/v1/roles/{id}" + } + ] + }, + { + "code": "search", + "name": "Search", + "sequence": 6, + "type": "button", + "status": "enabled" + } + ], + "resources": [ + { + "method": "GET", + "path": "/api/v1/menus" + }, + { + "method": "GET", + "path": "/api/v1/roles" + }, + { + "method": "GET", + "path": "/api/v1/roles/{id}" + } + ] + }, + { + "code": "user", + "name": "User", + "sequence": 70, + "type": "page", + "path": "/system/user", + "status": "enabled", + "children": [ + { + "code": "add", + "name": "Add", + "sequence": 9, + "type": "button", + "status": "enabled", + "resources": [ + { + "method": "POST", + "path": "/api/v1/users" + } + ] + }, + { + "code": "edit", + "name": "Edit", + "sequence": 8, + "type": "button", + "status": "enabled", + "resources": [ + { + "method": "PUT", + "path": "/api/v1/users/{id}" + } + ] + }, + { + "code": "delete", + "name": "Delete", + "sequence": 7, + "type": "button", + "status": "enabled", + "resources": [ + { + "method": "DELETE", + "path": "/api/v1/users/{id}" + } + ] + }, + { + "code": "search", + "name": "Search", + "sequence": 6, + "type": "button", + "status": "enabled" + } + ], + "resources": [ + { + "method": "GET", + "path": "/api/v1/roles" + }, + { + "method": "GET", + "path": "/api/v1/users" + }, + { + "method": "GET", + "path": "/api/v1/users/{id}" + } + ] + }, + { + "code": "logger", + "name": "Logger", + "sequence": 10, + "type": "page", + "path": "/system/logger", + "status": "enabled", + "resources": [ + { + "method": "GET", + "path": "/api/v1/loggers" + } + ] + } + ] + } +] \ No newline at end of file diff --git a/configs/menu_cn.json b/configs/menu_cn.json new file mode 100644 index 0000000..2b3fad4 --- /dev/null +++ b/configs/menu_cn.json @@ -0,0 +1,240 @@ +[ + { + "code": "home", + "name": "首页", + "sequence": 90, + "type": "page", + "path": "/home", + "status": "enabled" + }, + { + "code": "system", + "name": "系统管理", + "sequence": 10, + "type": "page", + "path": "/system", + "status": "enabled", + "children": [ + { + "code": "menu", + "name": "菜单管理", + "sequence": 90, + "type": "page", + "path": "/system/menu", + "status": "enabled", + "children": [ + { + "code": "add", + "name": "增加", + "sequence": 9, + "type": "button", + "status": "enabled", + "resources": [ + { + "method": "POST", + "path": "/api/v1/menus" + } + ] + }, + { + "code": "edit", + "name": "编辑", + "sequence": 8, + "type": "button", + "status": "enabled", + "resources": [ + { + "method": "PUT", + "path": "/api/v1/menus/{id}" + } + ] + }, + { + "code": "delete", + "name": "删除", + "sequence": 7, + "type": "button", + "status": "enabled", + "resources": [ + { + "method": "DELETE", + "path": "/api/v1/menus/{id}" + } + ] + }, + { + "code": "search", + "name": "查询", + "sequence": 6, + "type": "button", + "status": "enabled" + } + ], + "resources": [ + { + "method": "GET", + "path": "/api/v1/menus" + }, + { + "method": "GET", + "path": "/api/v1/menus/{id}" + } + ] + }, + { + "code": "role", + "name": "角色管理", + "sequence": 80, + "type": "page", + "path": "/system/role", + "status": "enabled", + "children": [ + { + "code": "add", + "name": "增加", + "sequence": 9, + "type": "button", + "status": "enabled", + "resources": [ + { + "method": "POST", + "path": "/api/v1/roles" + } + ] + }, + { + "code": "edit", + "name": "编辑", + "sequence": 8, + "type": "button", + "status": "enabled", + "resources": [ + { + "method": "PUT", + "path": "/api/v1/roles/{id}" + } + ] + }, + { + "code": "delete", + "name": "删除", + "sequence": 7, + "type": "button", + "status": "enabled", + "resources": [ + { + "method": "DELETE", + "path": "/api/v1/roles/{id}" + } + ] + }, + { + "code": "search", + "name": "查询", + "sequence": 6, + "type": "button", + "status": "enabled" + } + ], + "resources": [ + { + "method": "GET", + "path": "/api/v1/menus" + }, + { + "method": "GET", + "path": "/api/v1/roles" + }, + { + "method": "GET", + "path": "/api/v1/roles/{id}" + } + ] + }, + { + "code": "user", + "name": "用户管理", + "sequence": 70, + "type": "page", + "path": "/system/user", + "status": "enabled", + "children": [ + { + "code": "add", + "name": "增加", + "sequence": 9, + "type": "button", + "status": "enabled", + "resources": [ + { + "method": "POST", + "path": "/api/v1/users" + } + ] + }, + { + "code": "edit", + "name": "编辑", + "sequence": 8, + "type": "button", + "status": "enabled", + "resources": [ + { + "method": "PUT", + "path": "/api/v1/users/{id}" + } + ] + }, + { + "code": "delete", + "name": "删除", + "sequence": 7, + "type": "button", + "status": "enabled", + "resources": [ + { + "method": "DELETE", + "path": "/api/v1/users/{id}" + } + ] + }, + { + "code": "search", + "name": "查询", + "sequence": 6, + "type": "button", + "status": "enabled" + } + ], + "resources": [ + { + "method": "GET", + "path": "/api/v1/roles" + }, + { + "method": "GET", + "path": "/api/v1/users" + }, + { + "method": "GET", + "path": "/api/v1/users/{id}" + } + ] + }, + { + "code": "logger", + "name": "日志查询", + "sequence": 10, + "type": "page", + "path": "/system/logger", + "status": "enabled", + "resources": [ + { + "method": "GET", + "path": "/api/v1/loggers" + } + ] + } + ] + } +] \ No newline at end of file diff --git a/configs/rbac_model.conf b/configs/rbac_model.conf new file mode 100644 index 0000000..6ede185 --- /dev/null +++ b/configs/rbac_model.conf @@ -0,0 +1,14 @@ +[request_definition] +r = sub, obj, act + +[policy_definition] +p = sub, obj, act + +[policy_effect] +e = some(where (p.eft == allow)) # Passes auth if any of the policies allows + +[role_definition] +g = _, _ + +[matchers] +m = g(r.sub, p.sub) && r.sub == p.sub && (keyMatch2(r.obj, p.obj) || keyMatch3(r.obj, p.obj)) && r.act == p.act diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..10ef306 --- /dev/null +++ b/go.mod @@ -0,0 +1,138 @@ +module github.guxuan/haibei + +go 1.19 + +require ( + github.com/BurntSushi/toml v1.2.1 + github.com/LyricTian/captcha v1.2.0 + github.com/aws/aws-sdk-go v1.44.300 + github.com/casbin/casbin/v2 v2.68.0 + github.com/creasty/defaults v1.7.0 + github.com/dgraph-io/badger/v3 v3.2103.5 + github.com/gavv/httpexpect/v2 v2.15.0 + github.com/gin-contrib/cors v1.4.0 + github.com/gin-gonic/gin v1.9.0 + github.com/go-playground/validator/v10 v10.12.0 + github.com/go-redis/redis/v8 v8.11.5 + github.com/go-redis/redis_rate/v9 v9.1.2 + github.com/go-sql-driver/mysql v1.7.0 + github.com/golang-jwt/jwt v3.2.2+incompatible + github.com/google/uuid v1.3.0 + github.com/google/wire v0.5.0 + github.com/json-iterator/go v1.1.12 + github.com/minio/minio-go/v7 v7.0.51 + github.com/patrickmn/go-cache v2.1.0+incompatible + github.com/pelletier/go-toml v1.9.5 + github.com/pkg/errors v0.9.1 + github.com/prometheus/client_golang v1.14.0 + github.com/redis/go-redis/v9 v9.0.4 + github.com/rs/xid v1.4.0 + github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e + github.com/spf13/cast v1.5.1 + github.com/stretchr/testify v1.8.4 + github.com/swaggo/files v1.0.1 + github.com/swaggo/gin-swagger v1.6.0 + github.com/swaggo/swag v1.16.2 + github.com/urfave/cli/v2 v2.25.1 + go.uber.org/zap v1.24.0 + golang.org/x/crypto v0.8.0 + golang.org/x/time v0.3.0 + gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df + gopkg.in/natefinch/lumberjack.v2 v2.2.1 + gopkg.in/yaml.v3 v3.0.1 + gorm.io/driver/mysql v1.4.7 + gorm.io/driver/postgres v1.5.0 + gorm.io/driver/sqlite v1.4.4 + gorm.io/gorm v1.24.7-0.20230306060331-85eaf9eeda11 + gorm.io/plugin/dbresolver v1.4.1 + gorm.io/plugin/soft_delete v1.2.1 +) + +require ( + github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible // indirect + github.com/KyleBanks/depth v1.2.1 // indirect + github.com/ajg/form v1.5.1 // indirect + github.com/andybalholm/brotli v1.0.4 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/bytedance/sonic v1.8.7 // indirect + github.com/cespare/xxhash v1.1.0 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/dgraph-io/ristretto v0.1.1 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/fatih/color v1.13.0 // indirect + github.com/fatih/structs v1.1.0 // indirect + github.com/gin-contrib/sse v0.1.0 // indirect + github.com/go-openapi/jsonpointer v0.19.6 // indirect + github.com/go-openapi/jsonreference v0.20.2 // indirect + github.com/go-openapi/spec v0.20.8 // indirect + github.com/go-openapi/swag v0.22.3 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/gobwas/glob v0.2.3 // indirect + github.com/goccy/go-json v0.10.2 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/glog v1.1.1 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/golang/snappy v0.0.4 // indirect + github.com/google/flatbuffers v23.3.3+incompatible // indirect + github.com/google/go-querystring v1.1.0 // indirect + github.com/gorilla/websocket v1.4.2 // indirect + github.com/imkira/go-interpol v1.1.0 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect + github.com/jackc/pgx/v5 v5.3.1 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect + github.com/jmespath/go-jmespath v0.4.0 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/klauspost/compress v1.16.4 // indirect + github.com/klauspost/cpuid/v2 v2.2.4 // indirect + github.com/leodido/go-urn v1.2.3 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.18 // indirect + github.com/mattn/go-sqlite3 v1.14.16 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect + github.com/minio/md5-simd v1.1.2 // indirect + github.com/minio/sha256-simd v1.0.0 // indirect + github.com/mitchellh/go-wordwrap v1.0.1 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pelletier/go-toml/v2 v2.0.7 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/prometheus/client_model v0.3.0 // indirect + github.com/prometheus/common v0.42.0 // indirect + github.com/prometheus/procfs v0.9.0 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/sanity-io/litter v1.5.5 // indirect + github.com/sergi/go-diff v1.0.0 // indirect + github.com/sirupsen/logrus v1.9.0 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.2.11 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasthttp v1.34.0 // indirect + github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect + github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect + github.com/xeipuuv/gojsonschema v1.2.0 // indirect + github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect + github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0 // indirect + github.com/yudai/gojsondiff v1.0.0 // indirect + github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect + go.opencensus.io v0.24.0 // indirect + go.uber.org/atomic v1.10.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + golang.org/x/arch v0.3.0 // indirect + golang.org/x/net v0.9.0 // indirect + golang.org/x/sys v0.7.0 // indirect + golang.org/x/text v0.9.0 // indirect + golang.org/x/tools v0.8.0 // indirect + google.golang.org/protobuf v1.30.0 // indirect + gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect + gopkg.in/ini.v1 v1.67.0 // indirect + moul.io/http2curl/v2 v2.3.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..41e47dc --- /dev/null +++ b/go.sum @@ -0,0 +1,603 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak= +github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible h1:1G1pk05UrOh0NlF1oeaaix1x8XzrfjIDK47TY0Zehcw= +github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= +github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= +github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= +github.com/LyricTian/captcha v1.2.0 h1:SXmXj9B1KHBTsVv9rXVVTsKdgNlHrSeb4Memlw+wks4= +github.com/LyricTian/captcha v1.2.0/go.mod h1:tpNDvMWf9XBnkfPB2Tyx7lxDWTgfZ9wA4o7Yl50FWs4= +github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU= +github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= +github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY= +github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/aws/aws-sdk-go v1.44.300 h1:Zn+3lqgYahIf9yfrwZ+g+hq/c3KzUBaQ8wqY/ZXiAbY= +github.com/aws/aws-sdk-go v1.44.300/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bsm/ginkgo/v2 v2.7.0 h1:ItPMPH90RbmZJt5GtkcNvIRuGEdwlBItdNVoyzaNQao= +github.com/bsm/gomega v1.26.0 h1:LhQm+AFcgV2M0WyKroMASzAzCAJVpAxQXv4SaI9a69Y= +github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= +github.com/bytedance/sonic v1.8.7 h1:d3sry5vGgVq/OpgozRUNP6xBsSo0mtNdwliApw+SAMQ= +github.com/bytedance/sonic v1.8.7/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= +github.com/casbin/casbin/v2 v2.68.0 h1:7L4kwNJJw/pzdSEhl4SkeHz+1JzYn8guO+Q422sxzLM= +github.com/casbin/casbin/v2 v2.68.0/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +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/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= +github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= +github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/creasty/defaults v1.7.0 h1:eNdqZvc5B509z18lD8yc212CAqJNvfT1Jq6L8WowdBA= +github.com/creasty/defaults v1.7.0/go.mod h1:iGzKe6pbEHnpMPtfDXZEr0NVxWnPTjb1bbDy08fPzYM= +github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgraph-io/badger/v3 v3.2103.5 h1:ylPa6qzbjYRQMU6jokoj4wzcaweHylt//CH0AKt0akg= +github.com/dgraph-io/badger/v3 v3.2103.5/go.mod h1:4MPiseMeDQ3FNCYwRbbcBOGJLf5jsE0PPFzRiKjtcdw= +github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8= +github.com/dgraph-io/ristretto v0.1.1/go.mod h1:S1GPSBCYCIhmVNfcth17y2zZtQT6wzkzgwUve0VDWWA= +github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA= +github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +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/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= +github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= +github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/gavv/httpexpect/v2 v2.15.0 h1:CCnFk9of4l4ijUhnMxyoEpJsIIBKcuWIFLMwwGTZxNs= +github.com/gavv/httpexpect/v2 v2.15.0/go.mod h1:7myOP3A3VyS4+qnA4cm8DAad8zMN+7zxDB80W9f8yIc= +github.com/gin-contrib/cors v1.4.0 h1:oJ6gwtUl3lqV0WEIwM/LxPF1QZ5qe2lGWdY2+bz7y0g= +github.com/gin-contrib/cors v1.4.0/go.mod h1:bs9pNM0x/UsmHPBWT2xZz9ROh8xYjYkiURUfmBoMlcs= +github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4= +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.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk= +github.com/gin-gonic/gin v1.9.0 h1:OjyFBKICoexlu99ctXNR2gg+c5pKrKMuyjgARg9qeY8= +github.com/gin-gonic/gin v1.9.0/go.mod h1:W1Me9+hsUSyj3CePGrd1/QrKJMSJ1Tu/0hFEH89961k= +github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= +github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= +github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo= +github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= +github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= +github.com/go-openapi/spec v0.20.8 h1:ubHmXNY3FCIOinT8RNrrPfGc9t7I1qhPtdOGoG2AxRU= +github.com/go-openapi/spec v0.20.8/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= +github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= +github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= +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.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= +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.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= +github.com/go-playground/validator/v10 v10.12.0 h1:E4gtWgxWxp8YSxExrQFv5BpCahla0PVF2oTTEYaWQGI= +github.com/go-playground/validator/v10 v10.12.0/go.mod h1:hCAPuzYvKdP33pxWa+2+6AIKXEKqjIUyqsNCtbsSJrA= +github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= +github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= +github.com/go-redis/redis_rate/v9 v9.1.2 h1:H0l5VzoAtOE6ydd38j8MCq3ABlGLnvvbA1xDSVVCHgQ= +github.com/go-redis/redis_rate/v9 v9.1.2/go.mod h1:oam2de2apSgRG8aJzwJddXbNu91Iyz1m8IKJE2vpvlQ= +github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc= +github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= +github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= +github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= +github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= +github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/glog v1.1.1 h1:jxpi2eWoU84wbX9iIEyAeeoac3FLuifZpY9tcNUD9kw= +github.com/golang/glog v1.1.1/go.mod h1:zR+okUeTbrL6EL3xHUDxZuEtGv04p5shwip1+mL/rLQ= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/flatbuffers v1.12.1/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= +github.com/google/flatbuffers v23.3.3+incompatible h1:5PJI/WbJkaMTvpGxsHVKG/LurN/KnWXNyGpwSCDgen0= +github.com/google/flatbuffers v23.3.3+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= +github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/subcommands v1.0.1/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/wire v0.5.0 h1:I7ELFeVBr3yfPIcc8+MWvrjk+3VjbcSzoXm3JVa+jD8= +github.com/google/wire v0.5.0/go.mod h1:ngWDr9Qvq3yZA10YrxfyGELY/AFWGVpy9c1LTRi1EoU= +github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/imkira/go-interpol v1.1.0 h1:KIiKr0VSG2CUW1hl1jpiyuzuJeKUUpC8iM1AIE7N1Vk= +github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +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-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.3.0/go.mod h1:t3JDKnCBlYIc0ewLF0Q7B8MXmoIaBOZj/ic7iHozM/8= +github.com/jackc/pgx/v5 v5.3.1 h1:Fcr8QJ1ZeLi5zsPZqQeUZhNhxfkkKBOgJuYkJHoBOtU= +github.com/jackc/pgx/v5 v5.3.1/go.mod h1:t3JDKnCBlYIc0ewLF0Q7B8MXmoIaBOZj/ic7iHozM/8= +github.com/jackc/puddle/v2 v2.2.0/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.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +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 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +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/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= +github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= +github.com/klauspost/compress v1.16.4 h1:91KN02FnsOYhuunwU4ssRe8lc2JosWmizWa91B5v1PU= +github.com/klauspost/compress v1.16.4/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= +github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +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.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= +github.com/leodido/go-urn v1.2.3 h1:6BE2vPT0lqoz3fmOesHZiaiFh7889ssCo2GMvLCfiuA= +github.com/leodido/go-urn v1.2.3/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98= +github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-sqlite3 v1.14.3/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI= +github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= +github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y= +github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= +github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= +github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34= +github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM= +github.com/minio/minio-go/v7 v7.0.51 h1:eSewrwc23TqUDEH8aw8Bwp4f+JDdozRrPWcKR7DZhmY= +github.com/minio/minio-go/v7 v7.0.51/go.mod h1:IbbodHyjUAguneyucUaahv+VMNs/EOTV9du7A7/Z3HU= +github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g= +github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= +github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +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/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= +github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= +github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= +github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= +github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= +github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= +github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo= +github.com/pelletier/go-toml/v2 v2.0.7 h1:muncTPStnKRos5dpVKULv2FVd4bMOhNePj9CjgDb8Us= +github.com/pelletier/go-toml/v2 v2.0.7/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= +github.com/pkg/diff v0.0.0-20200914180035-5b29258ca4f7/go.mod h1:zO8QMzTeZd5cpnIkz/Gn6iK0jDfGicM1nynOkkPIl28= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw= +github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= +github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= +github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM= +github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc= +github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI= +github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY= +github.com/redis/go-redis/v9 v9.0.4 h1:FC82T+CHJ/Q/PdyLW++GeCO+Ol59Y4T7R4jbgjvktgc= +github.com/redis/go-redis/v9 v9.0.4/go.mod h1:WqMKv5vnQbRuZstUwxQI195wHy+t4PuXDOjzMvcuQHk= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rs/xid v1.4.0 h1:qd7wPTDkN6KQx2VmMBLrpHkiyQwgFXRnkOLacUiaSNY= +github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +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/sanity-io/litter v1.5.5 h1:iE+sBxPBzoK6uaEP5Lt3fHNgpKcHXc/A2HGETy0uJQo= +github.com/sanity-io/litter v1.5.5/go.mod h1:9gzJgR2i4ZpjZHsKvUXIRQVk7P+yM3e+jAF7bU2UI5U= +github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= +github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= +github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0= +github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= +github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA= +github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48= +github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +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/testify v0.0.0-20161117074351-18a02ba4a312/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +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.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE= +github.com/swaggo/files v1.0.1/go.mod h1:0qXmMNH6sXNf+73t65aKeB+ApmgxdnkQzVTAj2uaMUg= +github.com/swaggo/gin-swagger v1.6.0 h1:y8sxvQ3E20/RCyrXeFfg60r6H0Z+SwpTjMYsMm+zy8M= +github.com/swaggo/gin-swagger v1.6.0/go.mod h1:BG00cCEy294xtVpyIAHG6+e2Qzj/xKlRdOqDkvq0uzo= +github.com/swaggo/swag v1.16.2 h1:28Pp+8DkQoV+HLzLx8RGJZXNGKbFqnuvSbAAtoxiY04= +github.com/swaggo/swag v1.16.2/go.mod h1:6YzXnDcpr0767iOejs318CwYkCQqyGer6BizOg03f+E= +github.com/tailscale/depaware v0.0.0-20210622194025-720c4b409502/go.mod h1:p9lPsd+cx33L3H9nNoecRRxPssFKUwwI50I3pZ0yT+8= +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 v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= +github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= +github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +github.com/urfave/cli/v2 v2.25.1 h1:zw8dSP7ghX0Gmm8vugrs6q9Ku0wzweqPyshy+syu9Gw= +github.com/urfave/cli/v2 v2.25.1/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasthttp v1.34.0 h1:d3AAQJ2DRcxJYHm7OXNXtXt2as1vMDfxeIcFvhmGGm4= +github.com/valyala/fasthttp v1.34.0/go.mod h1:epZA5N+7pY6ZaEKRmstzOuYJx9HI8DI1oaCGZpdH4h0= +github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= +github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= +github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= +github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= +github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0 h1:6fRhSjgLCkTD3JnJxvaJ4Sj+TYblw757bqYgZaOq5ZY= +github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI= +github.com/yudai/gojsondiff v1.0.0 h1:27cbfqXLVEJ1o8I6v3y9lg8Ydm53EKqHXAOMxEGlCOA= +github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= +github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M= +github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= +github.com/yudai/pp v2.0.1+incompatible h1:Q4//iY4pNF6yPLZIigmvcl7k/bPgrcTPIFIcmawg5bI= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= +go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= +go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= +golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= +golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ= +golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM= +golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +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.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= +golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= +golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190422233926-fe54fb35175b/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20201211185031-d93e913c1a58/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.8.0 h1:vSDcovVPld282ceKgDimkRSC8kpaH1dgyc9UMzlt84Y= +golang.org/x/tools v0.8.0/go.mod h1:JxBZ99ISMI5ViVkT1tr6tdNmXeTrcpVSD3vZ1RsRdN4= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk= +gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/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/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE= +gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw= +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/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= +gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/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.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/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/driver/mysql v1.4.3/go.mod h1:sSIebwZAVPiT+27jK9HIwvsqOGKx3YMPmrA3mBJR10c= +gorm.io/driver/mysql v1.4.7 h1:rY46lkCspzGHn7+IYsNpSfEv9tA+SU4SkkB+GFX125Y= +gorm.io/driver/mysql v1.4.7/go.mod h1:SxzItlnT1cb6e1e4ZRpgJN2VYtcqJgqnHxWr4wsP8oc= +gorm.io/driver/postgres v1.5.0 h1:u2FXTy14l45qc3UeCJ7QaAXZmZfDDv0YrthvmRq1l0U= +gorm.io/driver/postgres v1.5.0/go.mod h1:FUZXzO+5Uqg5zzwzv4KK49R8lvGIyscBOqYrtI1Ce9A= +gorm.io/driver/sqlite v1.1.3/go.mod h1:AKDgRWk8lcSQSw+9kxCJnX/yySj8G3rdwYlU57cB45c= +gorm.io/driver/sqlite v1.4.4 h1:gIufGoR0dQzjkyqDyYSCvsYR6fba1Gw5YKDqKeChxFc= +gorm.io/driver/sqlite v1.4.4/go.mod h1:0Aq3iPO+v9ZKbcdiz8gLWRw5VOPcBOPUQJFLq5e2ecI= +gorm.io/gorm v1.20.1/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw= +gorm.io/gorm v1.23.0/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk= +gorm.io/gorm v1.23.8/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk= +gorm.io/gorm v1.24.0/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA= +gorm.io/gorm v1.24.3/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA= +gorm.io/gorm v1.24.7-0.20230306060331-85eaf9eeda11 h1:9qNbmu21nNThCNnF5i2R3kw2aL27U8ZwbzccNjOmW0g= +gorm.io/gorm v1.24.7-0.20230306060331-85eaf9eeda11/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= +gorm.io/plugin/dbresolver v1.4.1 h1:Ug4LcoPhrvqq71UhxtF346f+skTYoCa/nEsdjvHwEzk= +gorm.io/plugin/dbresolver v1.4.1/go.mod h1:CTbCtMWhsjXSiJqiW2R8POvJ2cq18RVOl4WGyT5nhNc= +gorm.io/plugin/soft_delete v1.2.1 h1:qx9D/c4Xu6w5KT8LviX8DgLcB9hkKl6JC9f44Tj7cGU= +gorm.io/plugin/soft_delete v1.2.1/go.mod h1:Zv7vQctOJTGOsJ/bWgrN1n3od0GBAZgnLjEx+cApLGk= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +moul.io/http2curl/v2 v2.3.0 h1:9r3JfDzWPcbIklMOs2TnIFzDYvfAZvjeavG6EzP7jYs= +moul.io/http2curl/v2 v2.3.0/go.mod h1:RW4hyBjTWSYDOxapodpNEtX0g5Eb16sxklBqmd2RHcE= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/internal/bootstrap/bootstrap.go b/internal/bootstrap/bootstrap.go new file mode 100644 index 0000000..be7573c --- /dev/null +++ b/internal/bootstrap/bootstrap.go @@ -0,0 +1,106 @@ +package bootstrap + +import ( + "context" + "fmt" + "net/http" + _ "net/http/pprof" //nolint:gosec + "os" + "strings" + + "github.guxuan/haibei/internal/config" + _ "github.guxuan/haibei/internal/swagger" + "github.guxuan/haibei/internal/utility/prom" + "github.guxuan/haibei/internal/wirex" + "github.guxuan/haibei/pkg/logging" + "github.guxuan/haibei/pkg/util" + "go.uber.org/zap" +) + +// RunConfig defines the config for run command. +type RunConfig struct { + WorkDir string // Working directory + Configs string // Directory or files (multiple separated by commas) + StaticDir string // Static files directory +} + +// The Run function initializes and starts a service with configuration and logging, and handles +// cleanup upon exit. +func Run(ctx context.Context, runCfg RunConfig) error { + defer func() { + if err := zap.L().Sync(); err != nil { + fmt.Printf("failed to sync zap logger: %s \n", err.Error()) + } + }() + + // Load configuration. + workDir := runCfg.WorkDir + staticDir := runCfg.StaticDir + config.MustLoad(workDir, strings.Split(runCfg.Configs, ",")...) + config.C.General.WorkDir = workDir + config.C.Middleware.Static.Dir = staticDir + config.C.Print() + config.C.PreLoad() + + // Initialize logger. + cleanLoggerFn, err := logging.InitWithConfig(ctx, &config.C.Logger, initLoggerHook) + if err != nil { + return err + } + ctx = logging.NewTag(ctx, logging.TagKeyMain) + + logging.Context(ctx).Info("starting service ...", + zap.String("version", config.C.General.Version), + zap.Int("pid", os.Getpid()), + zap.String("workdir", workDir), + zap.String("config", runCfg.Configs), + zap.String("static", staticDir), + ) + + // Start pprof server. + if addr := config.C.General.PprofAddr; addr != "" { + logging.Context(ctx).Info("pprof server is listening on " + addr) + go func() { + err := http.ListenAndServe(addr, nil) + if err != nil { + logging.Context(ctx).Error("failed to listen pprof server", zap.Error(err)) + } + }() + } + + // Build injector. + injector, cleanInjectorFn, err := wirex.BuildInjector(ctx) + if err != nil { + return err + } + + if err := injector.M.Init(ctx); err != nil { + return err + } + + // Initialize global prometheus metrics. + prom.Init() + + return util.Run(ctx, func(ctx context.Context) (func(), error) { + cleanHTTPServerFn, err := startHTTPServer(ctx, injector) + if err != nil { + return cleanInjectorFn, err + } + + return func() { + if err := injector.M.Release(ctx); err != nil { + logging.Context(ctx).Error("failed to release injector", zap.Error(err)) + } + + if cleanHTTPServerFn != nil { + cleanHTTPServerFn() + } + if cleanInjectorFn != nil { + cleanInjectorFn() + } + if cleanLoggerFn != nil { + cleanLoggerFn() + } + }, nil + }) +} diff --git a/internal/bootstrap/http.go b/internal/bootstrap/http.go new file mode 100644 index 0000000..9abab85 --- /dev/null +++ b/internal/bootstrap/http.go @@ -0,0 +1,192 @@ +package bootstrap + +import ( + "context" + "crypto/tls" + "fmt" + "net/http" + "path/filepath" + "time" + + "github.com/casbin/casbin/v2" + "github.com/gin-gonic/gin" + swaggerFiles "github.com/swaggo/files" + ginSwagger "github.com/swaggo/gin-swagger" + "github.guxuan/haibei/internal/config" + "github.guxuan/haibei/internal/utility/prom" + "github.guxuan/haibei/internal/wirex" + "github.guxuan/haibei/pkg/errors" + "github.guxuan/haibei/pkg/logging" + "github.guxuan/haibei/pkg/middleware" + "github.guxuan/haibei/pkg/util" + "go.uber.org/zap" +) + +func startHTTPServer(ctx context.Context, injector *wirex.Injector) (func(), error) { + if config.C.IsDebug() { + gin.SetMode(gin.DebugMode) + } else { + gin.SetMode(gin.ReleaseMode) + } + + e := gin.New() + e.GET("/health", func(c *gin.Context) { + util.ResOK(c) + }) + e.Use(middleware.RecoveryWithConfig(middleware.RecoveryConfig{ + Skip: config.C.Middleware.Recovery.Skip, + })) + e.NoMethod(func(c *gin.Context) { + util.ResError(c, errors.MethodNotAllowed("", "Method Not Allowed")) + }) + e.NoRoute(func(c *gin.Context) { + util.ResError(c, errors.NotFound("", "Not Found")) + }) + + allowedPrefixes := injector.M.RouterPrefixes() + + // Register middlewares + if err := useHTTPMiddlewares(ctx, e, injector, allowedPrefixes); err != nil { + return nil, err + } + + // Register routers + if err := injector.M.RegisterRouters(ctx, e); err != nil { + return nil, err + } + + // Register swagger + if !config.C.General.DisableSwagger { + e.StaticFile("/openapi.json", filepath.Join(config.C.General.WorkDir, "openapi.json")) + e.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) + } + + if dir := config.C.Middleware.Static.Dir; dir != "" { + e.Use(middleware.StaticWithConfig(middleware.StaticConfig{ + Root: dir, + SkippedPathPrefixes: allowedPrefixes, + })) + } + + addr := config.C.General.HTTP.Addr + logging.Context(ctx).Info(fmt.Sprintf("HTTP server is listening on %s", addr)) + srv := &http.Server{ + Addr: addr, + Handler: e, + ReadTimeout: time.Second * time.Duration(config.C.General.HTTP.ReadTimeout), + WriteTimeout: time.Second * time.Duration(config.C.General.HTTP.WriteTimeout), + IdleTimeout: time.Second * time.Duration(config.C.General.HTTP.IdleTimeout), + } + + go func() { + var err error + if config.C.General.HTTP.CertFile != "" && config.C.General.HTTP.KeyFile != "" { + srv.TLSConfig = &tls.Config{MinVersion: tls.VersionTLS12} + err = srv.ListenAndServeTLS(config.C.General.HTTP.CertFile, config.C.General.HTTP.KeyFile) + } else { + err = srv.ListenAndServe() + } + + if err != nil && err != http.ErrServerClosed { + logging.Context(ctx).Error("Failed to listen http server", zap.Error(err)) + } + }() + + return func() { + ctx, cancel := context.WithTimeout(ctx, time.Second*time.Duration(config.C.General.HTTP.ShutdownTimeout)) + defer cancel() + + srv.SetKeepAlivesEnabled(false) + if err := srv.Shutdown(ctx); err != nil { + logging.Context(ctx).Error("Failed to shutdown http server", zap.Error(err)) + } + }, nil +} + +func useHTTPMiddlewares(_ context.Context, e *gin.Engine, injector *wirex.Injector, allowedPrefixes []string) error { + e.Use(middleware.CORSWithConfig(middleware.CORSConfig{ + Enable: config.C.Middleware.CORS.Enable, + AllowAllOrigins: config.C.Middleware.CORS.AllowAllOrigins, + AllowOrigins: config.C.Middleware.CORS.AllowOrigins, + AllowMethods: config.C.Middleware.CORS.AllowMethods, + AllowHeaders: config.C.Middleware.CORS.AllowHeaders, + AllowCredentials: config.C.Middleware.CORS.AllowCredentials, + ExposeHeaders: config.C.Middleware.CORS.ExposeHeaders, + MaxAge: config.C.Middleware.CORS.MaxAge, + AllowWildcard: config.C.Middleware.CORS.AllowWildcard, + AllowBrowserExtensions: config.C.Middleware.CORS.AllowBrowserExtensions, + AllowWebSockets: config.C.Middleware.CORS.AllowWebSockets, + AllowFiles: config.C.Middleware.CORS.AllowFiles, + })) + + e.Use(middleware.TraceWithConfig(middleware.TraceConfig{ + AllowedPathPrefixes: allowedPrefixes, + SkippedPathPrefixes: config.C.Middleware.Trace.SkippedPathPrefixes, + RequestHeaderKey: config.C.Middleware.Trace.RequestHeaderKey, + ResponseTraceKey: config.C.Middleware.Trace.ResponseTraceKey, + })) + + e.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{ + AllowedPathPrefixes: allowedPrefixes, + SkippedPathPrefixes: config.C.Middleware.Logger.SkippedPathPrefixes, + MaxOutputRequestBodyLen: config.C.Middleware.Logger.MaxOutputRequestBodyLen, + MaxOutputResponseBodyLen: config.C.Middleware.Logger.MaxOutputResponseBodyLen, + })) + + e.Use(middleware.CopyBodyWithConfig(middleware.CopyBodyConfig{ + AllowedPathPrefixes: allowedPrefixes, + SkippedPathPrefixes: config.C.Middleware.CopyBody.SkippedPathPrefixes, + MaxContentLen: config.C.Middleware.CopyBody.MaxContentLen, + })) + + e.Use(middleware.AuthWithConfig(middleware.AuthConfig{ + AllowedPathPrefixes: allowedPrefixes, + SkippedPathPrefixes: config.C.Middleware.Auth.SkippedPathPrefixes, + ParseUserID: injector.M.RBAC.LoginAPI.LoginBIZ.ParseUserID, + RootID: config.C.General.Root.ID, + })) + + e.Use(middleware.RateLimiterWithConfig(middleware.RateLimiterConfig{ + Enable: config.C.Middleware.RateLimiter.Enable, + AllowedPathPrefixes: allowedPrefixes, + SkippedPathPrefixes: config.C.Middleware.RateLimiter.SkippedPathPrefixes, + Period: config.C.Middleware.RateLimiter.Period, + MaxRequestsPerIP: config.C.Middleware.RateLimiter.MaxRequestsPerIP, + MaxRequestsPerUser: config.C.Middleware.RateLimiter.MaxRequestsPerUser, + StoreType: config.C.Middleware.RateLimiter.Store.Type, + MemoryStoreConfig: middleware.RateLimiterMemoryConfig{ + Expiration: time.Second * time.Duration(config.C.Middleware.RateLimiter.Store.Memory.Expiration), + CleanupInterval: time.Second * time.Duration(config.C.Middleware.RateLimiter.Store.Memory.CleanupInterval), + }, + RedisStoreConfig: middleware.RateLimiterRedisConfig{ + Addr: config.C.Middleware.RateLimiter.Store.Redis.Addr, + Password: config.C.Middleware.RateLimiter.Store.Redis.Password, + DB: config.C.Middleware.RateLimiter.Store.Redis.DB, + Username: config.C.Middleware.RateLimiter.Store.Redis.Username, + }, + })) + + e.Use(middleware.CasbinWithConfig(middleware.CasbinConfig{ + AllowedPathPrefixes: allowedPrefixes, + SkippedPathPrefixes: config.C.Middleware.Casbin.SkippedPathPrefixes, + Skipper: func(c *gin.Context) bool { + if config.C.Middleware.Casbin.Disable || + util.FromIsRootUser(c.Request.Context()) { + return true + } + return false + }, + GetEnforcer: func(c *gin.Context) *casbin.Enforcer { + return injector.M.RBAC.Casbinx.GetEnforcer() + }, + GetSubjects: func(c *gin.Context) []string { + return util.FromUserCache(c.Request.Context()).RoleIDs + }, + })) + + if config.C.Util.Prometheus.Enable { + e.Use(prom.GinMiddleware) + } + + return nil +} diff --git a/internal/bootstrap/logger.go b/internal/bootstrap/logger.go new file mode 100644 index 0000000..c995dc8 --- /dev/null +++ b/internal/bootstrap/logger.go @@ -0,0 +1,43 @@ +package bootstrap + +import ( + "context" + + "github.com/spf13/cast" + "github.guxuan/haibei/internal/config" + "github.guxuan/haibei/pkg/gormx" + "github.guxuan/haibei/pkg/logging" +) + +func initLoggerHook(_ context.Context, cfg *logging.HookConfig) (*logging.Hook, error) { + extra := cfg.Extra + if extra == nil { + extra = make(map[string]string) + } + extra["appname"] = config.C.General.AppName + + switch cfg.Type { + case "gorm": + db, err := gormx.New(gormx.Config{ + Debug: cast.ToBool(cfg.Options["Debug"]), + DBType: cast.ToString(cfg.Options["DBType"]), + DSN: cast.ToString(cfg.Options["DSN"]), + MaxLifetime: cast.ToInt(cfg.Options["MaxLifetime"]), + MaxIdleTime: cast.ToInt(cfg.Options["MaxIdleTime"]), + MaxOpenConns: cast.ToInt(cfg.Options["MaxOpenConns"]), + MaxIdleConns: cast.ToInt(cfg.Options["MaxIdleConns"]), + TablePrefix: config.C.Storage.DB.TablePrefix, + }) + if err != nil { + return nil, err + } + + hook := logging.NewHook(logging.NewGormHook(db), + logging.SetHookExtra(cfg.Extra), + logging.SetHookMaxJobs(cfg.MaxBuffer), + logging.SetHookMaxWorkers(cfg.MaxThread)) + return hook, nil + default: + return nil, nil + } +} diff --git a/internal/config/config.go b/internal/config/config.go new file mode 100644 index 0000000..2fb45fe --- /dev/null +++ b/internal/config/config.go @@ -0,0 +1,166 @@ +package config + +import ( + "fmt" + + "github.guxuan/haibei/pkg/encoding/json" + "github.guxuan/haibei/pkg/logging" +) + +type Config struct { + Logger logging.LoggerConfig + General General + Storage Storage + Middleware Middleware + Util Util + Dictionary Dictionary + FileConfig FileConfig +} +type FileConfig struct { + UploadDir string + StaticPrefix string +} + +type General struct { + AppName string `default:"haibei"` + Version string `default:"v1.0.0"` + Debug bool + PprofAddr string + DisableSwagger bool + DisablePrintConfig bool + DefaultLoginPwd string `default:"6351623c8cef86fefabfa7da046fc619"` // MD5(abc-123) + WorkDir string // From command arguments + MenuFile string // From schema.Menus (JSON/YAML) + DenyOperateMenu bool + HTTP struct { + Addr string `default:":8040"` + ShutdownTimeout int `default:"10"` // seconds + ReadTimeout int `default:"60"` // seconds + WriteTimeout int `default:"60"` // seconds + IdleTimeout int `default:"10"` // seconds + CertFile string + KeyFile string + } + Root struct { + ID string `default:"root"` + Username string `default:"admin"` + Password string + Name string `default:"Admin"` + } +} + +type Storage struct { + Cache struct { + Type string `default:"memory"` // memory/badger/redis + Delimiter string `default:":"` // delimiter for key + Memory struct { + CleanupInterval int `default:"60"` // seconds + } + Badger struct { + Path string `default:"data/cache"` + } + Redis struct { + Addr string + Username string + Password string + DB int + } + } + DB struct { + Debug bool + Type string `default:"sqlite3"` // sqlite3/mysql/postgres + DSN string `default:"data/haibei.db"` // database source name + MaxLifetime int `default:"86400"` // seconds + MaxIdleTime int `default:"3600"` // seconds + MaxOpenConns int `default:"100"` // connections + MaxIdleConns int `default:"50"` // connections + TablePrefix string `default:""` + AutoMigrate bool + PrepareStmt bool + Resolver []struct { + DBType string // sqlite3/mysql/postgres + Sources []string // DSN + Replicas []string // DSN + Tables []string + } + } +} + +type Util struct { + Captcha struct { + Length int `default:"4"` + Width int `default:"400"` + Height int `default:"160"` + CacheType string `default:"memory"` // memory/redis + Redis struct { + Addr string + Username string + Password string + DB int + KeyPrefix string `default:"captcha:"` + } + } + Prometheus struct { + Enable bool + Port int `default:"9100"` + BasicUsername string `default:"admin"` + BasicPassword string `default:"admin"` + LogApis []string + LogMethods []string + DefaultCollect bool + } +} + +type Dictionary struct { + UserCacheExp int `default:"4"` // hours +} + +func (c *Config) IsDebug() bool { + return c.General.Debug +} + +func (c *Config) String() string { + b, err := json.MarshalIndent(c, "", " ") + if err != nil { + panic("Failed to marshal config: " + err.Error()) + } + return string(b) +} + +func (c *Config) PreLoad() { + if addr := c.Storage.Cache.Redis.Addr; addr != "" { + username := c.Storage.Cache.Redis.Username + password := c.Storage.Cache.Redis.Password + if c.Util.Captcha.CacheType == "redis" && + c.Util.Captcha.Redis.Addr == "" { + c.Util.Captcha.Redis.Addr = addr + c.Util.Captcha.Redis.Username = username + c.Util.Captcha.Redis.Password = password + } + if c.Middleware.RateLimiter.Store.Type == "redis" && + c.Middleware.RateLimiter.Store.Redis.Addr == "" { + c.Middleware.RateLimiter.Store.Redis.Addr = addr + c.Middleware.RateLimiter.Store.Redis.Username = username + c.Middleware.RateLimiter.Store.Redis.Password = password + } + if c.Middleware.Auth.Store.Type == "redis" && + c.Middleware.Auth.Store.Redis.Addr == "" { + c.Middleware.Auth.Store.Redis.Addr = addr + c.Middleware.Auth.Store.Redis.Username = username + c.Middleware.Auth.Store.Redis.Password = password + } + } +} + +func (c *Config) Print() { + if c.General.DisablePrintConfig { + return + } + fmt.Println("// ----------------------- Load configurations start ------------------------") + fmt.Println(c.String()) + fmt.Println("// ----------------------- Load configurations end --------------------------") +} + +func (c *Config) FormatTableName(name string) string { + return c.Storage.DB.TablePrefix + name +} diff --git a/internal/config/consts.go b/internal/config/consts.go new file mode 100644 index 0000000..b028ef4 --- /dev/null +++ b/internal/config/consts.go @@ -0,0 +1,18 @@ +package config + +const ( + CacheNSForUser = "user" + CacheNSForCustomer = "customer" + + CacheNSForRole = "role" +) + +const ( + CacheKeyForSyncToCasbin = "sync:casbin" +) + +const ( + ErrInvalidTokenID = "com.invalid.token" + ErrInvalidCaptchaID = "com.invalid.captcha" + ErrInvalidUsernameOrPassword = "com.invalid.username-or-password" +) diff --git a/internal/config/middleware.go b/internal/config/middleware.go new file mode 100644 index 0000000..6bdcb47 --- /dev/null +++ b/internal/config/middleware.go @@ -0,0 +1,90 @@ +package config + +type Middleware struct { + Recovery struct { + Skip int `default:"3"` // skip the first n stack frames + } + CORS struct { + Enable bool + AllowAllOrigins bool + AllowOrigins []string + AllowMethods []string + AllowHeaders []string + AllowCredentials bool + ExposeHeaders []string + MaxAge int + AllowWildcard bool + AllowBrowserExtensions bool + AllowWebSockets bool + AllowFiles bool + } + Trace struct { + SkippedPathPrefixes []string + RequestHeaderKey string `default:"X-Request-Id"` + ResponseTraceKey string `default:"X-Trace-Id"` + } + Logger struct { + SkippedPathPrefixes []string + MaxOutputRequestBodyLen int `default:"4096"` + MaxOutputResponseBodyLen int `default:"1024"` + } + CopyBody struct { + SkippedPathPrefixes []string + MaxContentLen int64 `default:"33554432"` // max content length (default 32MB) + } + Auth struct { + Disable bool + SkippedPathPrefixes []string + SigningMethod string `default:"HS512"` // HS256/HS384/HS512 + SigningKey string `default:"XnEsT0S@"` // secret key + OldSigningKey string // old secret key (for migration) + Expired int `default:"86400"` // seconds + Store struct { + Type string `default:"memory"` // memory/badger/redis + Delimiter string `default:":"` // delimiter for key + Memory struct { + CleanupInterval int `default:"60"` // seconds + } + Badger struct { + Path string `default:"data/auth"` + } + Redis struct { + Addr string + Username string + Password string + DB int + } + } + } + RateLimiter struct { + Enable bool + SkippedPathPrefixes []string + Period int // seconds + MaxRequestsPerIP int + MaxRequestsPerUser int + Store struct { + Type string // memory/redis + Memory struct { + Expiration int `default:"3600"` // seconds + CleanupInterval int `default:"60"` // seconds + } + Redis struct { + Addr string + Username string + Password string + DB int + } + } + } + Casbin struct { + Disable bool + SkippedPathPrefixes []string + LoadThread int `default:"2"` + AutoLoadInterval int `default:"3"` // seconds + ModelFile string `default:"rbac_model.conf"` + GenPolicyFile string `default:"gen_rbac_policy.csv"` + } + Static struct { + Dir string // Static files directory (From command arguments) + } +} diff --git a/internal/config/parse.go b/internal/config/parse.go new file mode 100644 index 0000000..a17a3c6 --- /dev/null +++ b/internal/config/parse.go @@ -0,0 +1,84 @@ +package config + +import ( + "os" + "path/filepath" + "strings" + "sync" + + "github.com/creasty/defaults" + "github.guxuan/haibei/pkg/encoding/json" + "github.guxuan/haibei/pkg/encoding/toml" + "github.guxuan/haibei/pkg/errors" +) + +var ( + once sync.Once + C = new(Config) +) + +func MustLoad(dir string, names ...string) { + once.Do(func() { + if err := Load(dir, names...); err != nil { + panic(err) + } + }) +} + +// Loads configuration files in various formats from a directory and parses them into +// a struct. +func Load(dir string, names ...string) error { + // Set default values + if err := defaults.Set(C); err != nil { + return err + } + + supportExts := []string{".json", ".toml"} + parseFile := func(name string) error { + ext := filepath.Ext(name) + if ext == "" || !strings.Contains(strings.Join(supportExts, ","), ext) { + return nil + } + + buf, err := os.ReadFile(name) + if err != nil { + return errors.Wrapf(err, "failed to read config file %s", name) + } + + switch ext { + case ".json": + err = json.Unmarshal(buf, C) + case ".toml": + err = toml.Unmarshal(buf, C) + } + return errors.Wrapf(err, "failed to unmarshal config %s", name) + } + + for _, name := range names { + fullname := filepath.Join(dir, name) + info, err := os.Stat(fullname) + if err != nil { + return errors.Wrapf(err, "failed to get config file %s", name) + } + + if info.IsDir() { + err := filepath.WalkDir(fullname, func(path string, d os.DirEntry, err error) error { + if err != nil { + return err + } else if d.IsDir() { + return nil + } + return parseFile(path) + }) + if err != nil { + return errors.Wrapf(err, "failed to walk config dir %s", name) + } + continue + } + if err := parseFile(fullname); err != nil { + return err + } + } + + return nil +} diff --git a/internal/mods/activity/api/activity.api.go b/internal/mods/activity/api/activity.api.go new file mode 100644 index 0000000..c6e0640 --- /dev/null +++ b/internal/mods/activity/api/activity.api.go @@ -0,0 +1,145 @@ +package api + +import ( + "github.com/gin-gonic/gin" + "github.guxuan/haibei/internal/mods/activity/biz" + "github.guxuan/haibei/internal/mods/activity/schema" + "github.guxuan/haibei/pkg/util" +) + +// Defining the `Activity` api. +type Activity struct { + ActivityBIZ *biz.Activity +} + +// @Tags 活动模块 +// @Security ApiKeyAuth +// @Summary Query activity list +// @Param current query int true "pagination index" default(1) +// @Param pageSize query int true "pagination size" default(10) +// @Success 200 {object} util.ResponseResult{data=[]schema.Activity} +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/activities [get] +func (a *Activity) Query(c *gin.Context) { + ctx := c.Request.Context() + var params schema.ActivityQueryParam + if err := util.ParseQuery(c, ¶ms); err != nil { + util.ResError(c, err) + return + } + + result, err := a.ActivityBIZ.Query(ctx, params) + if err != nil { + util.ResError(c, err) + return + } + util.ResPage(c, result.Data, result.PageResult) +} + +// @Tags 活动模块 +// @Security ApiKeyAuth +// @Summary Get activity record by ID +// @Param id path string true "unique id" +// @Success 200 {object} util.ResponseResult{data=schema.Activity} +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/activities/{id} [get] +func (a *Activity) Get(c *gin.Context) { + ctx := c.Request.Context() + id, err := util.GetQueryID(c) + if err != nil { + util.ResError(c, err) + return + } + item, err := a.ActivityBIZ.Get(ctx, id) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, item) +} + +// @Tags 活动模块 +// @Security ApiKeyAuth +// @Summary Create activity record +// @Param body body schema.ActivityForm true "Request body" +// @Success 200 {object} util.ResponseResult{data=schema.Activity} +// @Failure 400 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/activities [post] +func (a *Activity) Create(c *gin.Context) { + ctx := c.Request.Context() + item := new(schema.ActivityForm) + if err := util.ParseJSON(c, item); err != nil { + util.ResError(c, err) + return + } else if err := item.Validate(); err != nil { + util.ResError(c, err) + return + } + + result, err := a.ActivityBIZ.Create(ctx, item) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, result) +} + +// @Tags 活动模块 +// @Security ApiKeyAuth +// @Summary Update activity record by ID +// @Param id path string true "unique id" +// @Param body body schema.ActivityForm true "Request body" +// @Success 200 {object} util.ResponseResult +// @Failure 400 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/activities/{id} [put] +func (a *Activity) Update(c *gin.Context) { + ctx := c.Request.Context() + item := new(schema.ActivityForm) + if err := util.ParseJSON(c, item); err != nil { + util.ResError(c, err) + return + } else if err := item.Validate(); err != nil { + util.ResError(c, err) + return + } + id, err := util.GetQueryID(c) + if err != nil { + util.ResError(c, err) + return + } + err = a.ActivityBIZ.Update(ctx, id, item) + if err != nil { + util.ResError(c, err) + return + } + util.ResOK(c) +} + +// @Tags 活动模块 +// @Security ApiKeyAuth +// @Summary Delete activity record by ID +// @Param id path string true "unique id" +// @Success 200 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/activities/{id} [delete] +func (a *Activity) Delete(c *gin.Context) { + ctx := c.Request.Context() + id, err := util.GetQueryID(c) + if err != nil { + util.ResError(c, err) + return + } + err = a.ActivityBIZ.Delete(ctx, id) + if err != nil { + util.ResError(c, err) + return + } + util.ResOK(c) +} diff --git a/internal/mods/activity/api/activity_detail.api.go b/internal/mods/activity/api/activity_detail.api.go new file mode 100644 index 0000000..27dddf5 --- /dev/null +++ b/internal/mods/activity/api/activity_detail.api.go @@ -0,0 +1,145 @@ +package api + +import ( + "github.com/gin-gonic/gin" + "github.guxuan/haibei/internal/mods/activity/biz" + "github.guxuan/haibei/internal/mods/activity/schema" + "github.guxuan/haibei/pkg/util" +) + +// Defining the `ActivityDetail` api. +type ActivityDetail struct { + ActivityDetailBIZ *biz.ActivityDetail +} + +// @Tags ActivityDetailAPI +// @Security ApiKeyAuth +// @Summary Query activity detail list +// @Param current query int true "pagination index" default(1) +// @Param pageSize query int true "pagination size" default(10) +// @Success 200 {object} util.ResponseResult{data=[]schema.ActivityDetail} +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/activity-details [get] +func (a *ActivityDetail) Query(c *gin.Context) { + ctx := c.Request.Context() + var params schema.ActivityDetailQueryParam + if err := util.ParseQuery(c, ¶ms); err != nil { + util.ResError(c, err) + return + } + + result, err := a.ActivityDetailBIZ.Query(ctx, params) + if err != nil { + util.ResError(c, err) + return + } + util.ResPage(c, result.Data, result.PageResult) +} + +// @Tags ActivityDetailAPI +// @Security ApiKeyAuth +// @Summary Get activity detail record by ID +// @Param id path string true "unique id" +// @Success 200 {object} util.ResponseResult{data=schema.ActivityDetail} +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/activity-details/{id} [get] +func (a *ActivityDetail) Get(c *gin.Context) { + ctx := c.Request.Context() + id, err := util.GetQueryID(c) + if err != nil { + util.ResError(c, err) + return + } + item, err := a.ActivityDetailBIZ.Get(ctx, id) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, item) +} + +// @Tags ActivityDetailAPI +// @Security ApiKeyAuth +// @Summary Create activity detail record +// @Param body body schema.ActivityDetailForm true "Request body" +// @Success 200 {object} util.ResponseResult{data=schema.ActivityDetail} +// @Failure 400 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/activity-details [post] +func (a *ActivityDetail) Create(c *gin.Context) { + ctx := c.Request.Context() + item := new(schema.ActivityDetailForm) + if err := util.ParseJSON(c, item); err != nil { + util.ResError(c, err) + return + } else if err := item.Validate(); err != nil { + util.ResError(c, err) + return + } + + result, err := a.ActivityDetailBIZ.Create(ctx, item) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, result) +} + +// @Tags ActivityDetailAPI +// @Security ApiKeyAuth +// @Summary Update activity detail record by ID +// @Param id path string true "unique id" +// @Param body body schema.ActivityDetailForm true "Request body" +// @Success 200 {object} util.ResponseResult +// @Failure 400 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/activity-details/{id} [put] +func (a *ActivityDetail) Update(c *gin.Context) { + ctx := c.Request.Context() + item := new(schema.ActivityDetailForm) + if err := util.ParseJSON(c, item); err != nil { + util.ResError(c, err) + return + } else if err := item.Validate(); err != nil { + util.ResError(c, err) + return + } + id, err := util.GetQueryID(c) + if err != nil { + util.ResError(c, err) + return + } + err = a.ActivityDetailBIZ.Update(ctx, id, item) + if err != nil { + util.ResError(c, err) + return + } + util.ResOK(c) +} + +// @Tags ActivityDetailAPI +// @Security ApiKeyAuth +// @Summary Delete activity detail record by ID +// @Param id path string true "unique id" +// @Success 200 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/activity-details/{id} [delete] +func (a *ActivityDetail) Delete(c *gin.Context) { + ctx := c.Request.Context() + id, err := util.GetQueryID(c) + if err != nil { + util.ResError(c, err) + return + } + err = a.ActivityDetailBIZ.Delete(ctx, id) + if err != nil { + util.ResError(c, err) + return + } + util.ResOK(c) +} diff --git a/internal/mods/activity/api/house.api.go b/internal/mods/activity/api/house.api.go new file mode 100644 index 0000000..9600101 --- /dev/null +++ b/internal/mods/activity/api/house.api.go @@ -0,0 +1,144 @@ +package api + +import ( + "github.com/gin-gonic/gin" + "github.guxuan/haibei/internal/mods/activity/biz" + "github.guxuan/haibei/internal/mods/activity/schema" + "github.guxuan/haibei/pkg/util" +) + +type House struct { + HouseBIZ *biz.House +} + +// @Tags 房源模块 +// @Security ApiKeyAuth +// @Summary Query house list +// @Param current query int true "pagination index" default(1) +// @Param pageSize query int true "pagination size" default(10) +// @Success 200 {object} util.ResponseResult{data=[]schema.House} +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/houses [get] +func (a *House) Query(c *gin.Context) { + ctx := c.Request.Context() + var params schema.HouseQueryParam + if err := util.ParseQuery(c, ¶ms); err != nil { + util.ResError(c, err) + return + } + + result, err := a.HouseBIZ.Query(ctx, params) + if err != nil { + util.ResError(c, err) + return + } + util.ResPage(c, result.Data, result.PageResult) +} + +// @Tags 房源模块 +// @Security ApiKeyAuth +// @Summary Get house record by ID +// @Param id path string true "unique id" +// @Success 200 {object} util.ResponseResult{data=schema.House} +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/houses/{id} [get] +func (a *House) Get(c *gin.Context) { + ctx := c.Request.Context() + id, err := util.GetQueryID(c) + if err != nil { + util.ResError(c, err) + return + } + item, err := a.HouseBIZ.Get(ctx, id) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, item) +} + +// @Tags 房源模块 +// @Security ApiKeyAuth +// @Summary Create house record +// @Param body body schema.HouseForm true "Request body" +// @Success 200 {object} util.ResponseResult{data=schema.House} +// @Failure 400 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/houses [post] +func (a *House) Create(c *gin.Context) { + ctx := c.Request.Context() + item := new(schema.HouseForm) + if err := util.ParseJSON(c, item); err != nil { + util.ResError(c, err) + return + } else if err := item.Validate(); err != nil { + util.ResError(c, err) + return + } + + result, err := a.HouseBIZ.Create(ctx, item) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, result) +} + +// @Tags 房源模块 +// @Security ApiKeyAuth +// @Summary Update house record by ID +// @Param id path string true "unique id" +// @Param body body schema.HouseForm true "Request body" +// @Success 200 {object} util.ResponseResult +// @Failure 400 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/houses/{id} [put] +func (a *House) Update(c *gin.Context) { + ctx := c.Request.Context() + item := new(schema.HouseForm) + if err := util.ParseJSON(c, item); err != nil { + util.ResError(c, err) + return + } else if err := item.Validate(); err != nil { + util.ResError(c, err) + return + } + id, err := util.GetQueryID(c) + if err != nil { + util.ResError(c, err) + return + } + err = a.HouseBIZ.Update(ctx, id, item) + if err != nil { + util.ResError(c, err) + return + } + util.ResOK(c) +} + +// @Tags 房源模块 +// @Security ApiKeyAuth +// @Summary Delete house record by ID +// @Param id path string true "unique id" +// @Success 200 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/houses/{id} [delete] +func (a *House) Delete(c *gin.Context) { + ctx := c.Request.Context() + id, err := util.GetQueryID(c) + if err != nil { + util.ResError(c, err) + return + } + err = a.HouseBIZ.Delete(ctx, id) + if err != nil { + util.ResError(c, err) + return + } + util.ResOK(c) +} diff --git a/internal/mods/activity/api/houseArticle.api.go b/internal/mods/activity/api/houseArticle.api.go new file mode 100644 index 0000000..d7528fd --- /dev/null +++ b/internal/mods/activity/api/houseArticle.api.go @@ -0,0 +1,144 @@ +package api + +import ( + "github.com/gin-gonic/gin" + "github.guxuan/haibei/internal/mods/activity/biz" + "github.guxuan/haibei/internal/mods/activity/schema" + "github.guxuan/haibei/pkg/util" +) + +type HouseArticle struct { + HouseArticleBIZ *biz.HouseArticle +} + +// @Tags 房刊模块 +// @Security ApiKeyAuth +// @Summary Query house list +// @Param current query int true "pagination index" default(1) +// @Param pageSize query int true "pagination size" default(10) +// @Success 200 {object} util.ResponseResult{data=[]schema.HouseArticle} +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/house_articles [get] +func (a *HouseArticle) Query(c *gin.Context) { + ctx := c.Request.Context() + var params schema.HouseArticleQueryParam + if err := util.ParseQuery(c, ¶ms); err != nil { + util.ResError(c, err) + return + } + + result, err := a.HouseArticleBIZ.Query(ctx, params) + if err != nil { + util.ResError(c, err) + return + } + util.ResPage(c, result.Data, result.PageResult) +} + +// @Tags 房刊模块 +// @Security ApiKeyAuth +// @Summary Get house record by ID +// @Param id path string true "unique id" +// @Success 200 {object} util.ResponseResult{data=schema.HouseArticle} +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/house_articles/{id} [get] +func (a *HouseArticle) Get(c *gin.Context) { + ctx := c.Request.Context() + id, err := util.GetQueryID(c) + if err != nil { + util.ResError(c, err) + return + } + item, err := a.HouseArticleBIZ.Get(ctx, id) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, item) +} + +// @Tags 房刊模块 +// @Security ApiKeyAuth +// @Summary Create house record +// @Param body body schema.HouseArticleForm true "Request body" +// @Success 200 {object} util.ResponseResult{data=schema.HouseArticle} +// @Failure 400 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/house_articles [post] +func (a *HouseArticle) Create(c *gin.Context) { + ctx := c.Request.Context() + item := new(schema.HouseArticleForm) + if err := util.ParseJSON(c, item); err != nil { + util.ResError(c, err) + return + } else if err := item.Validate(); err != nil { + util.ResError(c, err) + return + } + + result, err := a.HouseArticleBIZ.Create(ctx, item) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, result) +} + +// @Tags 房刊模块 +// @Security ApiKeyAuth +// @Summary Update house record by ID +// @Param id path string true "unique id" +// @Param body body schema.HouseArticleForm true "Request body" +// @Success 200 {object} util.ResponseResult +// @Failure 400 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/house_articles/{id} [put] +func (a *HouseArticle) Update(c *gin.Context) { + ctx := c.Request.Context() + item := new(schema.HouseArticleForm) + if err := util.ParseJSON(c, item); err != nil { + util.ResError(c, err) + return + } else if err := item.Validate(); err != nil { + util.ResError(c, err) + return + } + id, err := util.GetQueryID(c) + if err != nil { + util.ResError(c, err) + return + } + err = a.HouseArticleBIZ.Update(ctx, id, item) + if err != nil { + util.ResError(c, err) + return + } + util.ResOK(c) +} + +// @Tags 房刊模块 +// @Security ApiKeyAuth +// @Summary Delete house record by ID +// @Param id path string true "unique id" +// @Success 200 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/house_articles/{id} [delete] +func (a *HouseArticle) Delete(c *gin.Context) { + ctx := c.Request.Context() + id, err := util.GetQueryID(c) + if err != nil { + util.ResError(c, err) + return + } + err = a.HouseArticleBIZ.Delete(ctx, id) + if err != nil { + util.ResError(c, err) + return + } + util.ResOK(c) +} diff --git a/internal/mods/activity/api/qeustion_detail.api.go b/internal/mods/activity/api/qeustion_detail.api.go new file mode 100644 index 0000000..add8ab9 --- /dev/null +++ b/internal/mods/activity/api/qeustion_detail.api.go @@ -0,0 +1,145 @@ +package api + +import ( + "github.com/gin-gonic/gin" + "github.guxuan/haibei/internal/mods/activity/biz" + "github.guxuan/haibei/internal/mods/activity/schema" + "github.guxuan/haibei/pkg/util" +) + +// Defining the `QeustionDetail` api. +type QeustionDetail struct { + QeustionDetailBIZ *biz.QeustionDetail +} + +// @Tags QeustionDetailAPI +// @Security ApiKeyAuth +// @Summary Query qeustion detail list +// @Param current query int true "pagination index" default(1) +// @Param pageSize query int true "pagination size" default(10) +// @Success 200 {object} util.ResponseResult{data=[]schema.QeustionDetail} +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/qeustion-details [get] +func (a *QeustionDetail) Query(c *gin.Context) { + ctx := c.Request.Context() + var params schema.QeustionDetailQueryParam + if err := util.ParseQuery(c, ¶ms); err != nil { + util.ResError(c, err) + return + } + + result, err := a.QeustionDetailBIZ.Query(ctx, params) + if err != nil { + util.ResError(c, err) + return + } + util.ResPage(c, result.Data, result.PageResult) +} + +// @Tags QeustionDetailAPI +// @Security ApiKeyAuth +// @Summary Get qeustion detail record by ID +// @Param id path string true "unique id" +// @Success 200 {object} util.ResponseResult{data=schema.QeustionDetail} +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/qeustion-details/{id} [get] +func (a *QeustionDetail) Get(c *gin.Context) { + ctx := c.Request.Context() + id, err := util.GetQueryID(c) + if err != nil { + util.ResError(c, err) + return + } + item, err := a.QeustionDetailBIZ.Get(ctx, id) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, item) +} + +// @Tags QeustionDetailAPI +// @Security ApiKeyAuth +// @Summary Create qeustion detail record +// @Param body body schema.QeustionDetailForm true "Request body" +// @Success 200 {object} util.ResponseResult{data=schema.QeustionDetail} +// @Failure 400 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/qeustion-details [post] +func (a *QeustionDetail) Create(c *gin.Context) { + ctx := c.Request.Context() + item := new(schema.QeustionDetailForm) + if err := util.ParseJSON(c, item); err != nil { + util.ResError(c, err) + return + } else if err := item.Validate(); err != nil { + util.ResError(c, err) + return + } + + result, err := a.QeustionDetailBIZ.Create(ctx, item) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, result) +} + +// @Tags QeustionDetailAPI +// @Security ApiKeyAuth +// @Summary Update qeustion detail record by ID +// @Param id path string true "unique id" +// @Param body body schema.QeustionDetailForm true "Request body" +// @Success 200 {object} util.ResponseResult +// @Failure 400 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/qeustion-details/{id} [put] +func (a *QeustionDetail) Update(c *gin.Context) { + ctx := c.Request.Context() + item := new(schema.QeustionDetailForm) + if err := util.ParseJSON(c, item); err != nil { + util.ResError(c, err) + return + } else if err := item.Validate(); err != nil { + util.ResError(c, err) + return + } + id, err := util.GetQueryID(c) + if err != nil { + util.ResError(c, err) + return + } + err = a.QeustionDetailBIZ.Update(ctx, id, item) + if err != nil { + util.ResError(c, err) + return + } + util.ResOK(c) +} + +// @Tags QeustionDetailAPI +// @Security ApiKeyAuth +// @Summary Delete qeustion detail record by ID +// @Param id path string true "unique id" +// @Success 200 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/qeustion-details/{id} [delete] +func (a *QeustionDetail) Delete(c *gin.Context) { + ctx := c.Request.Context() + id, err := util.GetQueryID(c) + if err != nil { + util.ResError(c, err) + return + } + err = a.QeustionDetailBIZ.Delete(ctx, id) + if err != nil { + util.ResError(c, err) + return + } + util.ResOK(c) +} diff --git a/internal/mods/activity/api/questionnaire.api.go b/internal/mods/activity/api/questionnaire.api.go new file mode 100644 index 0000000..7b9ce7a --- /dev/null +++ b/internal/mods/activity/api/questionnaire.api.go @@ -0,0 +1,145 @@ +package api + +import ( + "github.com/gin-gonic/gin" + "github.guxuan/haibei/internal/mods/activity/biz" + "github.guxuan/haibei/internal/mods/activity/schema" + "github.guxuan/haibei/pkg/util" +) + +// Defining the `Questionnaire` api. +type Questionnaire struct { + QuestionnaireBIZ *biz.Questionnaire +} + +// @Tags 问卷模块 +// @Security ApiKeyAuth +// @Summary Query questionnaire list +// @Param current query int true "pagination index" default(1) +// @Param pageSize query int true "pagination size" default(10) +// @Success 200 {object} util.ResponseResult{data=[]schema.Questionnaire} +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/questionnaires [get] +func (a *Questionnaire) Query(c *gin.Context) { + ctx := c.Request.Context() + var params schema.QuestionnaireQueryParam + if err := util.ParseQuery(c, ¶ms); err != nil { + util.ResError(c, err) + return + } + + result, err := a.QuestionnaireBIZ.Query(ctx, params) + if err != nil { + util.ResError(c, err) + return + } + util.ResPage(c, result.Data, result.PageResult) +} + +// @Tags 问卷模块 +// @Security ApiKeyAuth +// @Summary Get questionnaire record by ID +// @Param id path string true "unique id" +// @Success 200 {object} util.ResponseResult{data=schema.Questionnaire} +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/questionnaires/{id} [get] +func (a *Questionnaire) Get(c *gin.Context) { + ctx := c.Request.Context() + id, err := util.GetQueryID(c) + if err != nil { + util.ResError(c, err) + return + } + item, err := a.QuestionnaireBIZ.Get(ctx, id) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, item) +} + +// @Tags 问卷模块 +// @Security ApiKeyAuth +// @Summary Create questionnaire record +// @Param body body schema.QuestionnaireForm true "Request body" +// @Success 200 {object} util.ResponseResult{data=schema.Questionnaire} +// @Failure 400 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/questionnaires [post] +func (a *Questionnaire) Create(c *gin.Context) { + ctx := c.Request.Context() + item := new(schema.QuestionnaireForm) + if err := util.ParseJSON(c, item); err != nil { + util.ResError(c, err) + return + } else if err := item.Validate(); err != nil { + util.ResError(c, err) + return + } + + result, err := a.QuestionnaireBIZ.Create(ctx, item) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, result) +} + +// @Tags 问卷模块 +// @Security ApiKeyAuth +// @Summary Update questionnaire record by ID +// @Param id path string true "unique id" +// @Param body body schema.QuestionnaireForm true "Request body" +// @Success 200 {object} util.ResponseResult +// @Failure 400 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/questionnaires/{id} [put] +func (a *Questionnaire) Update(c *gin.Context) { + ctx := c.Request.Context() + item := new(schema.QuestionnaireForm) + if err := util.ParseJSON(c, item); err != nil { + util.ResError(c, err) + return + } else if err := item.Validate(); err != nil { + util.ResError(c, err) + return + } + id, err := util.GetQueryID(c) + if err != nil { + util.ResError(c, err) + return + } + err = a.QuestionnaireBIZ.Update(ctx, id, item) + if err != nil { + util.ResError(c, err) + return + } + util.ResOK(c) +} + +// @Tags 问卷模块 +// @Security ApiKeyAuth +// @Summary Delete questionnaire record by ID +// @Param id path string true "unique id" +// @Success 200 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/questionnaires/{id} [delete] +func (a *Questionnaire) Delete(c *gin.Context) { + ctx := c.Request.Context() + id, err := util.GetQueryID(c) + if err != nil { + util.ResError(c, err) + return + } + err = a.QuestionnaireBIZ.Delete(ctx, id) + if err != nil { + util.ResError(c, err) + return + } + util.ResOK(c) +} diff --git a/internal/mods/activity/biz/activity.biz.go b/internal/mods/activity/biz/activity.biz.go new file mode 100644 index 0000000..8c7641d --- /dev/null +++ b/internal/mods/activity/biz/activity.biz.go @@ -0,0 +1,104 @@ +package biz + +import ( + "context" + "time" + + "github.guxuan/haibei/internal/mods/activity/dal" + "github.guxuan/haibei/internal/mods/activity/schema" + "github.guxuan/haibei/pkg/errors" + "github.guxuan/haibei/pkg/util" +) + +// Defining the `Activity` business logic. +type Activity struct { + Trans *util.Trans + ActivityDAL *dal.Activity +} + +// Query activities from the data access object based on the provided parameters and options. +func (a *Activity) Query(ctx context.Context, params schema.ActivityQueryParam) (*schema.ActivityQueryResult, error) { + params.Pagination = true + + result, err := a.ActivityDAL.Query(ctx, params, schema.ActivityQueryOptions{ + QueryOptions: util.QueryOptions{ + OrderFields: []util.OrderByParam{ + {Field: "created_at", Direction: util.DESC}, + }, + }, + }) + if err != nil { + return nil, err + } + return result, nil +} + +// Get the specified activity from the data access object. +func (a *Activity) Get(ctx context.Context, id uint) (*schema.Activity, error) { + activity, err := a.ActivityDAL.Get(ctx, id) + if err != nil { + return nil, err + } else if activity == nil { + return nil, errors.NotFound("", "Activity not found") + } + return activity, nil +} + +// Create a new activity in the data access object. +func (a *Activity) Create(ctx context.Context, formItem *schema.ActivityForm) (*schema.Activity, error) { + activity := &schema.Activity{} + + if err := formItem.FillTo(activity); err != nil { + return nil, err + } + + err := a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.ActivityDAL.Create(ctx, activity); err != nil { + return err + } + return nil + }) + if err != nil { + return nil, err + } + return activity, nil +} + +// Update the specified activity in the data access object. +func (a *Activity) Update(ctx context.Context, id uint, formItem *schema.ActivityForm) error { + activity, err := a.ActivityDAL.Get(ctx, id) + if err != nil { + return err + } else if activity == nil { + return errors.NotFound("", "Activity not found") + } + + if err := formItem.FillTo(activity); err != nil { + return err + } + activity.UpdatedAt = time.Now() + + return a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.ActivityDAL.Update(ctx, activity); err != nil { + return err + } + return nil + }) +} + +// Delete the specified activity from the data access object. +func (a *Activity) Delete(ctx context.Context, id uint) error { + exists, err := a.ActivityDAL.Exists(ctx, id) + if err != nil { + return err + } else if !exists { + return errors.NotFound("", "Activity not found") + } + + return a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.ActivityDAL.Delete(ctx, id); err != nil { + return err + } + return nil + }) +} diff --git a/internal/mods/activity/biz/activity_detail.biz.go b/internal/mods/activity/biz/activity_detail.biz.go new file mode 100644 index 0000000..1289584 --- /dev/null +++ b/internal/mods/activity/biz/activity_detail.biz.go @@ -0,0 +1,104 @@ +package biz + +import ( + "context" + "time" + + "github.guxuan/haibei/internal/mods/activity/dal" + "github.guxuan/haibei/internal/mods/activity/schema" + "github.guxuan/haibei/pkg/errors" + "github.guxuan/haibei/pkg/util" +) + +// Defining the `ActivityDetail` business logic. +type ActivityDetail struct { + Trans *util.Trans + ActivityDetailDAL *dal.ActivityDetail +} + +// Query activity details from the data access object based on the provided parameters and options. +func (a *ActivityDetail) Query(ctx context.Context, params schema.ActivityDetailQueryParam) (*schema.ActivityDetailQueryResult, error) { + params.Pagination = true + + result, err := a.ActivityDetailDAL.Query(ctx, params, schema.ActivityDetailQueryOptions{ + QueryOptions: util.QueryOptions{ + OrderFields: []util.OrderByParam{ + {Field: "created_at", Direction: util.DESC}, + }, + }, + }) + if err != nil { + return nil, err + } + return result, nil +} + +// Get the specified activity detail from the data access object. +func (a *ActivityDetail) Get(ctx context.Context, id uint) (*schema.ActivityDetail, error) { + activityDetail, err := a.ActivityDetailDAL.Get(ctx, id) + if err != nil { + return nil, err + } else if activityDetail == nil { + return nil, errors.NotFound("", "Activity detail not found") + } + return activityDetail, nil +} + +// Create a new activity detail in the data access object. +func (a *ActivityDetail) Create(ctx context.Context, formItem *schema.ActivityDetailForm) (*schema.ActivityDetail, error) { + activityDetail := &schema.ActivityDetail{} + + if err := formItem.FillTo(activityDetail); err != nil { + return nil, err + } + + err := a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.ActivityDetailDAL.Create(ctx, activityDetail); err != nil { + return err + } + return nil + }) + if err != nil { + return nil, err + } + return activityDetail, nil +} + +// Update the specified activity detail in the data access object. +func (a *ActivityDetail) Update(ctx context.Context, id uint, formItem *schema.ActivityDetailForm) error { + activityDetail, err := a.ActivityDetailDAL.Get(ctx, id) + if err != nil { + return err + } else if activityDetail == nil { + return errors.NotFound("", "Activity detail not found") + } + + if err := formItem.FillTo(activityDetail); err != nil { + return err + } + activityDetail.UpdatedAt = time.Now() + + return a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.ActivityDetailDAL.Update(ctx, activityDetail); err != nil { + return err + } + return nil + }) +} + +// Delete the specified activity detail from the data access object. +func (a *ActivityDetail) Delete(ctx context.Context, id uint) error { + exists, err := a.ActivityDetailDAL.Exists(ctx, id) + if err != nil { + return err + } else if !exists { + return errors.NotFound("", "Activity detail not found") + } + + return a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.ActivityDetailDAL.Delete(ctx, id); err != nil { + return err + } + return nil + }) +} diff --git a/internal/mods/activity/biz/house.biz.go b/internal/mods/activity/biz/house.biz.go new file mode 100644 index 0000000..d3e1e2f --- /dev/null +++ b/internal/mods/activity/biz/house.biz.go @@ -0,0 +1,98 @@ +package biz + +import ( + "context" + "time" + + "github.guxuan/haibei/internal/mods/activity/dal" + "github.guxuan/haibei/internal/mods/activity/schema" + "github.guxuan/haibei/pkg/errors" + "github.guxuan/haibei/pkg/util" +) + +type House struct { + Trans *util.Trans + HouseDAL *dal.House +} + +func (a *House) Query(ctx context.Context, params schema.HouseQueryParam) (*schema.HouseQueryResult, error) { + params.Pagination = true + + result, err := a.HouseDAL.Query(ctx, params, schema.HouseQueryOptions{ + QueryOptions: util.QueryOptions{ + OrderFields: []util.OrderByParam{ + {Field: "created_at", Direction: util.DESC}, + }, + }, + }) + if err != nil { + return nil, err + } + return result, nil +} + +func (a *House) Get(ctx context.Context, id uint) (*schema.House, error) { + item, err := a.HouseDAL.Get(ctx, id) + if err != nil { + return nil, err + } else if item == nil { + return nil, errors.NotFound("", "house not found") + } + return item, nil +} + +func (a *House) Create(ctx context.Context, formItem *schema.HouseForm) (*schema.House, error) { + item := &schema.House{} + + if err := formItem.FillTo(item); err != nil { + return nil, err + } + + err := a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.HouseDAL.Create(ctx, item); err != nil { + return err + } + return nil + }) + if err != nil { + return nil, err + } + return item, nil +} + +func (a *House) Update(ctx context.Context, id uint, formItem *schema.HouseForm) error { + item, err := a.HouseDAL.Get(ctx, id) + if err != nil { + return err + } else if item == nil { + return errors.NotFound("", "House not found") + } + + if err := formItem.FillTo(item); err != nil { + return err + } + item.UpdatedAt = time.Now() + + return a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.HouseDAL.Update(ctx, item); err != nil { + return err + } + return nil + }) +} + +func (a *House) Delete(ctx context.Context, id uint) error { + exists, err := a.HouseDAL.Exists(ctx, id) + if err != nil { + return err + } else if !exists { + return errors.NotFound("", "HOUSE not found") + } + + return a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.HouseDAL.Delete(ctx, id); err != nil { + return err + } + return nil + }) +} diff --git a/internal/mods/activity/biz/houseArticle.biz.go b/internal/mods/activity/biz/houseArticle.biz.go new file mode 100644 index 0000000..54ad76a --- /dev/null +++ b/internal/mods/activity/biz/houseArticle.biz.go @@ -0,0 +1,98 @@ +package biz + +import ( + "context" + "time" + + "github.guxuan/haibei/internal/mods/activity/dal" + "github.guxuan/haibei/internal/mods/activity/schema" + "github.guxuan/haibei/pkg/errors" + "github.guxuan/haibei/pkg/util" +) + +type HouseArticle struct { + Trans *util.Trans + HouseArticleDAL *dal.HouseArticle +} + +func (a *HouseArticle) Query(ctx context.Context, params schema.HouseArticleQueryParam) (*schema.HouseArticleQueryResult, error) { + params.Pagination = true + + result, err := a.HouseArticleDAL.Query(ctx, params, schema.HouseArticleQueryOptions{ + QueryOptions: util.QueryOptions{ + OrderFields: []util.OrderByParam{ + {Field: "created_at", Direction: util.DESC}, + }, + }, + }) + if err != nil { + return nil, err + } + return result, nil +} + +func (a *HouseArticle) Get(ctx context.Context, id uint) (*schema.HouseArticle, error) { + item, err := a.HouseArticleDAL.Get(ctx, id) + if err != nil { + return nil, err + } else if item == nil { + return nil, errors.NotFound("", "house not found") + } + return item, nil +} + +func (a *HouseArticle) Create(ctx context.Context, formItem *schema.HouseArticleForm) (*schema.HouseArticle, error) { + item := &schema.HouseArticle{} + + if err := formItem.FillTo(item); err != nil { + return nil, err + } + + err := a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.HouseArticleDAL.Create(ctx, item); err != nil { + return err + } + return nil + }) + if err != nil { + return nil, err + } + return item, nil +} + +func (a *HouseArticle) Update(ctx context.Context, id uint, formItem *schema.HouseArticleForm) error { + item, err := a.HouseArticleDAL.Get(ctx, id) + if err != nil { + return err + } else if item == nil { + return errors.NotFound("", "House not found") + } + + if err := formItem.FillTo(item); err != nil { + return err + } + item.UpdatedAt = time.Now() + + return a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.HouseArticleDAL.Update(ctx, item); err != nil { + return err + } + return nil + }) +} + +func (a *HouseArticle) Delete(ctx context.Context, id uint) error { + exists, err := a.HouseArticleDAL.Exists(ctx, id) + if err != nil { + return err + } else if !exists { + return errors.NotFound("", "HOUSE not found") + } + + return a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.HouseArticleDAL.Delete(ctx, id); err != nil { + return err + } + return nil + }) +} diff --git a/internal/mods/activity/biz/qeustion_detail.biz.go b/internal/mods/activity/biz/qeustion_detail.biz.go new file mode 100644 index 0000000..f9bdae9 --- /dev/null +++ b/internal/mods/activity/biz/qeustion_detail.biz.go @@ -0,0 +1,104 @@ +package biz + +import ( + "context" + "time" + + "github.guxuan/haibei/internal/mods/activity/dal" + "github.guxuan/haibei/internal/mods/activity/schema" + "github.guxuan/haibei/pkg/errors" + "github.guxuan/haibei/pkg/util" +) + +// Defining the `QeustionDetail` business logic. +type QeustionDetail struct { + Trans *util.Trans + QeustionDetailDAL *dal.QeustionDetail +} + +// Query qeustion details from the data access object based on the provided parameters and options. +func (a *QeustionDetail) Query(ctx context.Context, params schema.QeustionDetailQueryParam) (*schema.QeustionDetailQueryResult, error) { + params.Pagination = true + + result, err := a.QeustionDetailDAL.Query(ctx, params, schema.QeustionDetailQueryOptions{ + QueryOptions: util.QueryOptions{ + OrderFields: []util.OrderByParam{ + {Field: "created_at", Direction: util.DESC}, + }, + }, + }) + if err != nil { + return nil, err + } + return result, nil +} + +// Get the specified qeustion detail from the data access object. +func (a *QeustionDetail) Get(ctx context.Context, id uint) (*schema.QeustionDetail, error) { + qeustionDetail, err := a.QeustionDetailDAL.Get(ctx, id) + if err != nil { + return nil, err + } else if qeustionDetail == nil { + return nil, errors.NotFound("", "Qeustion detail not found") + } + return qeustionDetail, nil +} + +// Create a new qeustion detail in the data access object. +func (a *QeustionDetail) Create(ctx context.Context, formItem *schema.QeustionDetailForm) (*schema.QeustionDetail, error) { + qeustionDetail := &schema.QeustionDetail{} + + if err := formItem.FillTo(qeustionDetail); err != nil { + return nil, err + } + + err := a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.QeustionDetailDAL.Create(ctx, qeustionDetail); err != nil { + return err + } + return nil + }) + if err != nil { + return nil, err + } + return qeustionDetail, nil +} + +// Update the specified qeustion detail in the data access object. +func (a *QeustionDetail) Update(ctx context.Context, id uint, formItem *schema.QeustionDetailForm) error { + qeustionDetail, err := a.QeustionDetailDAL.Get(ctx, id) + if err != nil { + return err + } else if qeustionDetail == nil { + return errors.NotFound("", "Qeustion detail not found") + } + + if err := formItem.FillTo(qeustionDetail); err != nil { + return err + } + qeustionDetail.UpdatedAt = time.Now() + + return a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.QeustionDetailDAL.Update(ctx, qeustionDetail); err != nil { + return err + } + return nil + }) +} + +// Delete the specified qeustion detail from the data access object. +func (a *QeustionDetail) Delete(ctx context.Context, id uint) error { + exists, err := a.QeustionDetailDAL.Exists(ctx, id) + if err != nil { + return err + } else if !exists { + return errors.NotFound("", "Qeustion detail not found") + } + + return a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.QeustionDetailDAL.Delete(ctx, id); err != nil { + return err + } + return nil + }) +} diff --git a/internal/mods/activity/biz/questionnaire.biz.go b/internal/mods/activity/biz/questionnaire.biz.go new file mode 100644 index 0000000..cc4e561 --- /dev/null +++ b/internal/mods/activity/biz/questionnaire.biz.go @@ -0,0 +1,104 @@ +package biz + +import ( + "context" + "time" + + "github.guxuan/haibei/internal/mods/activity/dal" + "github.guxuan/haibei/internal/mods/activity/schema" + "github.guxuan/haibei/pkg/errors" + "github.guxuan/haibei/pkg/util" +) + +// Defining the `Questionnaire` business logic. +type Questionnaire struct { + Trans *util.Trans + QuestionnaireDAL *dal.Questionnaire +} + +// Query questionnaires from the data access object based on the provided parameters and options. +func (a *Questionnaire) Query(ctx context.Context, params schema.QuestionnaireQueryParam) (*schema.QuestionnaireQueryResult, error) { + params.Pagination = true + + result, err := a.QuestionnaireDAL.Query(ctx, params, schema.QuestionnaireQueryOptions{ + QueryOptions: util.QueryOptions{ + OrderFields: []util.OrderByParam{ + {Field: "created_at", Direction: util.DESC}, + }, + }, + }) + if err != nil { + return nil, err + } + return result, nil +} + +// Get the specified questionnaire from the data access object. +func (a *Questionnaire) Get(ctx context.Context, id uint) (*schema.Questionnaire, error) { + questionnaire, err := a.QuestionnaireDAL.Get(ctx, id) + if err != nil { + return nil, err + } else if questionnaire == nil { + return nil, errors.NotFound("", "Questionnaire not found") + } + return questionnaire, nil +} + +// Create a new questionnaire in the data access object. +func (a *Questionnaire) Create(ctx context.Context, formItem *schema.QuestionnaireForm) (*schema.Questionnaire, error) { + questionnaire := &schema.Questionnaire{} + + if err := formItem.FillTo(questionnaire); err != nil { + return nil, err + } + + err := a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.QuestionnaireDAL.Create(ctx, questionnaire); err != nil { + return err + } + return nil + }) + if err != nil { + return nil, err + } + return questionnaire, nil +} + +// Update the specified questionnaire in the data access object. +func (a *Questionnaire) Update(ctx context.Context, id uint, formItem *schema.QuestionnaireForm) error { + questionnaire, err := a.QuestionnaireDAL.Get(ctx, id) + if err != nil { + return err + } else if questionnaire == nil { + return errors.NotFound("", "Questionnaire not found") + } + + if err := formItem.FillTo(questionnaire); err != nil { + return err + } + questionnaire.UpdatedAt = time.Now() + + return a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.QuestionnaireDAL.Update(ctx, questionnaire); err != nil { + return err + } + return nil + }) +} + +// Delete the specified questionnaire from the data access object. +func (a *Questionnaire) Delete(ctx context.Context, id uint) error { + exists, err := a.QuestionnaireDAL.Exists(ctx, id) + if err != nil { + return err + } else if !exists { + return errors.NotFound("", "Questionnaire not found") + } + + return a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.QuestionnaireDAL.Delete(ctx, id); err != nil { + return err + } + return nil + }) +} diff --git a/internal/mods/activity/dal/activity.dal.go b/internal/mods/activity/dal/activity.dal.go new file mode 100644 index 0000000..5eeaf08 --- /dev/null +++ b/internal/mods/activity/dal/activity.dal.go @@ -0,0 +1,83 @@ +package dal + +import ( + "context" + + "github.guxuan/haibei/internal/mods/activity/schema" + "github.guxuan/haibei/pkg/errors" + "github.guxuan/haibei/pkg/util" + "gorm.io/gorm" +) + +// Get activity storage instance +func GetActivityDB(ctx context.Context, defDB *gorm.DB) *gorm.DB { + return util.GetDB(ctx, defDB).Model(new(schema.Activity)) +} + +// Defining the `Activity` data access object. +type Activity struct { + DB *gorm.DB +} + +// Query activities from the database based on the provided parameters and options. +func (a *Activity) Query(ctx context.Context, params schema.ActivityQueryParam, opts ...schema.ActivityQueryOptions) (*schema.ActivityQueryResult, error) { + var opt schema.ActivityQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + db := GetActivityDB(ctx, a.DB) + + var list schema.Activities + pageResult, err := util.WrapPageQuery(ctx, db, params.PaginationParam, opt.QueryOptions, &list) + if err != nil { + return nil, errors.WithStack(err) + } + + queryResult := &schema.ActivityQueryResult{ + PageResult: pageResult, + Data: list, + } + return queryResult, nil +} + +// Get the specified activity from the database. +func (a *Activity) Get(ctx context.Context, id uint, opts ...schema.ActivityQueryOptions) (*schema.Activity, error) { + var opt schema.ActivityQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + item := new(schema.Activity) + ok, err := util.FindOne(ctx, GetActivityDB(ctx, a.DB).Where("id=?", id), opt.QueryOptions, item) + if err != nil { + return nil, errors.WithStack(err) + } else if !ok { + return nil, nil + } + return item, nil +} + +// Exists checks if the specified activity exists in the database. +func (a *Activity) Exists(ctx context.Context, id uint) (bool, error) { + ok, err := util.Exists(ctx, GetActivityDB(ctx, a.DB).Where("id=?", id)) + return ok, errors.WithStack(err) +} + +// Create a new activity. +func (a *Activity) Create(ctx context.Context, item *schema.Activity) error { + result := GetActivityDB(ctx, a.DB).Create(item) + return errors.WithStack(result.Error) +} + +// Update the specified activity in the database. +func (a *Activity) Update(ctx context.Context, item *schema.Activity) error { + result := GetActivityDB(ctx, a.DB).Where("id=?", item.ID).Select("*").Omit("created_at").Updates(item) + return errors.WithStack(result.Error) +} + +// Delete the specified activity from the database. +func (a *Activity) Delete(ctx context.Context, id uint) error { + result := GetActivityDB(ctx, a.DB).Where("id=?", id).Delete(new(schema.Activity)) + return errors.WithStack(result.Error) +} diff --git a/internal/mods/activity/dal/activity_detail.dal.go b/internal/mods/activity/dal/activity_detail.dal.go new file mode 100644 index 0000000..0353838 --- /dev/null +++ b/internal/mods/activity/dal/activity_detail.dal.go @@ -0,0 +1,96 @@ +package dal + +import ( + "context" + + "github.guxuan/haibei/internal/mods/activity/schema" + "github.guxuan/haibei/pkg/errors" + "github.guxuan/haibei/pkg/util" + "gorm.io/gorm" +) + +// Get activity detail storage instance +func GetActivityDetailDB(ctx context.Context, defDB *gorm.DB) *gorm.DB { + return util.GetDB(ctx, defDB).Model(new(schema.ActivityDetail)) +} + +// Defining the `ActivityDetail` data access object. +type ActivityDetail struct { + DB *gorm.DB +} + +// Query activity details from the database based on the provided parameters and options. +func (a *ActivityDetail) Query(ctx context.Context, params schema.ActivityDetailQueryParam, opts ...schema.ActivityDetailQueryOptions) (*schema.ActivityDetailQueryResult, error) { + var opt schema.ActivityDetailQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + db := GetActivityDetailDB(ctx, a.DB) + + var list schema.ActivityDetails + pageResult, err := util.WrapPageQuery(ctx, db, params.PaginationParam, opt.QueryOptions, &list) + if err != nil { + return nil, errors.WithStack(err) + } + + queryResult := &schema.ActivityDetailQueryResult{ + PageResult: pageResult, + Data: list, + } + return queryResult, nil +} + +// Get the specified activity detail from the database. +func (a *ActivityDetail) Get(ctx context.Context, id uint, opts ...schema.ActivityDetailQueryOptions) (*schema.ActivityDetail, error) { + var opt schema.ActivityDetailQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + item := new(schema.ActivityDetail) + ok, err := util.FindOne(ctx, GetActivityDetailDB(ctx, a.DB).Where("id=?", id), opt.QueryOptions, item) + if err != nil { + return nil, errors.WithStack(err) + } else if !ok { + return nil, nil + } + return item, nil +} + +func (a *ActivityDetail) GetByCustomerIDAndActivityID(ctx context.Context, customerID, activityID uint) (*schema.ActivityDetail, error) { + var opt schema.ActivityDetailQueryOptions + + item := new(schema.ActivityDetail) + ok, err := util.FindOne(ctx, GetActivityDetailDB(ctx, a.DB).Where("customer_id =? AND activity_id = ? ", customerID, activityID), opt.QueryOptions, item) + if err != nil { + return nil, errors.WithStack(err) + } else if !ok { + return nil, nil + } + return item, nil +} + +// Exists checks if the specified activity detail exists in the database. +func (a *ActivityDetail) Exists(ctx context.Context, id uint) (bool, error) { + ok, err := util.Exists(ctx, GetActivityDetailDB(ctx, a.DB).Where("id=?", id)) + return ok, errors.WithStack(err) +} + +// Create a new activity detail. +func (a *ActivityDetail) Create(ctx context.Context, item *schema.ActivityDetail) error { + result := GetActivityDetailDB(ctx, a.DB).Create(item) + return errors.WithStack(result.Error) +} + +// Update the specified activity detail in the database. +func (a *ActivityDetail) Update(ctx context.Context, item *schema.ActivityDetail) error { + result := GetActivityDetailDB(ctx, a.DB).Where("id=?", item.ID).Select("*").Omit("created_at").Updates(item) + return errors.WithStack(result.Error) +} + +// Delete the specified activity detail from the database. +func (a *ActivityDetail) Delete(ctx context.Context, id uint) error { + result := GetActivityDetailDB(ctx, a.DB).Where("id=?", id).Delete(new(schema.ActivityDetail)) + return errors.WithStack(result.Error) +} diff --git a/internal/mods/activity/dal/house.dal.go b/internal/mods/activity/dal/house.dal.go new file mode 100644 index 0000000..73a8771 --- /dev/null +++ b/internal/mods/activity/dal/house.dal.go @@ -0,0 +1,76 @@ +package dal + +import ( + "context" + + "github.guxuan/haibei/internal/mods/activity/schema" + "github.guxuan/haibei/pkg/errors" + "github.guxuan/haibei/pkg/util" + "gorm.io/gorm" +) + +func GetHouseDB(ctx context.Context, defDB *gorm.DB) *gorm.DB { + return util.GetDB(ctx, defDB).Model(new(schema.House)) +} + +type House struct { + DB *gorm.DB +} + +// Query activities from the database based on the provided parameters and options. +func (a *House) Query(ctx context.Context, params schema.HouseQueryParam, opts ...schema.HouseQueryOptions) (*schema.HouseQueryResult, error) { + var opt schema.HouseQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + db := GetHouseDB(ctx, a.DB) + + var list schema.Houses + pageResult, err := util.WrapPageQuery(ctx, db, params.PaginationParam, opt.QueryOptions, &list) + if err != nil { + return nil, errors.WithStack(err) + } + + queryResult := &schema.HouseQueryResult{ + PageResult: pageResult, + Data: list, + } + return queryResult, nil +} + +func (a *House) Get(ctx context.Context, id uint, opts ...schema.HouseQueryOptions) (*schema.House, error) { + var opt schema.HouseQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + item := new(schema.House) + ok, err := util.FindOne(ctx, GetHouseDB(ctx, a.DB).Where("id=?", id), opt.QueryOptions, item) + if err != nil { + return nil, errors.WithStack(err) + } else if !ok { + return nil, nil + } + return item, nil +} + +func (a *House) Exists(ctx context.Context, id uint) (bool, error) { + ok, err := util.Exists(ctx, GetHouseDB(ctx, a.DB).Where("id=?", id)) + return ok, errors.WithStack(err) +} + +func (a *House) Create(ctx context.Context, item *schema.House) error { + result := GetHouseDB(ctx, a.DB).Create(item) + return errors.WithStack(result.Error) +} + +func (a *House) Update(ctx context.Context, item *schema.House) error { + result := GetHouseDB(ctx, a.DB).Where("id=?", item.ID).Select("*").Omit("created_at").Updates(item) + return errors.WithStack(result.Error) +} + +func (a *House) Delete(ctx context.Context, id uint) error { + result := GetHouseDB(ctx, a.DB).Where("id=?", id).Delete(new(schema.House)) + return errors.WithStack(result.Error) +} diff --git a/internal/mods/activity/dal/houseArticle.dal.go b/internal/mods/activity/dal/houseArticle.dal.go new file mode 100644 index 0000000..69bce9f --- /dev/null +++ b/internal/mods/activity/dal/houseArticle.dal.go @@ -0,0 +1,76 @@ +package dal + +import ( + "context" + + "github.guxuan/haibei/internal/mods/activity/schema" + "github.guxuan/haibei/pkg/errors" + "github.guxuan/haibei/pkg/util" + "gorm.io/gorm" +) + +func GetHouseArticleDB(ctx context.Context, defDB *gorm.DB) *gorm.DB { + return util.GetDB(ctx, defDB).Model(new(schema.HouseArticle)) +} + +type HouseArticle struct { + DB *gorm.DB +} + +// Query activities from the database based on the provided parameters and options. +func (a *HouseArticle) Query(ctx context.Context, params schema.HouseArticleQueryParam, opts ...schema.HouseArticleQueryOptions) (*schema.HouseArticleQueryResult, error) { + var opt schema.HouseArticleQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + db := GetHouseArticleDB(ctx, a.DB) + + var list schema.HouseArticles + pageResult, err := util.WrapPageQuery(ctx, db, params.PaginationParam, opt.QueryOptions, &list) + if err != nil { + return nil, errors.WithStack(err) + } + + queryResult := &schema.HouseArticleQueryResult{ + PageResult: pageResult, + Data: list, + } + return queryResult, nil +} + +func (a *HouseArticle) Get(ctx context.Context, id uint, opts ...schema.HouseArticleQueryOptions) (*schema.HouseArticle, error) { + var opt schema.HouseArticleQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + item := new(schema.HouseArticle) + ok, err := util.FindOne(ctx, GetHouseArticleDB(ctx, a.DB).Where("id=?", id), opt.QueryOptions, item) + if err != nil { + return nil, errors.WithStack(err) + } else if !ok { + return nil, nil + } + return item, nil +} + +func (a *HouseArticle) Exists(ctx context.Context, id uint) (bool, error) { + ok, err := util.Exists(ctx, GetHouseArticleDB(ctx, a.DB).Where("id=?", id)) + return ok, errors.WithStack(err) +} + +func (a *HouseArticle) Create(ctx context.Context, item *schema.HouseArticle) error { + result := GetHouseArticleDB(ctx, a.DB).Create(item) + return errors.WithStack(result.Error) +} + +func (a *HouseArticle) Update(ctx context.Context, item *schema.HouseArticle) error { + result := GetHouseArticleDB(ctx, a.DB).Where("id=?", item.ID).Select("*").Omit("created_at").Updates(item) + return errors.WithStack(result.Error) +} + +func (a *HouseArticle) Delete(ctx context.Context, id uint) error { + result := GetHouseArticleDB(ctx, a.DB).Where("id=?", id).Delete(new(schema.HouseArticle)) + return errors.WithStack(result.Error) +} diff --git a/internal/mods/activity/dal/qeustion_detail.dal.go b/internal/mods/activity/dal/qeustion_detail.dal.go new file mode 100644 index 0000000..b3004a1 --- /dev/null +++ b/internal/mods/activity/dal/qeustion_detail.dal.go @@ -0,0 +1,96 @@ +package dal + +import ( + "context" + + "github.guxuan/haibei/internal/mods/activity/schema" + "github.guxuan/haibei/pkg/errors" + "github.guxuan/haibei/pkg/util" + "gorm.io/gorm" +) + +// Get qeustion detail storage instance +func GetQeustionDetailDB(ctx context.Context, defDB *gorm.DB) *gorm.DB { + return util.GetDB(ctx, defDB).Model(new(schema.QeustionDetail)) +} + +// Defining the `QeustionDetail` data access object. +type QeustionDetail struct { + DB *gorm.DB +} + +// Query qeustion details from the database based on the provided parameters and options. +func (a *QeustionDetail) Query(ctx context.Context, params schema.QeustionDetailQueryParam, opts ...schema.QeustionDetailQueryOptions) (*schema.QeustionDetailQueryResult, error) { + var opt schema.QeustionDetailQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + db := GetQeustionDetailDB(ctx, a.DB) + + var list schema.QeustionDetails + pageResult, err := util.WrapPageQuery(ctx, db, params.PaginationParam, opt.QueryOptions, &list) + if err != nil { + return nil, errors.WithStack(err) + } + + queryResult := &schema.QeustionDetailQueryResult{ + PageResult: pageResult, + Data: list, + } + return queryResult, nil +} + +// Get the specified qeustion detail from the database. +func (a *QeustionDetail) Get(ctx context.Context, id uint, opts ...schema.QeustionDetailQueryOptions) (*schema.QeustionDetail, error) { + var opt schema.QeustionDetailQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + item := new(schema.QeustionDetail) + ok, err := util.FindOne(ctx, GetQeustionDetailDB(ctx, a.DB).Where("id=?", id), opt.QueryOptions, item) + if err != nil { + return nil, errors.WithStack(err) + } else if !ok { + return nil, nil + } + return item, nil +} + +func (a *QeustionDetail) GetByCustomerIDAndActivityID(ctx context.Context, customerID, questionID uint) (*schema.QeustionDetail, error) { + var opt schema.QeustionDetailQueryOptions + + item := new(schema.QeustionDetail) + ok, err := util.FindOne(ctx, GetQeustionDetailDB(ctx, a.DB).Where("customer_id =? AND question_id = ? ", customerID, questionID), opt.QueryOptions, item) + if err != nil { + return nil, errors.WithStack(err) + } else if !ok { + return nil, nil + } + return item, nil +} + +// Exists checks if the specified qeustion detail exists in the database. +func (a *QeustionDetail) Exists(ctx context.Context, id uint) (bool, error) { + ok, err := util.Exists(ctx, GetQeustionDetailDB(ctx, a.DB).Where("id=?", id)) + return ok, errors.WithStack(err) +} + +// Create a new qeustion detail. +func (a *QeustionDetail) Create(ctx context.Context, item *schema.QeustionDetail) error { + result := GetQeustionDetailDB(ctx, a.DB).Create(item) + return errors.WithStack(result.Error) +} + +// Update the specified qeustion detail in the database. +func (a *QeustionDetail) Update(ctx context.Context, item *schema.QeustionDetail) error { + result := GetQeustionDetailDB(ctx, a.DB).Where("id=?", item.ID).Select("*").Omit("created_at").Updates(item) + return errors.WithStack(result.Error) +} + +// Delete the specified qeustion detail from the database. +func (a *QeustionDetail) Delete(ctx context.Context, id uint) error { + result := GetQeustionDetailDB(ctx, a.DB).Where("id=?", id).Delete(new(schema.QeustionDetail)) + return errors.WithStack(result.Error) +} diff --git a/internal/mods/activity/dal/questionnaire.dal.go b/internal/mods/activity/dal/questionnaire.dal.go new file mode 100644 index 0000000..dc10aea --- /dev/null +++ b/internal/mods/activity/dal/questionnaire.dal.go @@ -0,0 +1,83 @@ +package dal + +import ( + "context" + + "github.guxuan/haibei/internal/mods/activity/schema" + "github.guxuan/haibei/pkg/errors" + "github.guxuan/haibei/pkg/util" + "gorm.io/gorm" +) + +// Get questionnaire storage instance +func GetQuestionnaireDB(ctx context.Context, defDB *gorm.DB) *gorm.DB { + return util.GetDB(ctx, defDB).Model(new(schema.Questionnaire)) +} + +// Defining the `Questionnaire` data access object. +type Questionnaire struct { + DB *gorm.DB +} + +// Query questionnaires from the database based on the provided parameters and options. +func (a *Questionnaire) Query(ctx context.Context, params schema.QuestionnaireQueryParam, opts ...schema.QuestionnaireQueryOptions) (*schema.QuestionnaireQueryResult, error) { + var opt schema.QuestionnaireQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + db := GetQuestionnaireDB(ctx, a.DB) + + var list schema.Questionnaires + pageResult, err := util.WrapPageQuery(ctx, db, params.PaginationParam, opt.QueryOptions, &list) + if err != nil { + return nil, errors.WithStack(err) + } + + queryResult := &schema.QuestionnaireQueryResult{ + PageResult: pageResult, + Data: list, + } + return queryResult, nil +} + +// Get the specified questionnaire from the database. +func (a *Questionnaire) Get(ctx context.Context, id uint, opts ...schema.QuestionnaireQueryOptions) (*schema.Questionnaire, error) { + var opt schema.QuestionnaireQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + item := new(schema.Questionnaire) + ok, err := util.FindOne(ctx, GetQuestionnaireDB(ctx, a.DB).Where("id=?", id), opt.QueryOptions, item) + if err != nil { + return nil, errors.WithStack(err) + } else if !ok { + return nil, nil + } + return item, nil +} + +// Exists checks if the specified questionnaire exists in the database. +func (a *Questionnaire) Exists(ctx context.Context, id uint) (bool, error) { + ok, err := util.Exists(ctx, GetQuestionnaireDB(ctx, a.DB).Where("id=?", id)) + return ok, errors.WithStack(err) +} + +// Create a new questionnaire. +func (a *Questionnaire) Create(ctx context.Context, item *schema.Questionnaire) error { + result := GetQuestionnaireDB(ctx, a.DB).Create(item) + return errors.WithStack(result.Error) +} + +// Update the specified questionnaire in the database. +func (a *Questionnaire) Update(ctx context.Context, item *schema.Questionnaire) error { + result := GetQuestionnaireDB(ctx, a.DB).Where("id=?", item.ID).Select("*").Omit("created_at").Updates(item) + return errors.WithStack(result.Error) +} + +// Delete the specified questionnaire from the database. +func (a *Questionnaire) Delete(ctx context.Context, id uint) error { + result := GetQuestionnaireDB(ctx, a.DB).Where("id=?", id).Delete(new(schema.Questionnaire)) + return errors.WithStack(result.Error) +} diff --git a/internal/mods/activity/main.go b/internal/mods/activity/main.go new file mode 100644 index 0000000..f2a6325 --- /dev/null +++ b/internal/mods/activity/main.go @@ -0,0 +1,73 @@ +package activity + +import ( + "context" + + "github.guxuan/haibei/internal/config" + + "github.com/gin-gonic/gin" + "github.guxuan/haibei/internal/mods/activity/api" + "github.guxuan/haibei/internal/mods/activity/schema" + "gorm.io/gorm" +) + +type Activity struct { + DB *gorm.DB + ActivityAPI *api.Activity + QuestionnaireAPI *api.Questionnaire + ActivityDetailAPI *api.ActivityDetail + QeustionDetailAPI *api.QeustionDetail +} + +func (a *Activity) AutoMigrate(ctx context.Context) error { + return a.DB.AutoMigrate(new(schema.Activity), new(schema.Questionnaire), new(schema.ActivityDetail), new(schema.QeustionDetail), new(schema.House), new(schema.HouseArticle)) +} + +func (a *Activity) Init(ctx context.Context) error { + if config.C.Storage.DB.AutoMigrate { + if err := a.AutoMigrate(ctx); err != nil { + return err + } + } + return nil +} + +func (a *Activity) RegisterV1Routers(ctx context.Context, v1 *gin.RouterGroup) error { + activity := v1.Group("activities") + { + activity.GET("", a.ActivityAPI.Query) + activity.GET(":id", a.ActivityAPI.Get) + activity.POST("", a.ActivityAPI.Create) + activity.PUT(":id", a.ActivityAPI.Update) + activity.DELETE(":id", a.ActivityAPI.Delete) + } + questionnaire := v1.Group("questionnaires") + { + questionnaire.GET("", a.QuestionnaireAPI.Query) + questionnaire.GET(":id", a.QuestionnaireAPI.Get) + questionnaire.POST("", a.QuestionnaireAPI.Create) + questionnaire.PUT(":id", a.QuestionnaireAPI.Update) + questionnaire.DELETE(":id", a.QuestionnaireAPI.Delete) + } + activityDetail := v1.Group("activity-details") + { + activityDetail.GET("", a.ActivityDetailAPI.Query) + activityDetail.GET(":id", a.ActivityDetailAPI.Get) + activityDetail.POST("", a.ActivityDetailAPI.Create) + activityDetail.PUT(":id", a.ActivityDetailAPI.Update) + activityDetail.DELETE(":id", a.ActivityDetailAPI.Delete) + } + qeustionDetail := v1.Group("qeustion-details") + { + qeustionDetail.GET("", a.QeustionDetailAPI.Query) + qeustionDetail.GET(":id", a.QeustionDetailAPI.Get) + qeustionDetail.POST("", a.QeustionDetailAPI.Create) + qeustionDetail.PUT(":id", a.QeustionDetailAPI.Update) + qeustionDetail.DELETE(":id", a.QeustionDetailAPI.Delete) + } + return nil +} + +func (a *Activity) Release(ctx context.Context) error { + return nil +} diff --git a/internal/mods/activity/schema/activity.go b/internal/mods/activity/schema/activity.go new file mode 100644 index 0000000..fdc8af8 --- /dev/null +++ b/internal/mods/activity/schema/activity.go @@ -0,0 +1,86 @@ +package schema + +import ( + "github.guxuan/haibei/pkg/util" + "time" +) + +// Defining the `Activity` struct. +type Activity struct { + util.BaseModel + Title string `json:"title" gorm:"size:128;not null;index;comment:标题"` + Cover string `json:"cover" gorm:"size:2048;not null;comment:封面"` + Images *[]string `json:"images" gorm:"serializer:json;comment:详图数组"` + StartAt *time.Time `json:"startAt" gorm:"not null;comment:开始时间"` + EndAt *time.Time `json:"endAt" gorm:"not null;comment:结束时间"` + EndSignupAt *time.Time `json:"endSignupAt" gorm:"not null;comment:最晚报名时间"` + MaxSignupNum int `json:"maxSignupNum" gorm:"not null;comment:最大报名人数"` + SignupNum int `json:"signupNum" gorm:"not null;default:0;comment:当前报名人数"` + Address string `json:"address" gorm:"size:1024;not null;comment:活动地址"` + Content string `json:"content" gorm:"type:text;not null;comment:活动详情"` + Point int `json:"point" gorm:"not null;default:0;comment:活动所需积分"` + AreaID uint `json:"areaId" gorm:"index;comment:城市id"` + Status string `json:"status" gorm:"size:20;index;comment:状态"` +} + +// Defining the query parameters for the `Activity` struct. +type ActivityQueryParam struct { + util.PaginationParam + LikeTitle string `form:"title" ` + Status string `form:"status" ` + AreaID uint `form:"areaId"` +} + +// Defining the query options for the `Activity` struct. +type ActivityQueryOptions struct { + util.QueryOptions +} + +// Defining the query result for the `Activity` struct. +type ActivityQueryResult struct { + Data Activities + PageResult *util.PaginationResult +} + +// Defining the slice of `Activity` struct. +type Activities []*Activity + +// Defining the data structure for creating a `Activity` struct. +type ActivityForm struct { + Title string `json:"title" ` + Cover string `json:"cover" ` + Images *[]string `json:"images" ` + StartAt *time.Time `json:"startAt" ` + EndAt *time.Time `json:"endAt" ` + EndSignupAt *time.Time `json:"endSignupAt" ` + MaxSignupNum int `json:"maxSignupNum" ` + SignupNum int `json:"signupNum"` + Address string `json:"address" ` + Content string `json:"content"` + Point int `json:"point" ` + Status string `json:"status" ` + AreaID uint `json:"areaId" ` +} + +// A validation function for the `ActivityForm` struct. +func (a *ActivityForm) Validate() error { + return nil +} + +// Convert `ActivityForm` to `Activity` object. +func (a *ActivityForm) FillTo(activity *Activity) error { + activity.Title = a.Title + activity.Cover = a.Cover + activity.Images = a.Images + activity.StartAt = a.StartAt + activity.EndAt = a.EndAt + activity.AreaID = a.AreaID + activity.EndSignupAt = a.EndSignupAt + activity.MaxSignupNum = a.MaxSignupNum + activity.SignupNum = a.SignupNum + activity.Address = a.Address + activity.Content = a.Content + activity.Point = a.Point + activity.Status = a.Status + return nil +} diff --git a/internal/mods/activity/schema/activity_detail.go b/internal/mods/activity/schema/activity_detail.go new file mode 100644 index 0000000..a702ca8 --- /dev/null +++ b/internal/mods/activity/schema/activity_detail.go @@ -0,0 +1,55 @@ +package schema + +import ( + "github.guxuan/haibei/pkg/util" +) + +// Defining the `ActivityDetail` struct. +type ActivityDetail struct { + util.BaseModel + ActivityID uint `json:"activityId" gorm:"index;"` + CustomerID uint `json:"customerId" gorm:"index;"` + Status string `json:"status" gorm:"size:20;index;comment:状态"` +} + +// Defining the query parameters for the `ActivityDetail` struct. +type ActivityDetailQueryParam struct { + util.PaginationParam + ActivityID uint `form:"activityId" ` + CustomerID uint `form:"customerId" ` + Status string `form:"status" ` +} + +// Defining the query options for the `ActivityDetail` struct. +type ActivityDetailQueryOptions struct { + util.QueryOptions +} + +// Defining the query result for the `ActivityDetail` struct. +type ActivityDetailQueryResult struct { + Data ActivityDetails + PageResult *util.PaginationResult +} + +// Defining the slice of `ActivityDetail` struct. +type ActivityDetails []*ActivityDetail + +// Defining the data structure for creating a `ActivityDetail` struct. +type ActivityDetailForm struct { + ActivityID uint `json:"activityId" ` + CustomerID uint `json:"customerId" ` + Status string `json:"status" ` +} + +// A validation function for the `ActivityDetailForm` struct. +func (a *ActivityDetailForm) Validate() error { + return nil +} + +// Convert `ActivityDetailForm` to `ActivityDetail` object. +func (a *ActivityDetailForm) FillTo(activityDetail *ActivityDetail) error { + activityDetail.ActivityID = a.ActivityID + activityDetail.CustomerID = a.CustomerID + activityDetail.Status = a.Status + return nil +} diff --git a/internal/mods/activity/schema/house.go b/internal/mods/activity/schema/house.go new file mode 100644 index 0000000..f4dc910 --- /dev/null +++ b/internal/mods/activity/schema/house.go @@ -0,0 +1,163 @@ +package schema + +import ( + "github.guxuan/haibei/pkg/util" +) + +// Defining the `Activity` struct. +type House struct { + util.BaseModel + Name string `json:"name" gorm:"size:128;not null;index;comment:名字"` + Title string `json:"title" gorm:"size:128;not null;index;comment:标题"` + Cover string `json:"cover" gorm:"size:2048;not null;comment:封面"` + Price int64 `json:"price" gorm:"size:11;comment:价格"` + Labels *[]string `json:"labels" gorm:"serializer:json;comment:标签数组"` + Images *[]string `json:"images" gorm:"serializer:json;comment:详图数组"` + Videos *[]string `json:"videos" gorm:"serializer:json;comment:视频数组"` + WatchAvatars *[]string `json:"watchAvatars" gorm:"serializer:json;comment:围观人数头像"` + WatchNum int `json:"watchNum" gorm:"size:11;comment:围观人数"` + Area float64 `json:"area" gorm:"not null;comment:占地面积"` + OpenAt string `json:"openAt" gorm:"comment:开盘时间"` + MasterType string `json:"masterType" gorm:"comment:主力户型"` + Address string `json:"address" gorm:"size:1024;not null;comment:楼盘地址"` + Latitude float64 `json:"latitude" gorm:"comment:经度"` + Longitude float64 `json:"longitude" gorm:"comment:维度"` + LayOuts *[]LayOut `json:"layOuts" gorm:"serializer:json;comment:户型图数组"` + Advisers *[]Adviser `json:"advisers" gorm:"serializer:json;comment:顾问数组"` + AreaID uint `json:"areaId" gorm:"index;comment:城市id"` + Detail Detail `json:"detail" gorm:"serializer:json;comment:项目详情"` + Surroundings *Surroundings `json:"surroundings" gorm:"serializer:json;comment:周边设施"` + Status string `json:"status" gorm:"size:20;index;comment:状态"` +} +type Detail struct { + BasicInfo BasicInfo `json:"basicInfo"` + SalesInfo SalesInfo `json:"salesInfo"` + CommunityInfo CommunityInfo `json:"communityInfo"` +} + +type BasicInfo struct { + Area string `json:"area"` + Address string `json:"address"` + Typer string `json:"type"` + UnitStruct string `json:"unitStruct"` + Status string `json:"status"` + MasterType string `json:"masterType"` + ShowAddress string `json:"showAddress"` + BuildingArea string `json:"buildingarea"` +} +type SalesInfo struct { + Status string `json:"status"` + Duration string `json:"duration"` + SalesType string `json:"salesType"` + SalesAddress string `json:"salesAddress"` + SalesPhone string `json:"salesPhone"` +} +type CommunityInfo struct { + PackingNum string `json:"packingNum"` + RoomNUM string `json:"roomNUM"` + GreeningRate string `json:"greeningRate"` + R19009Ratio string `json:"r19009Ratio"` + R19010Ratio string `json:"r19010Ratio"` + R19011Ratio string `json:"r19011Ratio"` + PropertyType string `json:"propertyType"` + PropertyName string `json:"propertyName"` + PropertyPrice string `json:"propertyPrice"` +} +type Surroundings struct { + BusNum int `json:"busNum"` + SchoolNum int `json:"schoolNum"` + HospitalNum int `json:"hospitalNum"` + LifeNum int `json:"lifeNum"` + MetroNum int `json:"metroNum"` +} +type LayOut struct { + Img string `json:"img"` + Title string `json:"title"` + Price string `json:"price"` + Labels *[]string `json:"labels"` + Area float64 `json:"area"` + Direction string `json:"direction"` +} +type Adviser struct { + Avatar string `json:"avatar"` + Name string `json:"name"` + Phone string `json:"phone"` +} + +// Defining the query parameters for the `Activity` struct. +type HouseQueryParam struct { + util.PaginationParam + LikeName string `form:"name" ` + LikeTitle string `form:"title" ` + Status string `form:"status" ` + AreaID uint `form:"areaId"` +} + +// Defining the query options for the `Activity` struct. +type HouseQueryOptions struct { + util.QueryOptions +} + +// Defining the query result for the `Activity` struct. +type HouseQueryResult struct { + Data Houses + PageResult *util.PaginationResult +} + +// Defining the slice of `Activity` struct. +type Houses []*House + +// Defining the data structure for creating a `Activity` struct. +type HouseForm struct { + Name string `json:"name" ` + Title string `json:"title" ` + Cover string `json:"cover" ` + Price int64 `json:"price" ` + Labels *[]string `json:"labels" ` + Images *[]string `json:"images" ` + Videos *[]string `json:"videos"` + WatchAvatars *[]string `json:"watchAvatars" ` + WatchNum int `json:"watchNum" ` + Area float64 `json:"area" ` + OpenAt string `json:"openAt"` + MasterType string `json:"masterType" ` + Address string `json:"address" ` + Latitude float64 `json:"latitude" ` + Longitude float64 `json:"longitude" ` + LayOuts *[]LayOut `json:"layOuts" ` + Advisers *[]Adviser `json:"advisers" ` + AreaID uint `json:"areaId" ` + Detail Detail `json:"detail" ` + Surroundings *Surroundings `json:"surroundings"` + Status string `json:"status" ` +} + +// A validation function for the `ActivityForm` struct. +func (a *HouseForm) Validate() error { + return nil +} + +// Convert `ActivityForm` to `Activity` object. +func (a *HouseForm) FillTo(house *House) error { + house.Name = a.Name + house.Title = a.Title + house.Cover = a.Cover + house.Price = a.Price + house.Labels = a.Labels + house.Images = a.Images + house.Videos = a.Videos + house.WatchAvatars = a.WatchAvatars + house.WatchNum = a.WatchNum + house.Area = a.Area + house.OpenAt = a.OpenAt + house.MasterType = a.MasterType + house.Address = a.Address + house.Latitude = a.Latitude + house.Longitude = a.Longitude + house.LayOuts = a.LayOuts + house.Advisers = a.Advisers + house.AreaID = a.AreaID + house.Detail = a.Detail + house.Status = a.Status + return nil +} diff --git a/internal/mods/activity/schema/houseArticle.go b/internal/mods/activity/schema/houseArticle.go new file mode 100644 index 0000000..a005f6f --- /dev/null +++ b/internal/mods/activity/schema/houseArticle.go @@ -0,0 +1,67 @@ +package schema + +import ( + "github.guxuan/haibei/pkg/util" +) + +// Defining the `Activity` struct. +type HouseArticle struct { + util.BaseModel + HouseID uint `json:"houseId" gorm:"index;comment:房源id"` + Title string `json:"title" gorm:"size:128;not null;index;comment:标题"` + Cover string `json:"cover" gorm:"size:2048;not null;comment:封面"` + Name string `json:"name" gorm:"size:1024;comment:作者名字"` + PushAt string `json:"pushAt" gorm:"index;comment:发布时间"` + Content string `json:"content" gorm:"type:text;comment:内容详情"` + Status string `json:"status" gorm:"size:20;index;comment:状态"` +} + +// Defining the query parameters for the `Activity` struct. +type HouseArticleQueryParam struct { + util.PaginationParam + LikeName string `form:"name" ` + LikeTitle string `form:"title" ` + Status string `form:"status" ` + AreaID uint `form:"areaId"` +} + +// Defining the query options for the `Activity` struct. +type HouseArticleQueryOptions struct { + util.QueryOptions +} + +// Defining the query result for the `Activity` struct. +type HouseArticleQueryResult struct { + Data HouseArticles + PageResult *util.PaginationResult +} + +// Defining the slice of `Activity` struct. +type HouseArticles []*HouseArticle + +// Defining the data structure for creating a `Activity` struct. +type HouseArticleForm struct { + HouseID uint `json:"houseId"` + Title string `json:"title"` + Cover string `json:"cover"` + Name string `json:"name"` + PushAt string `json:"pushAt"` + Content string `json:"content"` + Status string `json:"status"` +} + +// A validation function for the `ActivityForm` struct. +func (a *HouseArticleForm) Validate() error { + return nil +} + +// Convert `ActivityForm` to `Activity` object. +func (a *HouseArticleForm) FillTo(houseArticle *HouseArticle) error { + houseArticle.Name = a.Name + houseArticle.Title = a.Title + houseArticle.Cover = a.Cover + houseArticle.PushAt = a.PushAt + houseArticle.Content = a.Content + houseArticle.Status = a.Status + return nil +} diff --git a/internal/mods/activity/schema/qeustion_detail.go b/internal/mods/activity/schema/qeustion_detail.go new file mode 100644 index 0000000..badd101 --- /dev/null +++ b/internal/mods/activity/schema/qeustion_detail.go @@ -0,0 +1,66 @@ +package schema + +import ( + "github.guxuan/haibei/pkg/util" +) + +// Defining the `QeustionDetail` struct. +type QeustionDetail struct { + util.BaseModel + QuestionID uint `json:"questionId" gorm:"index;"` + CustomerID uint `json:"customerId" gorm:"index;"` + Content *[]QuestionDetailTable `json:"content" gorm:"serializer:json;comment:问卷表单详情"` + Status string `json:"status" gorm:"size:20;index;comment:状态"` +} +type QuestionDetailTable struct { + Label string `json:"label"` + Typer string `json:"type"` + Required bool `json:"required"` + Options []string `json:"options"` + Answer string `json:"answer"` +} + +// Defining the query parameters for the `QeustionDetail` struct. +type QeustionDetailQueryParam struct { + util.PaginationParam + QuestionID uint `form:"questionId" ` + CustomerID uint `form:"customerId" ` + Status string `form:"status" ` +} + +// Defining the query options for the `QeustionDetail` struct. +type QeustionDetailQueryOptions struct { + util.QueryOptions +} + +// Defining the query result for the `QeustionDetail` struct. +type QeustionDetailQueryResult struct { + Data QeustionDetails + PageResult *util.PaginationResult +} + +// Defining the slice of `QeustionDetail` struct. +type QeustionDetails []*QeustionDetail + +// Defining the data structure for creating a `QeustionDetail` struct. +type QeustionDetailForm struct { + QuestionID uint `json:"questionId" ` + CustomerID uint `json:"customerId" ` + Content *[]QuestionDetailTable `json:"content" ` + Status string `json:"status" ` +} + +// A validation function for the `QeustionDetailForm` struct. +func (a *QeustionDetailForm) Validate() error { + return nil +} + +// Convert `QeustionDetailForm` to `QeustionDetail` object. +func (a *QeustionDetailForm) FillTo(qeustionDetail *QeustionDetail) error { + qeustionDetail.CustomerID = a.CustomerID + qeustionDetail.QuestionID = a.QuestionID + qeustionDetail.Content = a.Content + qeustionDetail.Status = a.Status + + return nil +} diff --git a/internal/mods/activity/schema/questionnaire.go b/internal/mods/activity/schema/questionnaire.go new file mode 100644 index 0000000..6894655 --- /dev/null +++ b/internal/mods/activity/schema/questionnaire.go @@ -0,0 +1,78 @@ +package schema + +import ( + "time" + + "github.guxuan/haibei/pkg/util" +) + +// Defining the `Questionnaire` struct. +type Questionnaire struct { + util.BaseModel + Title string `json:"title" gorm:"size:128;not null;index;comment:标题"` + Cover string `json:"cover" gorm:"size:2048;not null;comment:封面"` + StartAt *time.Time `json:"startAt" gorm:"not null;comment:开始时间"` + EndAt *time.Time `json:"endAt" gorm:"not null;comment:结束时间"` + Content *[]QuestionnaireTable `json:"content" gorm:"serializer:json;comment:问卷表单详情"` + Point int `json:"point" gorm:"not null;default:0;comment:问卷获得积分积分"` + AreaID uint `json:"areaId" gorm:"index;comment:城市id"` + Status string `json:"status" gorm:"size:20;index;comment:状态"` +} +type QuestionnaireTable struct { + Label string `json:"label"` + Typer string `json:"type"` + Required bool `json:"required"` + Options []string `json:"options"` +} + +// Defining the query parameters for the `Questionnaire` struct. +type QuestionnaireQueryParam struct { + util.PaginationParam + LikeTitle string `form:"title" ` + Status string `form:"status"` + AreaID uint `form:"areaId"` +} + +// Defining the query options for the `Questionnaire` struct. +type QuestionnaireQueryOptions struct { + util.QueryOptions +} + +// Defining the query result for the `Questionnaire` struct. +type QuestionnaireQueryResult struct { + Data Questionnaires + PageResult *util.PaginationResult +} + +// Defining the slice of `Questionnaire` struct. +type Questionnaires []*Questionnaire + +// Defining the data structure for creating a `Questionnaire` struct. +type QuestionnaireForm struct { + Title string `json:"title" ` + Cover string `json:"cover"` + StartAt *time.Time `json:"startAt"` + EndAt *time.Time `json:"endAt"` + AreaID uint `json:"areaId"` + Content *[]QuestionnaireTable `json:"content"` + Point int `json:"point" ` + Status string `json:"status"` +} + +// A validation function for the `QuestionnaireForm` struct. +func (a *QuestionnaireForm) Validate() error { + return nil +} + +// Convert `QuestionnaireForm` to `Questionnaire` object. +func (a *QuestionnaireForm) FillTo(questionnaire *Questionnaire) error { + questionnaire.Title = a.Title + questionnaire.Cover = a.Cover + questionnaire.StartAt = a.StartAt + questionnaire.EndAt = a.EndAt + questionnaire.AreaID = a.AreaID + questionnaire.Content = a.Content + questionnaire.Point = a.Point + questionnaire.Status = a.Status + return nil +} diff --git a/internal/mods/activity/wire.go b/internal/mods/activity/wire.go new file mode 100644 index 0000000..21ab41f --- /dev/null +++ b/internal/mods/activity/wire.go @@ -0,0 +1,24 @@ +package activity + +import ( + "github.com/google/wire" + "github.guxuan/haibei/internal/mods/activity/api" + "github.guxuan/haibei/internal/mods/activity/biz" + "github.guxuan/haibei/internal/mods/activity/dal" +) + +var Set = wire.NewSet( + wire.Struct(new(Activity), "*"), + wire.Struct(new(dal.Activity), "*"), + wire.Struct(new(biz.Activity), "*"), + wire.Struct(new(api.Activity), "*"), + wire.Struct(new(dal.Questionnaire), "*"), + wire.Struct(new(biz.Questionnaire), "*"), + wire.Struct(new(api.Questionnaire), "*"), + wire.Struct(new(dal.ActivityDetail), "*"), + wire.Struct(new(biz.ActivityDetail), "*"), + wire.Struct(new(api.ActivityDetail), "*"), + wire.Struct(new(dal.QeustionDetail), "*"), + wire.Struct(new(biz.QeustionDetail), "*"), + wire.Struct(new(api.QeustionDetail), "*"), +) diff --git a/internal/mods/app/api/app.api.go b/internal/mods/app/api/app.api.go new file mode 100644 index 0000000..193df24 --- /dev/null +++ b/internal/mods/app/api/app.api.go @@ -0,0 +1,136 @@ +package api + +import ( + "github.com/gin-gonic/gin" + activityBiz "github.guxuan/haibei/internal/mods/activity/biz" + "github.guxuan/haibei/internal/mods/app/biz" + commonBiz "github.guxuan/haibei/internal/mods/common/biz" + commonSchema "github.guxuan/haibei/internal/mods/common/schema" + customerBiz "github.guxuan/haibei/internal/mods/customer/biz" + customerSchema "github.guxuan/haibei/internal/mods/customer/schema" + ProductBiz "github.guxuan/haibei/internal/mods/product/biz" + + "github.guxuan/haibei/pkg/util" +) + +// Defining the `App` api. +type App struct { + AppBIZ *biz.App + CustomerBIZ *customerBiz.Customer + BannerBIZ *commonBiz.Banner + ActivityBIZ *activityBiz.Activity + QuestionnaireBIZ *activityBiz.Questionnaire + ProductBIZ *ProductBiz.Product +} + +// @Tags APP端 +// @Security ApiKeyAuth +// @Summary 小程序登陆 +// @Success 200 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/apps/login [post] +func (a *App) Login(c *gin.Context) { + ctx := c.Request.Context() + item := new(customerSchema.CustomerLoginForm) + if err := util.ParseJSON(c, item); err != nil { + util.ResError(c, err) + return + } + data, err := a.CustomerBIZ.Login(ctx, item) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, data) +} + +// @Tags APP端 +// @Security ApiKeyAuth +// @Summary 发送验证码 +// @Success 200 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/apps/sms/{phone} [get] +func (a *App) SendSms(c *gin.Context) { + ctx := c.Request.Context() + phone := c.Param("phone") + data, err := a.CustomerBIZ.SendSms(ctx, phone) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, data) +} + +// @Tags APP端 +// @Security ApiKeyAuth +// @Summary 小程序绑定手机号 +// @Success 200 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/apps/bind [post] +func (a *App) BindPhone(c *gin.Context) { + ctx := c.Request.Context() + item := new(customerSchema.CustomerBindPhoneForm) + if err := util.ParseJSON(c, item); err != nil { + util.ResError(c, err) + return + } + data, err := a.CustomerBIZ.BindPhone(ctx, item) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, data) +} + +// @Tags APP端 +// @Security ApiKeyAuth +// @Summary 首页banner +// @Success 200 {object} util.ResponseResult{data=[]commonSchema.Banner} +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/apps/banner [get] +func (a *App) GetBannerList(c *gin.Context) { + ctx := c.Request.Context() + item := new(commonSchema.BannerQueryParam) + if err := util.ParseJSON(c, item); err != nil { + util.ResError(c, err) + return + } + data, err := a.BannerBIZ.Query(ctx, *item) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, data) +} + +// @Tags APP端 +// @Security ApiKeyAuth +// @Summary 首页接口 +// @Success 200 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/apps/home [post] +func (a *App) HomeDetails(c *gin.Context) { + ctx := c.Request.Context() + uid, err := a.AppBIZ.GetLoginUid(ctx) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, uid) +} + +// @Tags APP端 +// @Security ApiKeyAuth +// @Summary 首页接口 +// @Success 200 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/apps/mine [post] +func (a *App) MineDetails(c *gin.Context) { + //ctx := c.Request.Context() +} diff --git a/internal/mods/app/backResp/home.go b/internal/mods/app/backResp/home.go new file mode 100644 index 0000000..8485148 --- /dev/null +++ b/internal/mods/app/backResp/home.go @@ -0,0 +1,9 @@ +package backResp + +import "github.guxuan/haibei/internal/mods/common/schema" + +type HomePage struct { + BannerList schema.Banners `json:"bannerList"` + NoticeList schema.Notices `json:"noticeList"` + ActivityList schema.Notices `json:"activityList"` +} diff --git a/internal/mods/app/biz/app.biz.go b/internal/mods/app/biz/app.biz.go new file mode 100644 index 0000000..881ebb5 --- /dev/null +++ b/internal/mods/app/biz/app.biz.go @@ -0,0 +1,209 @@ +package biz + +import ( + "context" + "errors" + "fmt" + "gorm.io/gorm" + "strconv" + "time" + + //"time" + + "github.guxuan/haibei/internal/mods/app/dal" + //"github.guxuan/haibei/internal/mods/app/schema" + activityDal "github.guxuan/haibei/internal/mods/activity/dal" + activitySchema "github.guxuan/haibei/internal/mods/activity/schema" + commonDal "github.guxuan/haibei/internal/mods/common/dal" + commonSchema "github.guxuan/haibei/internal/mods/common/schema" + customerDal "github.guxuan/haibei/internal/mods/customer/dal" + customerSchema "github.guxuan/haibei/internal/mods/customer/schema" + productDal "github.guxuan/haibei/internal/mods/product/dal" + productSchema "github.guxuan/haibei/internal/mods/product/schema" + //"github.guxuan/haibei/pkg/errors" + "github.guxuan/haibei/pkg/util" +) + +// Defining the `App` business logic. +type App struct { + DB *gorm.DB + Trans *util.Trans + AppDAL *dal.App + BannerDal *commonDal.Banner + NoticeDal *commonDal.Notice + ProductDal *productDal.Product + ProductCategoryDal *productDal.ProductCategory + ProductOrderDal *customerDal.ProductOrder + ActivityDal *activityDal.Activity + ActivityDetailDal *activityDal.ActivityDetail + QuestionnaireDal *activityDal.Questionnaire + QeustionDetailDal *activityDal.QeustionDetail + CustomerDal *customerDal.Customer + BalanceDal *customerDal.Balance +} + +func (a *App) QueryBannerList(ctx context.Context, areaId uint) (*[]commonSchema.Banner, error) { + query := commonDal.GetBannerDB(ctx, a.DB) + if areaId > 0 { + query.Where("area_id = ?", areaId) + } + var list []commonSchema.Banner + query.Order("sequence DESC").Limit(3).Find(&list) + return &list, nil +} +func (a *App) QueryNoticeList(ctx context.Context, areaId uint) (*[]commonSchema.Notice, error) { + query := commonDal.GetNoticeDB(ctx, a.DB) + if areaId > 0 { + query.Where("area_id = ?", areaId) + } + var list []commonSchema.Notice + query.Order("sequence DESC").Limit(6).Find(&list) + return &list, nil +} + +func (a *App) QueryHomeProductList(ctx context.Context, isHot bool, areaId uint) (*[]productSchema.Product, error) { + query := productDal.GetProductDB(ctx, a.DB).Preload("Category").Where("status = ?", "enabled") + query.Where("area_id = ?", areaId) + if isHot { + query.Where("is_hot = ?", 1) + } + var list []productSchema.Product + query.Order("createAt DESC").Find(&list) + return &list, nil +} + +func (a *App) QueryProductDetails(ctx context.Context, id uint) (productSchema.Product, error) { + query := productDal.GetProductDB(ctx, a.DB).Preload("Category").Where("id = ?", id) + + var info productSchema.Product + query.First(&info) + return info, nil +} + +func (a *App) QueryHomeActivityList(ctx context.Context, areaId uint) (activitySchema.Activities, error) { + query := activityDal.GetActivityDB(ctx, a.DB).Where("status = ?", "enabled") + query.Where("area_id = ?", areaId) + var list activitySchema.Activities + query.Order("createAt DESC").Find(list) + return list, nil + +} + +func (a *App) QueryActivityDetails(ctx context.Context, customerId, id uint) (activitySchema.Activity, *activitySchema.ActivityDetail, error) { + query := activityDal.GetActivityDB(ctx, a.DB).Where("id = ?", id) + var info activitySchema.Activity + query.First(&info) + result, _ := a.ActivityDetailDal.GetByCustomerIDAndActivityID(ctx, customerId, id) + return info, result, nil +} + +func (a *App) QueryHomeQuestionsList(ctx context.Context, areaId uint) (activitySchema.Questionnaires, error) { + query := activityDal.GetQuestionnaireDB(ctx, a.DB).Where("status = ?", "enabled") + query.Where("area_id = ?", areaId) + var list activitySchema.Questionnaires + query.Order("createAt DESC").Find(list) + return list, nil + +} + +func (a *App) QueryQuestionsDetails(ctx context.Context, customerId, id uint) (activitySchema.Questionnaire, *activitySchema.QeustionDetail, error) { + query := activityDal.GetQuestionnaireDB(ctx, a.DB).Where("id = ?", id) + var info activitySchema.Questionnaire + query.First(&info) + result, _ := a.QeustionDetailDal.GetByCustomerIDAndActivityID(ctx, customerId, id) + return info, result, nil +} + +func (a *App) AddProductToCustomer(ctx context.Context, customerID, productID, num uint) error { + customer, err2 := a.CustomerDal.Get(ctx, customerID, customerSchema.CustomerQueryOptions{}) + if err2 != nil { + return errors.New("用户未找到") + } + + product, err := a.ProductDal.Get(ctx, productID, productSchema.ProductQueryOptions{}) + if err != nil { + return errors.New("商品未找到") + } + if product.Stock < int(num) { + return errors.New("商品库存不足") + } + if product.Status != "enabled" { + return errors.New("商品状态不可用") + } + if product.ExpireAt.Before(time.Now()) { + return errors.New("商品已过期") + } + if customer.Balance < product.Price*int(num) { + return errors.New("积分不足") + } + info, err := a.ProductOrderDal.ExistsByCustomerID(ctx, customerID, productID) + if err != nil { + return errors.New("系统繁忙,请稍后再试!") + } + if info != nil && len(*info) >= product.MaxNum { + return errors.New("兑换数量已达到上限") + } + + orderInfo := customerSchema.ProductOrder{ + CustomerID: customerID, + ProductID: productID, + MentorID: customer.MentorID, + Status: "UNCHECK", + } + return a.Trans.Exec(ctx, func(ctx context.Context) error { + //创建商品订单 + err = a.ProductOrderDal.Create(ctx, &orderInfo) + if err != nil { + return errors.New("系统繁忙,请稍后再试!") + } + //修改库存 + product.Stock-- + err = a.ProductDal.Update(ctx, product) + if err != nil { + return errors.New("系统繁忙,请稍后再试!") + } + //积分变动 + oldBalance := customer.Balance + change := product.Price * int(num) + newBalance := oldBalance - change + customer.Balance = newBalance + err = a.CustomerDal.Update(ctx, customer) + if err != nil { + return errors.New("系统繁忙,请稍后再试!") + } + var record = customerSchema.Balance{ + CustomerID: customerID, + Before: oldBalance, + After: newBalance, + Change: -change, + Reason: fmt.Sprintf("用户通过小程序兑换商品-%s", product.Name), + Typer: "兑换商品", + OperatorID: productID, + CreatorID: "", + } + err = a.BalanceDal.Create(ctx, &record) + if err != nil { + return errors.New("系统繁忙,请稍后再试!") + } + return nil + }) + +} + +func (a *App) RegisterActivity(ctx context.Context, customerID, activityID uint) error { + return nil +} + +func (a *App) RegisterQuestion(ctx context.Context, customerID, questionID uint, questionBody *[]activitySchema.QuestionDetailTable) error { + return nil +} + +func (a *App) GetLoginUid(ctx context.Context) (uint, error) { + userID := util.FromUserID(ctx) + u64, err := strconv.ParseUint(fmt.Sprintf("%v", userID), 10, 64) + if err != nil { + fmt.Println("转换错误:", err) + return 0, errors.New("登录id获取失败") + } + return uint(u64), nil +} diff --git a/internal/mods/app/dal/app.dal.go b/internal/mods/app/dal/app.dal.go new file mode 100644 index 0000000..1aba9af --- /dev/null +++ b/internal/mods/app/dal/app.dal.go @@ -0,0 +1,83 @@ +package dal + +import ( + "context" + + "github.guxuan/haibei/internal/mods/app/schema" + "github.guxuan/haibei/pkg/errors" + "github.guxuan/haibei/pkg/util" + "gorm.io/gorm" +) + +// Get app storage instance +func GetAppDB(ctx context.Context, defDB *gorm.DB) *gorm.DB { + return util.GetDB(ctx, defDB).Model(new(schema.App)) +} + +// Defining the `App` data access object. +type App struct { + DB *gorm.DB +} + +// Query apps from the database based on the provided parameters and options. +func (a *App) Query(ctx context.Context, params schema.AppQueryParam, opts ...schema.AppQueryOptions) (*schema.AppQueryResult, error) { + var opt schema.AppQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + db := GetAppDB(ctx, a.DB) + + var list schema.Apps + pageResult, err := util.WrapPageQuery(ctx, db, params.PaginationParam, opt.QueryOptions, &list) + if err != nil { + return nil, errors.WithStack(err) + } + + queryResult := &schema.AppQueryResult{ + PageResult: pageResult, + Data: list, + } + return queryResult, nil +} + +// Get the specified app from the database. +func (a *App) Get(ctx context.Context, id string, opts ...schema.AppQueryOptions) (*schema.App, error) { + var opt schema.AppQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + item := new(schema.App) + ok, err := util.FindOne(ctx, GetAppDB(ctx, a.DB).Where("id=?", id), opt.QueryOptions, item) + if err != nil { + return nil, errors.WithStack(err) + } else if !ok { + return nil, nil + } + return item, nil +} + +// Exists checks if the specified app exists in the database. +func (a *App) Exists(ctx context.Context, id string) (bool, error) { + ok, err := util.Exists(ctx, GetAppDB(ctx, a.DB).Where("id=?", id)) + return ok, errors.WithStack(err) +} + +// Create a new app. +func (a *App) Create(ctx context.Context, item *schema.App) error { + result := GetAppDB(ctx, a.DB).Create(item) + return errors.WithStack(result.Error) +} + +// Update the specified app in the database. +func (a *App) Update(ctx context.Context, item *schema.App) error { + result := GetAppDB(ctx, a.DB).Where("id=?", item.ID).Select("*").Omit("created_at").Updates(item) + return errors.WithStack(result.Error) +} + +// Delete the specified app from the database. +func (a *App) Delete(ctx context.Context, id string) error { + result := GetAppDB(ctx, a.DB).Where("id=?", id).Delete(new(schema.App)) + return errors.WithStack(result.Error) +} diff --git a/internal/mods/app/main.go b/internal/mods/app/main.go new file mode 100644 index 0000000..fe30615 --- /dev/null +++ b/internal/mods/app/main.go @@ -0,0 +1,44 @@ +package app + +import ( + "context" + + "github.guxuan/haibei/internal/config" + + "github.com/gin-gonic/gin" + "github.guxuan/haibei/internal/mods/app/api" + "github.guxuan/haibei/internal/mods/app/schema" + "gorm.io/gorm" +) + +type App struct { + DB *gorm.DB + AppAPI *api.App +} + +func (a *App) AutoMigrate(ctx context.Context) error { + return a.DB.AutoMigrate(new(schema.App)) +} + +func (a *App) Init(ctx context.Context) error { + if config.C.Storage.DB.AutoMigrate { + if err := a.AutoMigrate(ctx); err != nil { + return err + } + } + return nil +} + +func (a *App) RegisterV1Routers(ctx context.Context, v1 *gin.RouterGroup) error { + app := v1.Group("apps") + { + app.POST("login", a.AppAPI.Login) + app.GET("sms/:phone", a.AppAPI.SendSms) + app.POST("bind", a.AppAPI.BindPhone) + } + return nil +} + +func (a *App) Release(ctx context.Context) error { + return nil +} diff --git a/internal/mods/app/schema/app.go b/internal/mods/app/schema/app.go new file mode 100644 index 0000000..8f5b335 --- /dev/null +++ b/internal/mods/app/schema/app.go @@ -0,0 +1,47 @@ +package schema + +import ( + "time" + + "github.guxuan/haibei/pkg/util" +) + +// Defining the `App` struct. +type App struct { + ID string `json:"id" gorm:"size:20;primaryKey;"` // Unique ID + CreatedAt time.Time `json:"created_at" gorm:"index;"` // Create time + UpdatedAt time.Time `json:"updated_at" gorm:"index;"` // Update time +} + +// Defining the query parameters for the `App` struct. +type AppQueryParam struct { + util.PaginationParam +} + +// Defining the query options for the `App` struct. +type AppQueryOptions struct { + util.QueryOptions +} + +// Defining the query result for the `App` struct. +type AppQueryResult struct { + Data Apps + PageResult *util.PaginationResult +} + +// Defining the slice of `App` struct. +type Apps []*App + +// Defining the data structure for creating a `App` struct. +type AppForm struct { +} + +// A validation function for the `AppForm` struct. +func (a *AppForm) Validate() error { + return nil +} + +// Convert `AppForm` to `App` object. +func (a *AppForm) FillTo(app *App) error { + return nil +} diff --git a/internal/mods/app/wire.go b/internal/mods/app/wire.go new file mode 100644 index 0000000..f06519a --- /dev/null +++ b/internal/mods/app/wire.go @@ -0,0 +1,15 @@ +package app + +import ( + "github.com/google/wire" + "github.guxuan/haibei/internal/mods/app/api" + "github.guxuan/haibei/internal/mods/app/biz" + "github.guxuan/haibei/internal/mods/app/dal" +) + +var Set = wire.NewSet( + wire.Struct(new(App), "*"), + wire.Struct(new(dal.App), "*"), + wire.Struct(new(biz.App), "*"), + wire.Struct(new(api.App), "*"), +) diff --git a/internal/mods/common/api/area.api.go b/internal/mods/common/api/area.api.go new file mode 100644 index 0000000..6ca8e33 --- /dev/null +++ b/internal/mods/common/api/area.api.go @@ -0,0 +1,145 @@ +package api + +import ( + "github.com/gin-gonic/gin" + "github.guxuan/haibei/internal/mods/common/biz" + "github.guxuan/haibei/internal/mods/common/schema" + "github.guxuan/haibei/pkg/util" +) + +// Defining the `Area` api. +type Area struct { + AreaBIZ *biz.Area +} + +// @Tags 区域模块 +// @Security ApiKeyAuth +// @Summary Query area list +// @Param current query int true "pagination index" default(1) +// @Param pageSize query int true "pagination size" default(10) +// @Success 200 {object} util.ResponseResult{data=[]schema.Area} +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/areas [get] +func (a *Area) Query(c *gin.Context) { + ctx := c.Request.Context() + var params schema.AreaQueryParam + if err := util.ParseQuery(c, ¶ms); err != nil { + util.ResError(c, err) + return + } + + result, err := a.AreaBIZ.Query(ctx, params) + if err != nil { + util.ResError(c, err) + return + } + util.ResPage(c, result.Data, result.PageResult) +} + +// @Tags 区域模块 +// @Security ApiKeyAuth +// @Summary Get area record by ID +// @Param id path string true "unique id" +// @Success 200 {object} util.ResponseResult{data=schema.Area} +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/areas/{id} [get] +func (a *Area) Get(c *gin.Context) { + ctx := c.Request.Context() + id, err := util.GetQueryID(c) + if err != nil { + util.ResError(c, err) + return + } + item, err := a.AreaBIZ.Get(ctx, id) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, item) +} + +// @Tags 区域模块 +// @Security ApiKeyAuth +// @Summary Create area record +// @Param body body schema.AreaForm true "Request body" +// @Success 200 {object} util.ResponseResult{data=schema.Area} +// @Failure 400 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/areas [post] +func (a *Area) Create(c *gin.Context) { + ctx := c.Request.Context() + item := new(schema.AreaForm) + if err := util.ParseJSON(c, item); err != nil { + util.ResError(c, err) + return + } else if err := item.Validate(); err != nil { + util.ResError(c, err) + return + } + + result, err := a.AreaBIZ.Create(ctx, item) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, result) +} + +// @Tags 区域模块 +// @Security ApiKeyAuth +// @Summary Update area record by ID +// @Param id path string true "unique id" +// @Param body body schema.AreaForm true "Request body" +// @Success 200 {object} util.ResponseResult +// @Failure 400 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/areas/{id} [put] +func (a *Area) Update(c *gin.Context) { + ctx := c.Request.Context() + item := new(schema.AreaForm) + if err := util.ParseJSON(c, item); err != nil { + util.ResError(c, err) + return + } else if err := item.Validate(); err != nil { + util.ResError(c, err) + return + } + id, err := util.GetQueryID(c) + if err != nil { + util.ResError(c, err) + return + } + err = a.AreaBIZ.Update(ctx, id, item) + if err != nil { + util.ResError(c, err) + return + } + util.ResOK(c) +} + +// @Tags 区域模块 +// @Security ApiKeyAuth +// @Summary Delete area record by ID +// @Param id path string true "unique id" +// @Success 200 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/areas/{id} [delete] +func (a *Area) Delete(c *gin.Context) { + ctx := c.Request.Context() + id, err := util.GetQueryID(c) + if err != nil { + util.ResError(c, err) + return + } + err = a.AreaBIZ.Delete(ctx, id) + if err != nil { + util.ResError(c, err) + return + } + util.ResOK(c) +} diff --git a/internal/mods/common/api/banner.api.go b/internal/mods/common/api/banner.api.go new file mode 100644 index 0000000..cedba90 --- /dev/null +++ b/internal/mods/common/api/banner.api.go @@ -0,0 +1,144 @@ +package api + +import ( + "github.com/gin-gonic/gin" + "github.guxuan/haibei/internal/mods/common/biz" + "github.guxuan/haibei/internal/mods/common/schema" + "github.guxuan/haibei/pkg/util" +) + +// Defining the `Banner` api. +type Banner struct { + BannerBIZ *biz.Banner +} + +// @Tags 首页轮播图模块 +// @Security ApiKeyAuth +// @Summary Query banner list +// @Param current query int true "pagination index" default(1) +// @Param pageSize query int true "pagination size" default(10) +// @Success 200 {object} util.ResponseResult{data=[]schema.Banner} +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/banners [get] +func (a *Banner) Query(c *gin.Context) { + ctx := c.Request.Context() + var params schema.BannerQueryParam + if err := util.ParseQuery(c, ¶ms); err != nil { + util.ResError(c, err) + return + } + result, err := a.BannerBIZ.Query(ctx, params) + if err != nil { + util.ResError(c, err) + return + } + util.ResPage(c, result.Data, result.PageResult) +} + +// @Tags 首页轮播图模块 +// @Security ApiKeyAuth +// @Summary Get banner record by ID +// @Param id path string true "unique id" +// @Success 200 {object} util.ResponseResult{data=schema.Banner} +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/banners/{id} [get] +func (a *Banner) Get(c *gin.Context) { + ctx := c.Request.Context() + id, err := util.GetQueryID(c) + if err != nil { + util.ResError(c, err) + return + } + item, err := a.BannerBIZ.Get(ctx, id) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, item) +} + +// @Tags 首页轮播图模块 +// @Security ApiKeyAuth +// @Summary Create banner record +// @Param body body schema.BannerForm true "Request body" +// @Success 200 {object} util.ResponseResult{data=schema.Banner} +// @Failure 400 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/banners [post] +func (a *Banner) Create(c *gin.Context) { + ctx := c.Request.Context() + item := new(schema.BannerForm) + if err := util.ParseJSON(c, item); err != nil { + util.ResError(c, err) + return + } else if err := item.Validate(); err != nil { + util.ResError(c, err) + return + } + + result, err := a.BannerBIZ.Create(ctx, item) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, result) +} + +// @Tags 首页轮播图模块 +// @Security ApiKeyAuth +// @Summary Update banner record by ID +// @Param id path string true "unique id" +// @Param body body schema.BannerForm true "Request body" +// @Success 200 {object} util.ResponseResult +// @Failure 400 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/banners/{id} [put] +func (a *Banner) Update(c *gin.Context) { + ctx := c.Request.Context() + item := new(schema.BannerForm) + if err := util.ParseJSON(c, item); err != nil { + util.ResError(c, err) + return + } else if err := item.Validate(); err != nil { + util.ResError(c, err) + return + } + id, err := util.GetQueryID(c) + if err != nil { + util.ResError(c, err) + return + } + err = a.BannerBIZ.Update(ctx, id, item) + if err != nil { + util.ResError(c, err) + return + } + util.ResOK(c) +} + +// @Tags 首页轮播图模块 +// @Security ApiKeyAuth +// @Summary Delete banner record by ID +// @Param id path string true "unique id" +// @Success 200 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/banners/{id} [delete] +func (a *Banner) Delete(c *gin.Context) { + ctx := c.Request.Context() + id, err := util.GetQueryID(c) + if err != nil { + util.ResError(c, err) + return + } + err = a.BannerBIZ.Delete(ctx, id) + if err != nil { + util.ResError(c, err) + return + } + util.ResOK(c) +} diff --git a/internal/mods/common/api/common.api.go b/internal/mods/common/api/common.api.go new file mode 100644 index 0000000..8cf76b1 --- /dev/null +++ b/internal/mods/common/api/common.api.go @@ -0,0 +1,145 @@ +package api + +import ( + "github.com/gin-gonic/gin" + "github.guxuan/haibei/internal/mods/common/biz" + "github.guxuan/haibei/internal/mods/common/schema" + "github.guxuan/haibei/pkg/util" +) + +// Defining the `Common` api. +type Common struct { + CommonBIZ *biz.Common +} + +// @Tags CommonAPI +// @Security ApiKeyAuth +// @Summary Query common list +// @Param current query int true "pagination index" default(1) +// @Param pageSize query int true "pagination size" default(10) +// @Success 200 {object} util.ResponseResult{data=[]schema.Common} +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/commons [get] +func (a *Common) Query(c *gin.Context) { + ctx := c.Request.Context() + var params schema.CommonQueryParam + if err := util.ParseQuery(c, ¶ms); err != nil { + util.ResError(c, err) + return + } + + result, err := a.CommonBIZ.Query(ctx, params) + if err != nil { + util.ResError(c, err) + return + } + util.ResPage(c, result.Data, result.PageResult) +} + +// @Tags CommonAPI +// @Security ApiKeyAuth +// @Summary Get common record by ID +// @Param id path string true "unique id" +// @Success 200 {object} util.ResponseResult{data=schema.Common} +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/commons/{id} [get] +func (a *Common) Get(c *gin.Context) { + ctx := c.Request.Context() + id, err := util.GetQueryID(c) + if err != nil { + util.ResError(c, err) + return + } + item, err := a.CommonBIZ.Get(ctx, id) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, item) +} + +// @Tags CommonAPI +// @Security ApiKeyAuth +// @Summary Create common record +// @Param body body schema.CommonForm true "Request body" +// @Success 200 {object} util.ResponseResult{data=schema.Common} +// @Failure 400 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/commons [post] +func (a *Common) Create(c *gin.Context) { + ctx := c.Request.Context() + item := new(schema.CommonForm) + if err := util.ParseJSON(c, item); err != nil { + util.ResError(c, err) + return + } else if err := item.Validate(); err != nil { + util.ResError(c, err) + return + } + + result, err := a.CommonBIZ.Create(ctx, item) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, result) +} + +// @Tags CommonAPI +// @Security ApiKeyAuth +// @Summary Update common record by ID +// @Param id path string true "unique id" +// @Param body body schema.CommonForm true "Request body" +// @Success 200 {object} util.ResponseResult +// @Failure 400 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/commons/{id} [put] +func (a *Common) Update(c *gin.Context) { + ctx := c.Request.Context() + item := new(schema.CommonForm) + if err := util.ParseJSON(c, item); err != nil { + util.ResError(c, err) + return + } else if err := item.Validate(); err != nil { + util.ResError(c, err) + return + } + id, err := util.GetQueryID(c) + if err != nil { + util.ResError(c, err) + return + } + err = a.CommonBIZ.Update(ctx, id, item) + if err != nil { + util.ResError(c, err) + return + } + util.ResOK(c) +} + +// @Tags CommonAPI +// @Security ApiKeyAuth +// @Summary Delete common record by ID +// @Param id path string true "unique id" +// @Success 200 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/commons/{id} [delete] +func (a *Common) Delete(c *gin.Context) { + ctx := c.Request.Context() + id, err := util.GetQueryID(c) + if err != nil { + util.ResError(c, err) + return + } + err = a.CommonBIZ.Delete(ctx, id) + if err != nil { + util.ResError(c, err) + return + } + util.ResOK(c) +} diff --git a/internal/mods/common/api/company.api.go b/internal/mods/common/api/company.api.go new file mode 100644 index 0000000..6dd3ab4 --- /dev/null +++ b/internal/mods/common/api/company.api.go @@ -0,0 +1,145 @@ +package api + +import ( + "github.com/gin-gonic/gin" + "github.guxuan/haibei/internal/mods/common/biz" + "github.guxuan/haibei/internal/mods/common/schema" + "github.guxuan/haibei/pkg/util" +) + +// Defining the `Company` api. +type Company struct { + CompanyBIZ *biz.Company +} + +// @Tags 项目模块 +// @Security ApiKeyAuth +// @Summary Query company list +// @Param current query int true "pagination index" default(1) +// @Param pageSize query int true "pagination size" default(10) +// @Success 200 {object} util.ResponseResult{data=[]schema.Company} +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/companies [get] +func (a *Company) Query(c *gin.Context) { + ctx := c.Request.Context() + var params schema.CompanyQueryParam + if err := util.ParseQuery(c, ¶ms); err != nil { + util.ResError(c, err) + return + } + + result, err := a.CompanyBIZ.Query(ctx, params) + if err != nil { + util.ResError(c, err) + return + } + util.ResPage(c, result.Data, result.PageResult) +} + +// @Tags 项目模块 +// @Security ApiKeyAuth +// @Summary Get company record by ID +// @Param id path string true "unique id" +// @Success 200 {object} util.ResponseResult{data=schema.Company} +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/companies/{id} [get] +func (a *Company) Get(c *gin.Context) { + ctx := c.Request.Context() + id, err := util.GetQueryID(c) + if err != nil { + util.ResError(c, err) + return + } + item, err := a.CompanyBIZ.Get(ctx, id) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, item) +} + +// @Tags 项目模块 +// @Security ApiKeyAuth +// @Summary Create company record +// @Param body body schema.CompanyForm true "Request body" +// @Success 200 {object} util.ResponseResult{data=schema.Company} +// @Failure 400 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/companies [post] +func (a *Company) Create(c *gin.Context) { + ctx := c.Request.Context() + item := new(schema.CompanyForm) + if err := util.ParseJSON(c, item); err != nil { + util.ResError(c, err) + return + } else if err := item.Validate(); err != nil { + util.ResError(c, err) + return + } + + result, err := a.CompanyBIZ.Create(ctx, item) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, result) +} + +// @Tags 项目模块 +// @Security ApiKeyAuth +// @Summary Update company record by ID +// @Param id path string true "unique id" +// @Param body body schema.CompanyForm true "Request body" +// @Success 200 {object} util.ResponseResult +// @Failure 400 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/companies/{id} [put] +func (a *Company) Update(c *gin.Context) { + ctx := c.Request.Context() + item := new(schema.CompanyForm) + if err := util.ParseJSON(c, item); err != nil { + util.ResError(c, err) + return + } else if err := item.Validate(); err != nil { + util.ResError(c, err) + return + } + id, err := util.GetQueryID(c) + if err != nil { + util.ResError(c, err) + return + } + err = a.CompanyBIZ.Update(ctx, id, item) + if err != nil { + util.ResError(c, err) + return + } + util.ResOK(c) +} + +// @Tags 项目模块 +// @Security ApiKeyAuth +// @Summary Delete company record by ID +// @Param id path string true "unique id" +// @Success 200 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/companies/{id} [delete] +func (a *Company) Delete(c *gin.Context) { + ctx := c.Request.Context() + id, err := util.GetQueryID(c) + if err != nil { + util.ResError(c, err) + return + } + err = a.CompanyBIZ.Delete(ctx, id) + if err != nil { + util.ResError(c, err) + return + } + util.ResOK(c) +} diff --git a/internal/mods/common/api/notice.api.go b/internal/mods/common/api/notice.api.go new file mode 100644 index 0000000..715c9a2 --- /dev/null +++ b/internal/mods/common/api/notice.api.go @@ -0,0 +1,138 @@ +package api + +import ( + "github.com/gin-gonic/gin" + "github.guxuan/haibei/internal/mods/common/biz" + "github.guxuan/haibei/internal/mods/common/schema" + "github.guxuan/haibei/pkg/util" +) + +// Defining the `Notice` api. +type Notice struct { + NoticeBIZ *biz.Notice +} + +// @Tags 公告模块 +// @Security ApiKeyAuth +// @Summary Query notice list +// @Param current query int true "pagination index" default(1) +// @Param pageSize query int true "pagination size" default(10) +// @Success 200 {object} util.ResponseResult{data=[]schema.Notice} +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/notices [get] +func (a *Notice) Query(c *gin.Context) { + ctx := c.Request.Context() + var params schema.NoticeQueryParam + if err := util.ParseQuery(c, ¶ms); err != nil { + util.ResError(c, err) + return + } + + result, err := a.NoticeBIZ.Query(ctx, params) + if err != nil { + util.ResError(c, err) + return + } + util.ResPage(c, result.Data, result.PageResult) +} + +// @Tags 公告模块 +// @Security ApiKeyAuth +// @Summary Get notice record by ID +// @Param id path string true "unique id" +// @Success 200 {object} util.ResponseResult{data=schema.Notice} +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/notices/{id} [get] +func (a *Notice) Get(c *gin.Context) { + ctx := c.Request.Context() + id, err := util.GetQueryID(c) + if err != nil { + util.ResError(c, err) + return + } + item, err := a.NoticeBIZ.Get(ctx, id) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, item) +} + +// @Tags 公告模块 +// @Security ApiKeyAuth +// @Summary Create notice record +// @Param body body schema.NoticeForm true "Request body" +// @Success 200 {object} util.ResponseResult{data=schema.Notice} +// @Failure 400 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/notices [post] +func (a *Notice) Create(c *gin.Context) { + ctx := c.Request.Context() + item := new(schema.NoticeForm) + if err := util.ParseJSON(c, item); err != nil { + util.ResError(c, err) + return + } + result, err := a.NoticeBIZ.Create(ctx, item) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, result) +} + +// @Tags 公告模块 +// @Security ApiKeyAuth +// @Summary Update notice record by ID +// @Param id path string true "unique id" +// @Param body body schema.NoticeForm true "Request body" +// @Success 200 {object} util.ResponseResult +// @Failure 400 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/notices/{id} [put] +func (a *Notice) Update(c *gin.Context) { + ctx := c.Request.Context() + item := new(schema.NoticeForm) + if err := util.ParseJSON(c, item); err != nil { + util.ResError(c, err) + return + } + id, err := util.GetQueryID(c) + if err != nil { + util.ResError(c, err) + return + } + err = a.NoticeBIZ.Update(ctx, id, item) + if err != nil { + util.ResError(c, err) + return + } + util.ResOK(c) +} + +// @Tags 公告模块 +// @Security ApiKeyAuth +// @Summary Delete notice record by ID +// @Param id path string true "unique id" +// @Success 200 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/notices/{id} [delete] +func (a *Notice) Delete(c *gin.Context) { + ctx := c.Request.Context() + id, err := util.GetQueryID(c) + if err != nil { + util.ResError(c, err) + return + } + err = a.NoticeBIZ.Delete(ctx, id) + if err != nil { + util.ResError(c, err) + return + } + util.ResOK(c) +} diff --git a/internal/mods/common/api/receptionCenter.api.go b/internal/mods/common/api/receptionCenter.api.go new file mode 100644 index 0000000..6ad9cd9 --- /dev/null +++ b/internal/mods/common/api/receptionCenter.api.go @@ -0,0 +1,198 @@ +package api + +import ( + "github.com/gin-gonic/gin" + "github.guxuan/haibei/internal/mods/common/biz" + "github.guxuan/haibei/internal/mods/common/schema" + "github.guxuan/haibei/pkg/util" +) + +// Defining the `Company` api. +type ReceptionCenter struct { + ReceptionCenterBIZ *biz.ReceptionCenter +} + +// @Tags 楼盘模块 +// @Security ApiKeyAuth +// @Summary Query ReceptionCenter list +// @Param current query int true "pagination index" default(1) +// @Param pageSize query int true "pagination size" default(10) +// @Success 200 {object} util.ResponseResult{data=[]schema.ReceptionCenter} +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/reception_center [get] +func (a *ReceptionCenter) Query(c *gin.Context) { + ctx := c.Request.Context() + var params schema.ReceptionCenterQueryParam + if err := util.ParseQuery(c, ¶ms); err != nil { + util.ResError(c, err) + return + } + + result, err := a.ReceptionCenterBIZ.Query(ctx, params) + if err != nil { + util.ResError(c, err) + return + } + util.ResPage(c, result.Data, result.PageResult) +} + +// @Tags 楼盘模块 +// @Security ApiKeyAuth +// @Summary Get ReceptionCenter record by ID +// @Param id path string true "unique id" +// @Success 200 {object} util.ResponseResult{data=schema.ReceptionCenter} +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/reception_center/{id} [get] +func (a *ReceptionCenter) Get(c *gin.Context) { + ctx := c.Request.Context() + id, err := util.GetQueryID(c) + if err != nil { + util.ResError(c, err) + return + } + item, err := a.ReceptionCenterBIZ.Get(ctx, id) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, item) +} + +// @Tags 楼盘模块 +// @Security ApiKeyAuth +// @Summary Create ReceptionCenter record +// @Param body body schema.ReceptionCenterForm true "Request body" +// @Success 200 {object} util.ResponseResult{data=schema.ReceptionCenter} +// @Failure 400 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/reception_center [post] +func (a *ReceptionCenter) Create(c *gin.Context) { + ctx := c.Request.Context() + item := new(schema.ReceptionCenterForm) + if err := util.ParseJSON(c, item); err != nil { + util.ResError(c, err) + return + } else if err := item.Validate(); err != nil { + util.ResError(c, err) + return + } + + result, err := a.ReceptionCenterBIZ.Create(ctx, item) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, result) +} + +// @Tags 楼盘模块 +// @Security ApiKeyAuth +// @Summary Update ReceptionCenter record by ID +// @Param id path string true "unique id" +// @Param body body schema.ReceptionCenterForm true "Request body" +// @Success 200 {object} util.ResponseResult +// @Failure 400 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/reception_center/{id} [put] +func (a *ReceptionCenter) Update(c *gin.Context) { + ctx := c.Request.Context() + item := new(schema.ReceptionCenterForm) + if err := util.ParseJSON(c, item); err != nil { + util.ResError(c, err) + return + } else if err := item.Validate(); err != nil { + util.ResError(c, err) + return + } + id, err := util.GetQueryID(c) + if err != nil { + util.ResError(c, err) + return + } + err = a.ReceptionCenterBIZ.Update(ctx, id, item) + if err != nil { + util.ResError(c, err) + return + } + util.ResOK(c) +} + +// @Tags 楼盘模块 +// @Security ApiKeyAuth +// @Summary Delete ReceptionCenter record by ID +// @Param id path string true "unique id" +// @Success 200 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/reception_center/{id} [delete] +func (a *ReceptionCenter) Delete(c *gin.Context) { + ctx := c.Request.Context() + id, err := util.GetQueryID(c) + if err != nil { + util.ResError(c, err) + return + } + err = a.ReceptionCenterBIZ.Delete(ctx, id) + if err != nil { + util.ResError(c, err) + return + } + util.ResOK(c) +} + +// @Tags 楼盘模块 +// @Security ApiKeyAuth +// @Summary Get ReceptionCenter QrCode by ID +// @Param id path string true "unique id" +// @Success 200 {object} util.ResponseResult{data=[]byte} +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/reception_center/qr_code/{id} [get] +func (a *ReceptionCenter) GetQrCode(c *gin.Context) { + ctx := c.Request.Context() + id, err := util.GetQueryID(c) + if err != nil { + util.ResError(c, err) + return + } + qrcode, err := a.ReceptionCenterBIZ.GetQrCode(ctx, id) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, qrcode) +} + +// @Tags 楼盘模块 +// @Security ApiKeyAuth +// @Summary check QrCode +// @Param body body CheckQrCodeStruct true "Request body" +// @Success 200 {object} util.ResponseResult +// @Failure 400 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/reception_center [post] +func (a *ReceptionCenter) CheckQrCode(c *gin.Context) { + ctx := c.Request.Context() + item := new(CheckQrCodeStruct) + if err := util.ParseJSON(c, item); err != nil { + util.ResError(c, err) + return + } + + err := a.ReceptionCenterBIZ.CheckQrcode(ctx, item.CustomerID, item.ReceptionCenterID) + if err != nil { + util.ResError(c, err) + return + } + util.ResOK(c) +} + +type CheckQrCodeStruct struct { + CustomerID uint `json:"customer_id"` + ReceptionCenterID uint `json:"reception_center_id"` +} diff --git a/internal/mods/common/api/upload.api.go b/internal/mods/common/api/upload.api.go new file mode 100644 index 0000000..595f55c --- /dev/null +++ b/internal/mods/common/api/upload.api.go @@ -0,0 +1,24 @@ +package api + +import ( + "fmt" + "github.com/gin-gonic/gin" + "github.guxuan/haibei/internal/config" + "github.guxuan/haibei/internal/mods/common/biz" + "github.guxuan/haibei/pkg/util" +) + +type Upload struct { + UploadBIZ *biz.Upload +} + +func (a *Upload) SaveFile(c *gin.Context) { + file, err := c.FormFile("file") + if err != nil { + util.ResError(c, err) + return + } + result, err := a.UploadBIZ.SaveFile(c, file) + fileURL := fmt.Sprintf("%s/%s", config.C.FileConfig.StaticPrefix, result) + util.ResSuccess(c, fileURL) +} diff --git a/internal/mods/common/biz/area.biz.go b/internal/mods/common/biz/area.biz.go new file mode 100644 index 0000000..ad6eb78 --- /dev/null +++ b/internal/mods/common/biz/area.biz.go @@ -0,0 +1,104 @@ +package biz + +import ( + "context" + "time" + + "github.guxuan/haibei/internal/mods/common/dal" + "github.guxuan/haibei/internal/mods/common/schema" + "github.guxuan/haibei/pkg/errors" + "github.guxuan/haibei/pkg/util" +) + +// Defining the `Area` business logic. +type Area struct { + Trans *util.Trans + AreaDAL *dal.Area +} + +// Query areas from the data access object based on the provided parameters and options. +func (a *Area) Query(ctx context.Context, params schema.AreaQueryParam) (*schema.AreaQueryResult, error) { + params.Pagination = true + + result, err := a.AreaDAL.Query(ctx, params, schema.AreaQueryOptions{ + QueryOptions: util.QueryOptions{ + OrderFields: []util.OrderByParam{ + {Field: "created_at", Direction: util.DESC}, + }, + }, + }) + if err != nil { + return nil, err + } + return result, nil +} + +// Get the specified area from the data access object. +func (a *Area) Get(ctx context.Context, id uint) (*schema.Area, error) { + area, err := a.AreaDAL.Get(ctx, id) + if err != nil { + return nil, err + } else if area == nil { + return nil, errors.NotFound("", "Area not found") + } + return area, nil +} + +// Create a new area in the data access object. +func (a *Area) Create(ctx context.Context, formItem *schema.AreaForm) (*schema.Area, error) { + area := &schema.Area{} + + if err := formItem.FillTo(area); err != nil { + return nil, err + } + + err := a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.AreaDAL.Create(ctx, area); err != nil { + return err + } + return nil + }) + if err != nil { + return nil, err + } + return area, nil +} + +// Update the specified area in the data access object. +func (a *Area) Update(ctx context.Context, id uint, formItem *schema.AreaForm) error { + area, err := a.AreaDAL.Get(ctx, id) + if err != nil { + return err + } else if area == nil { + return errors.NotFound("", "Area not found") + } + + if err := formItem.FillTo(area); err != nil { + return err + } + area.UpdatedAt = time.Now() + + return a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.AreaDAL.Update(ctx, area); err != nil { + return err + } + return nil + }) +} + +// Delete the specified area from the data access object. +func (a *Area) Delete(ctx context.Context, id uint) error { + exists, err := a.AreaDAL.Exists(ctx, id) + if err != nil { + return err + } else if !exists { + return errors.NotFound("", "Area not found") + } + + return a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.AreaDAL.Delete(ctx, id); err != nil { + return err + } + return nil + }) +} diff --git a/internal/mods/common/biz/banner.biz.go b/internal/mods/common/biz/banner.biz.go new file mode 100644 index 0000000..53aecad --- /dev/null +++ b/internal/mods/common/biz/banner.biz.go @@ -0,0 +1,104 @@ +package biz + +import ( + "context" + "time" + + "github.guxuan/haibei/internal/mods/common/dal" + "github.guxuan/haibei/internal/mods/common/schema" + "github.guxuan/haibei/pkg/errors" + "github.guxuan/haibei/pkg/util" +) + +// Defining the `Banner` business logic. +type Banner struct { + Trans *util.Trans + BannerDAL *dal.Banner +} + +// Query banners from the data access object based on the provided parameters and options. +func (a *Banner) Query(ctx context.Context, params schema.BannerQueryParam) (*schema.BannerQueryResult, error) { + params.Pagination = true + + result, err := a.BannerDAL.Query(ctx, params, schema.BannerQueryOptions{ + QueryOptions: util.QueryOptions{ + OrderFields: []util.OrderByParam{ + {Field: "created_at", Direction: util.DESC}, + }, + }, + }) + if err != nil { + return nil, err + } + return result, nil +} + +// Get the specified banner from the data access object. +func (a *Banner) Get(ctx context.Context, id uint) (*schema.Banner, error) { + banner, err := a.BannerDAL.Get(ctx, id) + if err != nil { + return nil, err + } else if banner == nil { + return nil, errors.NotFound("", "Banner not found") + } + return banner, nil +} + +// Create a new banner in the data access object. +func (a *Banner) Create(ctx context.Context, formItem *schema.BannerForm) (*schema.Banner, error) { + banner := &schema.Banner{} + + if err := formItem.FillTo(banner); err != nil { + return nil, err + } + + err := a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.BannerDAL.Create(ctx, banner); err != nil { + return err + } + return nil + }) + if err != nil { + return nil, err + } + return banner, nil +} + +// Update the specified banner in the data access object. +func (a *Banner) Update(ctx context.Context, id uint, formItem *schema.BannerForm) error { + banner, err := a.BannerDAL.Get(ctx, id) + if err != nil { + return err + } else if banner == nil { + return errors.NotFound("", "Banner not found") + } + + if err := formItem.FillTo(banner); err != nil { + return err + } + banner.UpdatedAt = time.Now() + + return a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.BannerDAL.Update(ctx, banner); err != nil { + return err + } + return nil + }) +} + +// Delete the specified banner from the data access object. +func (a *Banner) Delete(ctx context.Context, id uint) error { + exists, err := a.BannerDAL.Exists(ctx, id) + if err != nil { + return err + } else if !exists { + return errors.NotFound("", "Banner not found") + } + + return a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.BannerDAL.Delete(ctx, id); err != nil { + return err + } + return nil + }) +} diff --git a/internal/mods/common/biz/common.biz.go b/internal/mods/common/biz/common.biz.go new file mode 100644 index 0000000..15b25ab --- /dev/null +++ b/internal/mods/common/biz/common.biz.go @@ -0,0 +1,107 @@ +package biz + +import ( + "context" + "time" + + "github.guxuan/haibei/internal/mods/common/dal" + "github.guxuan/haibei/internal/mods/common/schema" + "github.guxuan/haibei/pkg/errors" + "github.guxuan/haibei/pkg/util" +) + +// Defining the `Common` business logic. +type Common struct { + Trans *util.Trans + CommonDAL *dal.Common +} + +// Query commons from the data access object based on the provided parameters and options. +func (a *Common) Query(ctx context.Context, params schema.CommonQueryParam) (*schema.CommonQueryResult, error) { + params.Pagination = true + + result, err := a.CommonDAL.Query(ctx, params, schema.CommonQueryOptions{ + QueryOptions: util.QueryOptions{ + OrderFields: []util.OrderByParam{ + {Field: "created_at", Direction: util.DESC}, + }, + }, + }) + if err != nil { + return nil, err + } + return result, nil +} + +// Get the specified common from the data access object. +func (a *Common) Get(ctx context.Context, id uint) (*schema.Common, error) { + common, err := a.CommonDAL.Get(ctx, id) + if err != nil { + return nil, err + } else if common == nil { + return nil, errors.NotFound("", "Common not found") + } + return common, nil +} + +// Create a new common in the data access object. +func (a *Common) Create(ctx context.Context, formItem *schema.CommonForm) (*schema.Common, error) { + common := &schema.Common{ + ID: util.NewXID(), + CreatedAt: time.Now(), + } + + if err := formItem.FillTo(common); err != nil { + return nil, err + } + + err := a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.CommonDAL.Create(ctx, common); err != nil { + return err + } + return nil + }) + if err != nil { + return nil, err + } + return common, nil +} + +// Update the specified common in the data access object. +func (a *Common) Update(ctx context.Context, id uint, formItem *schema.CommonForm) error { + common, err := a.CommonDAL.Get(ctx, id) + if err != nil { + return err + } else if common == nil { + return errors.NotFound("", "Common not found") + } + + if err := formItem.FillTo(common); err != nil { + return err + } + common.UpdatedAt = time.Now() + + return a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.CommonDAL.Update(ctx, common); err != nil { + return err + } + return nil + }) +} + +// Delete the specified common from the data access object. +func (a *Common) Delete(ctx context.Context, id uint) error { + exists, err := a.CommonDAL.Exists(ctx, id) + if err != nil { + return err + } else if !exists { + return errors.NotFound("", "Common not found") + } + + return a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.CommonDAL.Delete(ctx, id); err != nil { + return err + } + return nil + }) +} diff --git a/internal/mods/common/biz/company.biz.go b/internal/mods/common/biz/company.biz.go new file mode 100644 index 0000000..4bf46b5 --- /dev/null +++ b/internal/mods/common/biz/company.biz.go @@ -0,0 +1,104 @@ +package biz + +import ( + "context" + "time" + + "github.guxuan/haibei/internal/mods/common/dal" + "github.guxuan/haibei/internal/mods/common/schema" + "github.guxuan/haibei/pkg/errors" + "github.guxuan/haibei/pkg/util" +) + +// Defining the `Company` business logic. +type Company struct { + Trans *util.Trans + CompanyDAL *dal.Company +} + +// Query companies from the data access object based on the provided parameters and options. +func (a *Company) Query(ctx context.Context, params schema.CompanyQueryParam) (*schema.CompanyQueryResult, error) { + params.Pagination = true + + result, err := a.CompanyDAL.Query(ctx, params, schema.CompanyQueryOptions{ + QueryOptions: util.QueryOptions{ + OrderFields: []util.OrderByParam{ + {Field: "created_at", Direction: util.DESC}, + }, + }, + }) + if err != nil { + return nil, err + } + return result, nil +} + +// Get the specified company from the data access object. +func (a *Company) Get(ctx context.Context, id uint) (*schema.Company, error) { + company, err := a.CompanyDAL.Get(ctx, id) + if err != nil { + return nil, err + } else if company == nil { + return nil, errors.NotFound("", "Company not found") + } + return company, nil +} + +// Create a new company in the data access object. +func (a *Company) Create(ctx context.Context, formItem *schema.CompanyForm) (*schema.Company, error) { + company := &schema.Company{} + + if err := formItem.FillTo(company); err != nil { + return nil, err + } + + err := a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.CompanyDAL.Create(ctx, company); err != nil { + return err + } + return nil + }) + if err != nil { + return nil, err + } + return company, nil +} + +// Update the specified company in the data access object. +func (a *Company) Update(ctx context.Context, id uint, formItem *schema.CompanyForm) error { + company, err := a.CompanyDAL.Get(ctx, id) + if err != nil { + return err + } else if company == nil { + return errors.NotFound("", "Company not found") + } + + if err := formItem.FillTo(company); err != nil { + return err + } + company.UpdatedAt = time.Now() + + return a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.CompanyDAL.Update(ctx, company); err != nil { + return err + } + return nil + }) +} + +// Delete the specified company from the data access object. +func (a *Company) Delete(ctx context.Context, id uint) error { + exists, err := a.CompanyDAL.Exists(ctx, id) + if err != nil { + return err + } else if !exists { + return errors.NotFound("", "Company not found") + } + + return a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.CompanyDAL.Delete(ctx, id); err != nil { + return err + } + return nil + }) +} diff --git a/internal/mods/common/biz/notice.biz.go b/internal/mods/common/biz/notice.biz.go new file mode 100644 index 0000000..9a52fbf --- /dev/null +++ b/internal/mods/common/biz/notice.biz.go @@ -0,0 +1,104 @@ +package biz + +import ( + "context" + "time" + + "github.guxuan/haibei/internal/mods/common/dal" + "github.guxuan/haibei/internal/mods/common/schema" + "github.guxuan/haibei/pkg/errors" + "github.guxuan/haibei/pkg/util" +) + +// Defining the `Notice` business logic. +type Notice struct { + Trans *util.Trans + NoticeDAL *dal.Notice +} + +// Query notices from the data access object based on the provided parameters and options. +func (a *Notice) Query(ctx context.Context, params schema.NoticeQueryParam) (*schema.NoticeQueryResult, error) { + params.Pagination = true + + result, err := a.NoticeDAL.Query(ctx, params, schema.NoticeQueryOptions{ + QueryOptions: util.QueryOptions{ + OrderFields: []util.OrderByParam{ + {Field: "created_at", Direction: util.DESC}, + }, + }, + }) + if err != nil { + return nil, err + } + return result, nil +} + +// Get the specified notice from the data access object. +func (a *Notice) Get(ctx context.Context, id uint) (*schema.Notice, error) { + notice, err := a.NoticeDAL.Get(ctx, id) + if err != nil { + return nil, err + } else if notice == nil { + return nil, errors.NotFound("", "Notice not found") + } + return notice, nil +} + +// Create a new notice in the data access object. +func (a *Notice) Create(ctx context.Context, formItem *schema.NoticeForm) (*schema.Notice, error) { + notice := &schema.Notice{} + + if err := formItem.FillTo(notice); err != nil { + return nil, err + } + + err := a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.NoticeDAL.Create(ctx, notice); err != nil { + return err + } + return nil + }) + if err != nil { + return nil, err + } + return notice, nil +} + +// Update the specified notice in the data access object. +func (a *Notice) Update(ctx context.Context, id uint, formItem *schema.NoticeForm) error { + notice, err := a.NoticeDAL.Get(ctx, id) + if err != nil { + return err + } else if notice == nil { + return errors.NotFound("", "Notice not found") + } + + if err := formItem.FillTo(notice); err != nil { + return err + } + notice.UpdatedAt = time.Now() + + return a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.NoticeDAL.Update(ctx, notice); err != nil { + return err + } + return nil + }) +} + +// Delete the specified notice from the data access object. +func (a *Notice) Delete(ctx context.Context, id uint) error { + exists, err := a.NoticeDAL.Exists(ctx, id) + if err != nil { + return err + } else if !exists { + return errors.NotFound("", "Notice not found") + } + + return a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.NoticeDAL.Delete(ctx, id); err != nil { + return err + } + return nil + }) +} diff --git a/internal/mods/common/biz/receptionCenter.go b/internal/mods/common/biz/receptionCenter.go new file mode 100644 index 0000000..a8e502b --- /dev/null +++ b/internal/mods/common/biz/receptionCenter.go @@ -0,0 +1,133 @@ +package biz + +import ( + "context" + "fmt" + "github.com/skip2/go-qrcode" + "github.guxuan/haibei/internal/mods/common/dal" + "github.guxuan/haibei/internal/mods/common/schema" + "github.guxuan/haibei/pkg/errors" + "github.guxuan/haibei/pkg/util" + "time" +) + +type ReceptionCenter struct { + Trans *util.Trans + ReceptionCenterDAL *dal.ReceptionCenter +} + +// Query companies from the data access object based on the provided parameters and options. +func (a *ReceptionCenter) Query(ctx context.Context, params schema.ReceptionCenterQueryParam) (*schema.ReceptionCenterQueryResult, error) { + params.Pagination = true + + result, err := a.ReceptionCenterDAL.Query(ctx, params, schema.ReceptionCenterQueryOptions{ + QueryOptions: util.QueryOptions{ + OrderFields: []util.OrderByParam{ + {Field: "created_at", Direction: util.DESC}, + }, + }, + }) + if err != nil { + return nil, err + } + return result, nil +} + +// Get the specified company from the data access object. +func (a *ReceptionCenter) Get(ctx context.Context, id uint) (*schema.ReceptionCenter, error) { + company, err := a.ReceptionCenterDAL.Get(ctx, id) + if err != nil { + return nil, err + } else if company == nil { + return nil, errors.NotFound("", "Company not found") + } + return company, nil +} +func GenerateStoreQR(recepId uint) ([]byte, error) { + // 格式:checkin:store_id=123 (添加前缀防止恶意扫描) + content := fmt.Sprintf("checkin:reception_center_id=%d", recepId) + return qrcode.Encode(content, qrcode.Medium, 256) +} +func (a *ReceptionCenter) GetQrCode(ctx context.Context, id uint) ([]byte, error) { + recep, err := a.ReceptionCenterDAL.Get(ctx, id) + if err != nil { + return nil, err + } else if recep == nil { + return nil, errors.NotFound("", "ReceptionCenter not found") + } + png, err := GenerateStoreQR(recep.ID) + if err != nil { + return nil, err + + } + return png, nil +} + +// Create a new company in the data access object. +func (a *ReceptionCenter) Create(ctx context.Context, formItem *schema.ReceptionCenterForm) (*schema.ReceptionCenter, error) { + recep := &schema.ReceptionCenter{} + + if err := formItem.FillTo(recep); err != nil { + return nil, err + } + + err := a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.ReceptionCenterDAL.Create(ctx, recep); err != nil { + return err + } + return nil + }) + if err != nil { + return nil, err + } + return recep, nil +} + +func (a *ReceptionCenter) CheckQrcode(ctx context.Context, receptionID, customerID uint) error { + recep, err := a.ReceptionCenterDAL.Get(ctx, receptionID) + if err != nil { + return err + } else if recep == nil { + return errors.NotFound("", "ReceptionCenter not found") + } + return nil +} + +// Update the specified company in the data access object. +func (a *ReceptionCenter) Update(ctx context.Context, id uint, formItem *schema.ReceptionCenterForm) error { + recep, err := a.ReceptionCenterDAL.Get(ctx, id) + if err != nil { + return err + } else if recep == nil { + return errors.NotFound("", "recep not found") + } + + if err := formItem.FillTo(recep); err != nil { + return err + } + recep.UpdatedAt = time.Now() + + return a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.ReceptionCenterDAL.Update(ctx, recep); err != nil { + return err + } + return nil + }) +} + +// Delete the specified company from the data access object. +func (a *ReceptionCenter) Delete(ctx context.Context, id uint) error { + exists, err := a.ReceptionCenterDAL.Exists(ctx, id) + if err != nil { + return err + } else if !exists { + return errors.NotFound("", "recep not found") + } + + return a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.ReceptionCenterDAL.Delete(ctx, id); err != nil { + return err + } + return nil + }) +} diff --git a/internal/mods/common/biz/upload.biz.go b/internal/mods/common/biz/upload.biz.go new file mode 100644 index 0000000..773ce15 --- /dev/null +++ b/internal/mods/common/biz/upload.biz.go @@ -0,0 +1,54 @@ +package biz + +import ( + "context" + "fmt" + "github.guxuan/haibei/internal/config" + "github.guxuan/haibei/pkg/cachex" + "github.guxuan/haibei/pkg/util" + "io" + "mime/multipart" + "os" + "path/filepath" + "strings" +) + +// Role management for RBAC +type Upload struct { + Cache cachex.Cacher + Trans *util.Trans +} + +func (a *Upload) SaveFile(ctx context.Context, file *multipart.FileHeader) (string, error) { + ext := strings.ToLower(filepath.Ext(file.Filename)) + // 简单过滤危险扩展名 + blacklist := []string{".exe", ".bat", ".sh", ".php", ".js"} + for _, b := range blacklist { + if ext == b { + return "", fmt.Errorf("不支持的文件类型: %s", ext) + } + } + // 确保上传目录存在 + if err := os.MkdirAll(config.C.FileConfig.UploadDir, os.ModePerm); err != nil { + return "", err + } + // 构造保存路径 + dstPath := filepath.Join(config.C.FileConfig.UploadDir, file.Filename) + // 打开源文件 + srcFile, err := file.Open() + if err != nil { + return "", err + } + defer srcFile.Close() + // 在目标位置创建文件 + outFile, err := os.Create(dstPath) + if err != nil { + return "", err + } + defer outFile.Close() + // 复制内容 + if _, err := io.Copy(outFile, srcFile); err != nil { + return "", err + } + return file.Filename, nil +} diff --git a/internal/mods/common/dal/area.dal.go b/internal/mods/common/dal/area.dal.go new file mode 100644 index 0000000..e49bcdf --- /dev/null +++ b/internal/mods/common/dal/area.dal.go @@ -0,0 +1,83 @@ +package dal + +import ( + "context" + + "github.guxuan/haibei/internal/mods/common/schema" + "github.guxuan/haibei/pkg/errors" + "github.guxuan/haibei/pkg/util" + "gorm.io/gorm" +) + +// Get area storage instance +func GetAreaDB(ctx context.Context, defDB *gorm.DB) *gorm.DB { + return util.GetDB(ctx, defDB).Model(new(schema.Area)) +} + +// Defining the `Area` data access object. +type Area struct { + DB *gorm.DB +} + +// Query areas from the database based on the provided parameters and options. +func (a *Area) Query(ctx context.Context, params schema.AreaQueryParam, opts ...schema.AreaQueryOptions) (*schema.AreaQueryResult, error) { + var opt schema.AreaQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + db := GetAreaDB(ctx, a.DB) + + var list schema.Areas + pageResult, err := util.WrapPageQuery(ctx, db, params.PaginationParam, opt.QueryOptions, &list) + if err != nil { + return nil, errors.WithStack(err) + } + + queryResult := &schema.AreaQueryResult{ + PageResult: pageResult, + Data: list, + } + return queryResult, nil +} + +// Get the specified area from the database. +func (a *Area) Get(ctx context.Context, id uint, opts ...schema.AreaQueryOptions) (*schema.Area, error) { + var opt schema.AreaQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + item := new(schema.Area) + ok, err := util.FindOne(ctx, GetAreaDB(ctx, a.DB).Where("id=?", id), opt.QueryOptions, item) + if err != nil { + return nil, errors.WithStack(err) + } else if !ok { + return nil, nil + } + return item, nil +} + +// Exists checks if the specified area exists in the database. +func (a *Area) Exists(ctx context.Context, id uint) (bool, error) { + ok, err := util.Exists(ctx, GetAreaDB(ctx, a.DB).Where("id=?", id)) + return ok, errors.WithStack(err) +} + +// Create a new area. +func (a *Area) Create(ctx context.Context, item *schema.Area) error { + result := GetAreaDB(ctx, a.DB).Create(item) + return errors.WithStack(result.Error) +} + +// Update the specified area in the database. +func (a *Area) Update(ctx context.Context, item *schema.Area) error { + result := GetAreaDB(ctx, a.DB).Where("id=?", item.ID).Select("*").Omit("created_at").Updates(item) + return errors.WithStack(result.Error) +} + +// Delete the specified area from the database. +func (a *Area) Delete(ctx context.Context, id uint) error { + result := GetAreaDB(ctx, a.DB).Where("id=?", id).Delete(new(schema.Area)) + return errors.WithStack(result.Error) +} diff --git a/internal/mods/common/dal/banner.dal.go b/internal/mods/common/dal/banner.dal.go new file mode 100644 index 0000000..af598d5 --- /dev/null +++ b/internal/mods/common/dal/banner.dal.go @@ -0,0 +1,83 @@ +package dal + +import ( + "context" + + "github.guxuan/haibei/internal/mods/common/schema" + "github.guxuan/haibei/pkg/errors" + "github.guxuan/haibei/pkg/util" + "gorm.io/gorm" +) + +// Get banner storage instance +func GetBannerDB(ctx context.Context, defDB *gorm.DB) *gorm.DB { + return util.GetDB(ctx, defDB).Model(new(schema.Banner)) +} + +// Defining the `Banner` data access object. +type Banner struct { + DB *gorm.DB +} + +// Query banners from the database based on the provided parameters and options. +func (a *Banner) Query(ctx context.Context, params schema.BannerQueryParam, opts ...schema.BannerQueryOptions) (*schema.BannerQueryResult, error) { + var opt schema.BannerQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + db := GetBannerDB(ctx, a.DB) + + var list schema.Banners + pageResult, err := util.WrapPageQuery(ctx, db, params.PaginationParam, opt.QueryOptions, &list) + if err != nil { + return nil, errors.WithStack(err) + } + + queryResult := &schema.BannerQueryResult{ + PageResult: pageResult, + Data: list, + } + return queryResult, nil +} + +// Get the specified banner from the database. +func (a *Banner) Get(ctx context.Context, id uint, opts ...schema.BannerQueryOptions) (*schema.Banner, error) { + var opt schema.BannerQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + item := new(schema.Banner) + ok, err := util.FindOne(ctx, GetBannerDB(ctx, a.DB).Where("id=?", id), opt.QueryOptions, item) + if err != nil { + return nil, errors.WithStack(err) + } else if !ok { + return nil, nil + } + return item, nil +} + +// Exists checks if the specified banner exists in the database. +func (a *Banner) Exists(ctx context.Context, id uint) (bool, error) { + ok, err := util.Exists(ctx, GetBannerDB(ctx, a.DB).Where("id=?", id)) + return ok, errors.WithStack(err) +} + +// Create a new banner. +func (a *Banner) Create(ctx context.Context, item *schema.Banner) error { + result := GetBannerDB(ctx, a.DB).Create(item) + return errors.WithStack(result.Error) +} + +// Update the specified banner in the database. +func (a *Banner) Update(ctx context.Context, item *schema.Banner) error { + result := GetBannerDB(ctx, a.DB).Where("id=?", item.ID).Select("*").Omit("created_at").Updates(item) + return errors.WithStack(result.Error) +} + +// Delete the specified banner from the database. +func (a *Banner) Delete(ctx context.Context, id uint) error { + result := GetBannerDB(ctx, a.DB).Where("id=?", id).Delete(new(schema.Banner)) + return errors.WithStack(result.Error) +} diff --git a/internal/mods/common/dal/common.dal.go b/internal/mods/common/dal/common.dal.go new file mode 100644 index 0000000..f0b2127 --- /dev/null +++ b/internal/mods/common/dal/common.dal.go @@ -0,0 +1,83 @@ +package dal + +import ( + "context" + + "github.guxuan/haibei/internal/mods/common/schema" + "github.guxuan/haibei/pkg/errors" + "github.guxuan/haibei/pkg/util" + "gorm.io/gorm" +) + +// Get common storage instance +func GetCommonDB(ctx context.Context, defDB *gorm.DB) *gorm.DB { + return util.GetDB(ctx, defDB).Model(new(schema.Common)) +} + +// Defining the `Common` data access object. +type Common struct { + DB *gorm.DB +} + +// Query commons from the database based on the provided parameters and options. +func (a *Common) Query(ctx context.Context, params schema.CommonQueryParam, opts ...schema.CommonQueryOptions) (*schema.CommonQueryResult, error) { + var opt schema.CommonQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + db := GetCommonDB(ctx, a.DB) + + var list schema.Commons + pageResult, err := util.WrapPageQuery(ctx, db, params.PaginationParam, opt.QueryOptions, &list) + if err != nil { + return nil, errors.WithStack(err) + } + + queryResult := &schema.CommonQueryResult{ + PageResult: pageResult, + Data: list, + } + return queryResult, nil +} + +// Get the specified common from the database. +func (a *Common) Get(ctx context.Context, id uint, opts ...schema.CommonQueryOptions) (*schema.Common, error) { + var opt schema.CommonQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + item := new(schema.Common) + ok, err := util.FindOne(ctx, GetCommonDB(ctx, a.DB).Where("id=?", id), opt.QueryOptions, item) + if err != nil { + return nil, errors.WithStack(err) + } else if !ok { + return nil, nil + } + return item, nil +} + +// Exists checks if the specified common exists in the database. +func (a *Common) Exists(ctx context.Context, id uint) (bool, error) { + ok, err := util.Exists(ctx, GetCommonDB(ctx, a.DB).Where("id=?", id)) + return ok, errors.WithStack(err) +} + +// Create a new common. +func (a *Common) Create(ctx context.Context, item *schema.Common) error { + result := GetCommonDB(ctx, a.DB).Create(item) + return errors.WithStack(result.Error) +} + +// Update the specified common in the database. +func (a *Common) Update(ctx context.Context, item *schema.Common) error { + result := GetCommonDB(ctx, a.DB).Where("id=?", item.ID).Select("*").Omit("created_at").Updates(item) + return errors.WithStack(result.Error) +} + +// Delete the specified common from the database. +func (a *Common) Delete(ctx context.Context, id uint) error { + result := GetCommonDB(ctx, a.DB).Where("id=?", id).Delete(new(schema.Common)) + return errors.WithStack(result.Error) +} diff --git a/internal/mods/common/dal/company.dal.go b/internal/mods/common/dal/company.dal.go new file mode 100644 index 0000000..3af9774 --- /dev/null +++ b/internal/mods/common/dal/company.dal.go @@ -0,0 +1,83 @@ +package dal + +import ( + "context" + + "github.guxuan/haibei/internal/mods/common/schema" + "github.guxuan/haibei/pkg/errors" + "github.guxuan/haibei/pkg/util" + "gorm.io/gorm" +) + +// Get company storage instance +func GetCompanyDB(ctx context.Context, defDB *gorm.DB) *gorm.DB { + return util.GetDB(ctx, defDB).Model(new(schema.Company)) +} + +// Defining the `Company` data access object. +type Company struct { + DB *gorm.DB +} + +// Query companies from the database based on the provided parameters and options. +func (a *Company) Query(ctx context.Context, params schema.CompanyQueryParam, opts ...schema.CompanyQueryOptions) (*schema.CompanyQueryResult, error) { + var opt schema.CompanyQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + db := GetCompanyDB(ctx, a.DB) + + var list schema.Companies + pageResult, err := util.WrapPageQuery(ctx, db, params.PaginationParam, opt.QueryOptions, &list) + if err != nil { + return nil, errors.WithStack(err) + } + + queryResult := &schema.CompanyQueryResult{ + PageResult: pageResult, + Data: list, + } + return queryResult, nil +} + +// Get the specified company from the database. +func (a *Company) Get(ctx context.Context, id uint, opts ...schema.CompanyQueryOptions) (*schema.Company, error) { + var opt schema.CompanyQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + item := new(schema.Company) + ok, err := util.FindOne(ctx, GetCompanyDB(ctx, a.DB).Where("id=?", id), opt.QueryOptions, item) + if err != nil { + return nil, errors.WithStack(err) + } else if !ok { + return nil, nil + } + return item, nil +} + +// Exists checks if the specified company exists in the database. +func (a *Company) Exists(ctx context.Context, id uint) (bool, error) { + ok, err := util.Exists(ctx, GetCompanyDB(ctx, a.DB).Where("id=?", id)) + return ok, errors.WithStack(err) +} + +// Create a new company. +func (a *Company) Create(ctx context.Context, item *schema.Company) error { + result := GetCompanyDB(ctx, a.DB).Create(item) + return errors.WithStack(result.Error) +} + +// Update the specified company in the database. +func (a *Company) Update(ctx context.Context, item *schema.Company) error { + result := GetCompanyDB(ctx, a.DB).Where("id=?", item.ID).Select("*").Omit("created_at").Updates(item) + return errors.WithStack(result.Error) +} + +// Delete the specified company from the database. +func (a *Company) Delete(ctx context.Context, id uint) error { + result := GetCompanyDB(ctx, a.DB).Where("id=?", id).Delete(new(schema.Company)) + return errors.WithStack(result.Error) +} diff --git a/internal/mods/common/dal/notice.dal.go b/internal/mods/common/dal/notice.dal.go new file mode 100644 index 0000000..d0ce707 --- /dev/null +++ b/internal/mods/common/dal/notice.dal.go @@ -0,0 +1,83 @@ +package dal + +import ( + "context" + + "github.guxuan/haibei/internal/mods/common/schema" + "github.guxuan/haibei/pkg/errors" + "github.guxuan/haibei/pkg/util" + "gorm.io/gorm" +) + +// Get notice storage instance +func GetNoticeDB(ctx context.Context, defDB *gorm.DB) *gorm.DB { + return util.GetDB(ctx, defDB).Model(new(schema.Notice)) +} + +// Defining the `Notice` data access object. +type Notice struct { + DB *gorm.DB +} + +// Query notices from the database based on the provided parameters and options. +func (a *Notice) Query(ctx context.Context, params schema.NoticeQueryParam, opts ...schema.NoticeQueryOptions) (*schema.NoticeQueryResult, error) { + var opt schema.NoticeQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + db := GetNoticeDB(ctx, a.DB) + + var list schema.Notices + pageResult, err := util.WrapPageQuery(ctx, db, params.PaginationParam, opt.QueryOptions, &list) + if err != nil { + return nil, errors.WithStack(err) + } + + queryResult := &schema.NoticeQueryResult{ + PageResult: pageResult, + Data: list, + } + return queryResult, nil +} + +// Get the specified notice from the database. +func (a *Notice) Get(ctx context.Context, id uint, opts ...schema.NoticeQueryOptions) (*schema.Notice, error) { + var opt schema.NoticeQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + item := new(schema.Notice) + ok, err := util.FindOne(ctx, GetNoticeDB(ctx, a.DB).Where("id=?", id), opt.QueryOptions, item) + if err != nil { + return nil, errors.WithStack(err) + } else if !ok { + return nil, nil + } + return item, nil +} + +// Exists checks if the specified notice exists in the database. +func (a *Notice) Exists(ctx context.Context, id uint) (bool, error) { + ok, err := util.Exists(ctx, GetNoticeDB(ctx, a.DB).Where("id=?", id)) + return ok, errors.WithStack(err) +} + +// Create a new notice. +func (a *Notice) Create(ctx context.Context, item *schema.Notice) error { + result := GetNoticeDB(ctx, a.DB).Create(item) + return errors.WithStack(result.Error) +} + +// Update the specified notice in the database. +func (a *Notice) Update(ctx context.Context, item *schema.Notice) error { + result := GetNoticeDB(ctx, a.DB).Where("id=?", item.ID).Select("*").Omit("created_at").Updates(item) + return errors.WithStack(result.Error) +} + +// Delete the specified notice from the database. +func (a *Notice) Delete(ctx context.Context, id uint) error { + result := GetNoticeDB(ctx, a.DB).Where("id=?", id).Delete(new(schema.Notice)) + return errors.WithStack(result.Error) +} diff --git a/internal/mods/common/dal/receptionCenter.dal.go b/internal/mods/common/dal/receptionCenter.dal.go new file mode 100644 index 0000000..7dd0e00 --- /dev/null +++ b/internal/mods/common/dal/receptionCenter.dal.go @@ -0,0 +1,83 @@ +package dal + +import ( + "context" + + "github.guxuan/haibei/internal/mods/common/schema" + "github.guxuan/haibei/pkg/errors" + "github.guxuan/haibei/pkg/util" + "gorm.io/gorm" +) + +// Get company storage instance +func GetReceptionCenterDB(ctx context.Context, defDB *gorm.DB) *gorm.DB { + return util.GetDB(ctx, defDB).Model(new(schema.ReceptionCenter)) +} + +// Defining the `Company` data access object. +type ReceptionCenter struct { + DB *gorm.DB +} + +// Query companies from the database based on the provided parameters and options. +func (a *ReceptionCenter) Query(ctx context.Context, params schema.ReceptionCenterQueryParam, opts ...schema.ReceptionCenterQueryOptions) (*schema.ReceptionCenterQueryResult, error) { + var opt schema.ReceptionCenterQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + db := GetReceptionCenterDB(ctx, a.DB) + + var list schema.ReceptionCenters + pageResult, err := util.WrapPageQuery(ctx, db, params.PaginationParam, opt.QueryOptions, &list) + if err != nil { + return nil, errors.WithStack(err) + } + + queryResult := &schema.ReceptionCenterQueryResult{ + PageResult: pageResult, + Data: list, + } + return queryResult, nil +} + +// Get the specified company from the database. +func (a *ReceptionCenter) Get(ctx context.Context, id uint, opts ...schema.ReceptionCenterQueryOptions) (*schema.ReceptionCenter, error) { + var opt schema.ReceptionCenterQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + item := new(schema.ReceptionCenter) + ok, err := util.FindOne(ctx, GetReceptionCenterDB(ctx, a.DB).Where("id=?", id), opt.QueryOptions, item) + if err != nil { + return nil, errors.WithStack(err) + } else if !ok { + return nil, nil + } + return item, nil +} + +// Exists checks if the specified company exists in the database. +func (a *ReceptionCenter) Exists(ctx context.Context, id uint) (bool, error) { + ok, err := util.Exists(ctx, GetReceptionCenterDB(ctx, a.DB).Where("id=?", id)) + return ok, errors.WithStack(err) +} + +// Create a new company. +func (a *ReceptionCenter) Create(ctx context.Context, item *schema.ReceptionCenter) error { + result := GetReceptionCenterDB(ctx, a.DB).Create(item) + return errors.WithStack(result.Error) +} + +// Update the specified company in the database. +func (a *ReceptionCenter) Update(ctx context.Context, item *schema.ReceptionCenter) error { + result := GetReceptionCenterDB(ctx, a.DB).Where("id=?", item.ID).Select("*").Omit("created_at").Updates(item) + return errors.WithStack(result.Error) +} + +// Delete the specified company from the database. +func (a *ReceptionCenter) Delete(ctx context.Context, id uint) error { + result := GetReceptionCenterDB(ctx, a.DB).Where("id=?", id).Delete(new(schema.ReceptionCenter)) + return errors.WithStack(result.Error) +} diff --git a/internal/mods/common/dal/signIn.dal.go b/internal/mods/common/dal/signIn.dal.go new file mode 100644 index 0000000..467126f --- /dev/null +++ b/internal/mods/common/dal/signIn.dal.go @@ -0,0 +1,81 @@ +package dal + +import ( + "context" + + "github.guxuan/haibei/internal/mods/common/schema" + "github.guxuan/haibei/pkg/errors" + "github.guxuan/haibei/pkg/util" + "gorm.io/gorm" +) + +func GetSignInDB(ctx context.Context, defDB *gorm.DB) *gorm.DB { + return util.GetDB(ctx, defDB).Model(new(schema.SignIn)) +} + +type SignIn struct { + DB *gorm.DB +} + +// Query notices from the database based on the provided parameters and options. +func (a *SignIn) Query(ctx context.Context, params schema.SignInQueryParam, opts ...schema.SignInQueryOptions) (*schema.SignInQueryResult, error) { + var opt schema.SignInQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + db := GetSignInDB(ctx, a.DB) + + var list schema.SignIns + pageResult, err := util.WrapPageQuery(ctx, db, params.PaginationParam, opt.QueryOptions, &list) + if err != nil { + return nil, errors.WithStack(err) + } + + queryResult := &schema.SignInQueryResult{ + PageResult: pageResult, + Data: list, + } + return queryResult, nil +} + +// Get the specified notice from the database. +func (a *SignIn) Get(ctx context.Context, id uint, opts ...schema.SignInQueryOptions) (*schema.SignIn, error) { + var opt schema.SignInQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + item := new(schema.SignIn) + ok, err := util.FindOne(ctx, GetSignInDB(ctx, a.DB).Where("id=?", id), opt.QueryOptions, item) + if err != nil { + return nil, errors.WithStack(err) + } else if !ok { + return nil, nil + } + return item, nil +} + +// Exists checks if the specified notice exists in the database. +func (a *SignIn) Exists(ctx context.Context, id uint) (bool, error) { + ok, err := util.Exists(ctx, GetSignInDB(ctx, a.DB).Where("id=?", id)) + return ok, errors.WithStack(err) +} + +// Create a new notice. +func (a *SignIn) Create(ctx context.Context, item *schema.SignIn) error { + result := GetSignInDB(ctx, a.DB).Create(item) + return errors.WithStack(result.Error) +} + +// Update the specified notice in the database. +func (a *SignIn) Update(ctx context.Context, item *schema.SignIn) error { + result := GetSignInDB(ctx, a.DB).Where("id=?", item.ID).Select("*").Omit("created_at").Updates(item) + return errors.WithStack(result.Error) +} + +// Delete the specified notice from the database. +func (a *SignIn) Delete(ctx context.Context, id uint) error { + result := GetSignInDB(ctx, a.DB).Where("id=?", id).Delete(new(schema.SignIn)) + return errors.WithStack(result.Error) +} diff --git a/internal/mods/common/main.go b/internal/mods/common/main.go new file mode 100644 index 0000000..e23ac4d --- /dev/null +++ b/internal/mods/common/main.go @@ -0,0 +1,99 @@ +package common + +import ( + "context" + + "github.guxuan/haibei/internal/config" + + "github.com/gin-gonic/gin" + "github.guxuan/haibei/internal/mods/common/api" + "github.guxuan/haibei/internal/mods/common/schema" + "gorm.io/gorm" +) + +type Common struct { + DB *gorm.DB + CommonAPI *api.Common + BannerAPI *api.Banner + NoticeAPI *api.Notice + CompanyAPI *api.Company + AreaAPI *api.Area + UploadAPI *api.Upload + ReceptionCenterAPI *api.ReceptionCenter +} + +func (a *Common) AutoMigrate(ctx context.Context) error { + return a.DB.AutoMigrate(new(schema.Common), new(schema.Banner), new(schema.Notice), new(schema.Company), new(schema.Area), new(schema.ReceptionCenter), new(schema.SignIn)) +} + +func (a *Common) Init(ctx context.Context) error { + if config.C.Storage.DB.AutoMigrate { + if err := a.AutoMigrate(ctx); err != nil { + return err + } + } + return nil +} + +func (a *Common) RegisterV1Routers(ctx context.Context, v1 *gin.RouterGroup) error { + common := v1.Group("commons") + { + common.GET("", a.CommonAPI.Query) + common.GET(":id", a.CommonAPI.Get) + common.POST("", a.CommonAPI.Create) + common.PUT(":id", a.CommonAPI.Update) + common.DELETE(":id", a.CommonAPI.Delete) + } + upload := v1.Group("upload") + { + upload.POST("", a.UploadAPI.SaveFile) + + } + banner := v1.Group("banners") + { + banner.GET("", a.BannerAPI.Query) + banner.GET(":id", a.BannerAPI.Get) + banner.POST("", a.BannerAPI.Create) + banner.PUT(":id", a.BannerAPI.Update) + banner.DELETE(":id", a.BannerAPI.Delete) + } + notice := v1.Group("notices") + { + notice.GET("", a.NoticeAPI.Query) + notice.GET(":id", a.NoticeAPI.Get) + notice.POST("", a.NoticeAPI.Create) + notice.PUT(":id", a.NoticeAPI.Update) + notice.DELETE(":id", a.NoticeAPI.Delete) + } + company := v1.Group("companies") + { + company.GET("", a.CompanyAPI.Query) + company.GET(":id", a.CompanyAPI.Get) + company.POST("", a.CompanyAPI.Create) + company.PUT(":id", a.CompanyAPI.Update) + company.DELETE(":id", a.CompanyAPI.Delete) + } + area := v1.Group("areas") + { + area.GET("", a.AreaAPI.Query) + area.GET(":id", a.AreaAPI.Get) + area.POST("", a.AreaAPI.Create) + area.PUT(":id", a.AreaAPI.Update) + area.DELETE(":id", a.AreaAPI.Delete) + } + reception := v1.Group("reception_center") + { + reception.GET("", a.ReceptionCenterAPI.Query) + reception.GET(":id", a.ReceptionCenterAPI.Get) + reception.POST("", a.ReceptionCenterAPI.Create) + reception.GET("qr_code/:id", a.ReceptionCenterAPI.GetQrCode) + reception.POST("qr_code", a.ReceptionCenterAPI.CheckQrCode) + reception.PUT(":id", a.ReceptionCenterAPI.Update) + reception.DELETE(":id", a.ReceptionCenterAPI.Delete) + } + return nil +} + +func (a *Common) Release(ctx context.Context) error { + return nil +} diff --git a/internal/mods/common/schema/area.go b/internal/mods/common/schema/area.go new file mode 100644 index 0000000..c7eee04 --- /dev/null +++ b/internal/mods/common/schema/area.go @@ -0,0 +1,49 @@ +package schema + +import ( + "github.guxuan/haibei/pkg/util" +) + +// Defining the `Area` struct. +type Area struct { + util.BaseModel + Name string `json:"name" gorm:"size:128;not null;index"` + Status string `json:"status" gorm:"size:20;index"` +} + +// Defining the query parameters for the `Area` struct. +type AreaQueryParam struct { + util.PaginationParam +} + +// Defining the query options for the `Area` struct. +type AreaQueryOptions struct { + util.QueryOptions +} + +// Defining the query result for the `Area` struct. +type AreaQueryResult struct { + Data Areas + PageResult *util.PaginationResult +} + +// Defining the slice of `Area` struct. +type Areas []*Area + +// Defining the data structure for creating a `Area` struct. +type AreaForm struct { + Name string `json:"name" ` + Status string `json:"status"` +} + +// A validation function for the `AreaForm` struct. +func (a *AreaForm) Validate() error { + return nil +} + +// Convert `AreaForm` to `Area` object. +func (a *AreaForm) FillTo(area *Area) error { + area.Status = a.Status + area.Name = a.Name + return nil +} diff --git a/internal/mods/common/schema/banner.go b/internal/mods/common/schema/banner.go new file mode 100644 index 0000000..08c04fa --- /dev/null +++ b/internal/mods/common/schema/banner.go @@ -0,0 +1,69 @@ +package schema + +import ( + "github.guxuan/haibei/pkg/util" +) + +// Defining the `Banner` struct. +type Banner struct { + util.BaseModel + Name string `json:"name" gorm:"size:128;not null;index"` + Img string `json:"img" gorm:"size:2048"` + Sequence int `json:"sequence" gorm:"index;default:0"` + Link string `json:"link" gorm:"size:2048;"` + AreaID uint `json:"areaId" gorm:"index;comment:城市id"` + + Status string `json:"status" gorm:"size:20;index"` +} + +// Defining the query parameters for the `Banner` struct. +type BannerQueryParam struct { + util.PaginationParam + Status string `form:"status"` + AreaID uint `form:"areaId" ` +} + +// Defining the query options for the `Banner` struct. +type BannerQueryOptions struct { + util.QueryOptions +} + +// Defining the query result for the `Banner` struct. +type BannerQueryResult struct { + Data Banners + PageResult *util.PaginationResult +} + +// Defining the slice of `Banner` struct. +type Banners []*Banner + +// Defining the data structure for creating a `Banner` struct. +type BannerForm struct { + Name string `json:"name" ` + Img string `json:"img"` + Sequence int `json:"sequence"` + Link string `json:"link"` + AreaID uint `json:"areaId" ` + + Status string `json:"status"` +} + +// A validation function for the `BannerForm` struct. +func (a *BannerForm) Validate() error { + return nil +} + +// Convert `BannerForm` to `Banner` object. +func (a *BannerForm) FillTo(banner *Banner) error { + banner.Name = a.Name + banner.Img = a.Img + banner.AreaID = a.AreaID + banner.Sequence = a.Sequence + banner.Link = a.Link + if a.Status != "" { + banner.Status = a.Status + } else { + banner.Status = "active" + } + return nil +} diff --git a/internal/mods/common/schema/common.go b/internal/mods/common/schema/common.go new file mode 100644 index 0000000..aa3a2fc --- /dev/null +++ b/internal/mods/common/schema/common.go @@ -0,0 +1,47 @@ +package schema + +import ( + "time" + + "github.guxuan/haibei/pkg/util" +) + +// Defining the `Common` struct. +type Common struct { + ID string `json:"id" gorm:"size:20;primaryKey;"` // Unique ID + CreatedAt time.Time `json:"created_at" gorm:"index;"` // Create time + UpdatedAt time.Time `json:"updated_at" gorm:"index;"` // Update time +} + +// Defining the query parameters for the `Common` struct. +type CommonQueryParam struct { + util.PaginationParam +} + +// Defining the query options for the `Common` struct. +type CommonQueryOptions struct { + util.QueryOptions +} + +// Defining the query result for the `Common` struct. +type CommonQueryResult struct { + Data Commons + PageResult *util.PaginationResult +} + +// Defining the slice of `Common` struct. +type Commons []*Common + +// Defining the data structure for creating a `Common` struct. +type CommonForm struct { +} + +// A validation function for the `CommonForm` struct. +func (a *CommonForm) Validate() error { + return nil +} + +// Convert `CommonForm` to `Common` object. +func (a *CommonForm) FillTo(common *Common) error { + return nil +} diff --git a/internal/mods/common/schema/company.go b/internal/mods/common/schema/company.go new file mode 100644 index 0000000..37a4b7b --- /dev/null +++ b/internal/mods/common/schema/company.go @@ -0,0 +1,57 @@ +package schema + +import ( + "github.guxuan/haibei/pkg/util" +) + +// Defining the `Company` struct. +type Company struct { + util.BaseModel + Name string `json:"name" gorm:"size:128;not null;index"` + Img string `json:"img" gorm:"size:2048"` + AreaID uint `json:"areaId" gorm:"index;comment:城市id"` + Status string `json:"status" gorm:"size:20;index"` +} + +// Defining the query parameters for the `Company` struct. +type CompanyQueryParam struct { + util.PaginationParam + AreaID uint `form:"areaId"` +} + +// Defining the query options for the `Company` struct. +type CompanyQueryOptions struct { + util.QueryOptions +} + +// Defining the query result for the `Company` struct. +type CompanyQueryResult struct { + Data Companies + PageResult *util.PaginationResult +} + +// Defining the slice of `Company` struct. +type Companies []*Company + +// Defining the data structure for creating a `Company` struct. +type CompanyForm struct { + Name string `json:"name" ` + Img string `json:"img" ` + AreaID uint `json:"areaId" ` + + Status string `json:"status" ` +} + +// A validation function for the `CompanyForm` struct. +func (a *CompanyForm) Validate() error { + return nil +} + +// Convert `CompanyForm` to `Company` object. +func (a *CompanyForm) FillTo(company *Company) error { + company.Name = a.Name + company.Img = a.Img + company.AreaID = a.AreaID + company.Status = a.Status + return nil +} diff --git a/internal/mods/common/schema/notice.go b/internal/mods/common/schema/notice.go new file mode 100644 index 0000000..0e560f4 --- /dev/null +++ b/internal/mods/common/schema/notice.go @@ -0,0 +1,60 @@ +package schema + +import ( + "github.guxuan/haibei/pkg/util" +) + +// Defining the `Notice` struct. +type Notice struct { + util.BaseModel + Title string `json:"title" gorm:"size:128;not null;index"` + Content string `json:"content" gorm:"size:2048;not null"` + Sequence int `json:"sequence" gorm:"index;default:0"` + AreaID uint `json:"areaId" gorm:"index;comment:城市id"` + + Status string `json:"status" gorm:"size:20;index"` +} + +// Defining the query parameters for the `Notice` struct. +type NoticeQueryParam struct { + util.PaginationParam + AreaID uint `form:"areaId"` +} + +// Defining the query options for the `Notice` struct. +type NoticeQueryOptions struct { + util.QueryOptions +} + +// Defining the query result for the `Notice` struct. +type NoticeQueryResult struct { + Data Notices + PageResult *util.PaginationResult +} + +// Defining the slice of `Notice` struct. +type Notices []*Notice + +// Defining the data structure for creating a `Notice` struct. +type NoticeForm struct { + Title string `json:"title" ` + Content string `json:"content"` + Sequence int `json:"sequence" ` + Status string `json:"status"` + AreaID uint `json:"areaId"` +} + +// A validation function for the `NoticeForm` struct. +func (a *NoticeForm) Validate() error { + return nil +} + +// Convert `NoticeForm` to `Notice` object. +func (a *NoticeForm) FillTo(notice *Notice) error { + notice.Title = a.Title + notice.Content = a.Content + notice.Sequence = a.Sequence + notice.Status = a.Status + notice.AreaID = a.AreaID + return nil +} diff --git a/internal/mods/common/schema/receptionCenter.go b/internal/mods/common/schema/receptionCenter.go new file mode 100644 index 0000000..d59b2d8 --- /dev/null +++ b/internal/mods/common/schema/receptionCenter.go @@ -0,0 +1,56 @@ +package schema + +import ( + "github.guxuan/haibei/pkg/util" +) + +// Defining the `Company` struct. +type ReceptionCenter struct { + util.BaseModel + Name string `json:"name" gorm:"size:128;not null;index"` + Introduce string `json:"introduce" gorm:"size:1024;comment:介绍"` + AreaID uint `json:"areaId" gorm:"index;comment:城市id"` + Status string `json:"status" gorm:"size:20;index"` +} + +// Defining the query parameters for the `Company` struct. +type ReceptionCenterQueryParam struct { + util.PaginationParam + AreaID uint `form:"areaId"` +} + +// Defining the query options for the `Company` struct. +type ReceptionCenterQueryOptions struct { + util.QueryOptions +} + +// Defining the query result for the `Company` struct. +type ReceptionCenterQueryResult struct { + Data ReceptionCenters + PageResult *util.PaginationResult +} + +// Defining the slice of `Company` struct. +type ReceptionCenters []*ReceptionCenter + +// Defining the data structure for creating a `Company` struct. +type ReceptionCenterForm struct { + Name string `json:"name" ` + Introduce string `json:"introduce" ` + AreaID uint `json:"areaId" ` + Status string `json:"status" ` +} + +// A validation function for the `CompanyForm` struct. +func (a *ReceptionCenterForm) Validate() error { + return nil +} + +// Convert `CompanyForm` to `Company` object. +func (a *ReceptionCenterForm) FillTo(receptionCenter *ReceptionCenter) error { + receptionCenter.Name = a.Name + receptionCenter.Introduce = a.Introduce + receptionCenter.AreaID = a.AreaID + receptionCenter.Status = a.Status + return nil +} diff --git a/internal/mods/common/schema/signIn.go b/internal/mods/common/schema/signIn.go new file mode 100644 index 0000000..1f28d14 --- /dev/null +++ b/internal/mods/common/schema/signIn.go @@ -0,0 +1,50 @@ +package schema + +import ( + "github.guxuan/haibei/pkg/util" +) + +// Defining the `Notice` struct. +type SignIn struct { + util.BaseModel + CustomerID uint `json:"customerId" gorm:"not null;index"` + ReceptionID uint `json:"receptionId" gorm:"not null;index"` +} + +// Defining the query parameters for the `Notice` struct. +type SignInQueryParam struct { + util.PaginationParam +} + +// Defining the query options for the `Notice` struct. +type SignInQueryOptions struct { + util.QueryOptions +} + +// Defining the query result for the `Notice` struct. +type SignInQueryResult struct { + Data SignIns + PageResult *util.PaginationResult +} + +// Defining the slice of `Notice` struct. +type SignIns []*SignIn + +// Defining the data structure for creating a `Notice` struct. +type SignInForm struct { + CustomerID uint `json:"customerId" ` + ReceptionID uint `json:"receptionId"` +} + +// A validation function for the `NoticeForm` struct. +func (a *SignInForm) Validate() error { + return nil +} + +// Convert `NoticeForm` to `Notice` object. +func (a *SignInForm) FillTo(signIn *SignIn) error { + signIn.CustomerID = a.CustomerID + signIn.ReceptionID = a.ReceptionID + + return nil +} diff --git a/internal/mods/common/wire.go b/internal/mods/common/wire.go new file mode 100644 index 0000000..625268b --- /dev/null +++ b/internal/mods/common/wire.go @@ -0,0 +1,31 @@ +package common + +import ( + "github.com/google/wire" + "github.guxuan/haibei/internal/mods/common/api" + "github.guxuan/haibei/internal/mods/common/biz" + "github.guxuan/haibei/internal/mods/common/dal" +) + +var Set = wire.NewSet( + wire.Struct(new(Common), "*"), + wire.Struct(new(dal.ReceptionCenter), "*"), + wire.Struct(new(biz.ReceptionCenter), "*"), + wire.Struct(new(api.ReceptionCenter), "*"), + wire.Struct(new(dal.SignIn), "*"), + wire.Struct(new(dal.Common), "*"), + wire.Struct(new(biz.Common), "*"), + wire.Struct(new(api.Common), "*"), + wire.Struct(new(dal.Banner), "*"), + wire.Struct(new(biz.Banner), "*"), + wire.Struct(new(api.Banner), "*"), + wire.Struct(new(dal.Notice), "*"), + wire.Struct(new(biz.Notice), "*"), + wire.Struct(new(api.Notice), "*"), + wire.Struct(new(dal.Company), "*"), + wire.Struct(new(biz.Company), "*"), + wire.Struct(new(api.Company), "*"), + wire.Struct(new(dal.Area), "*"), + wire.Struct(new(biz.Area), "*"), + wire.Struct(new(api.Area), "*"), +) diff --git a/internal/mods/customer/api/balance.api.go b/internal/mods/customer/api/balance.api.go new file mode 100644 index 0000000..9840c3b --- /dev/null +++ b/internal/mods/customer/api/balance.api.go @@ -0,0 +1,146 @@ +package api + +import ( + "github.com/gin-gonic/gin" + "github.guxuan/haibei/internal/mods/customer/biz" + "github.guxuan/haibei/internal/mods/customer/schema" + "github.guxuan/haibei/pkg/util" + "strconv" +) + +// Defining the `Balance` api. +type Balance struct { + BalanceBIZ *biz.Balance +} + +// @Tags 积分模块 +// @Security ApiKeyAuth +// @Summary Query balance list +// @Param current query int true "pagination index" default(1) +// @Param pageSize query int true "pagination size" default(10) +// @Success 200 {object} util.ResponseResult{data=[]schema.Balance} +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/balances [get] +func (a *Balance) Query(c *gin.Context) { + ctx := c.Request.Context() + var params schema.BalanceQueryParam + if err := util.ParseQuery(c, ¶ms); err != nil { + util.ResError(c, err) + return + } + + result, err := a.BalanceBIZ.Query(ctx, params) + if err != nil { + util.ResError(c, err) + return + } + util.ResPage(c, result.Data, result.PageResult) +} + +// @Tags 积分模块 +// @Security ApiKeyAuth +// @Summary Get balance record by ID +// @Param id path string true "unique id" +// @Success 200 {object} util.ResponseResult{data=schema.Balance} +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/balances/{id} [get] +func (a *Balance) Get(c *gin.Context) { + ctx := c.Request.Context() + u64, err := strconv.ParseUint(c.Param("id"), 10, 32) + if err != nil { + util.ResError(c, err) + return + } + item, err := a.BalanceBIZ.Get(ctx, uint(u64)) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, item) +} + +// @Tags 积分模块 +// @Security ApiKeyAuth +// @Summary Create balance record +// @Param body body schema.BalanceForm true "Request body" +// @Success 200 {object} util.ResponseResult{data=schema.Balance} +// @Failure 400 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/balances [post] +func (a *Balance) Create(c *gin.Context) { + ctx := c.Request.Context() + item := new(schema.BalanceForm) + if err := util.ParseJSON(c, item); err != nil { + util.ResError(c, err) + return + } else if err := item.Validate(); err != nil { + util.ResError(c, err) + return + } + + result, err := a.BalanceBIZ.Create(ctx, item) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, result) +} + +// @Tags 积分模块 +// @Security ApiKeyAuth +// @Summary Update balance record by ID +// @Param id path string true "unique id" +// @Param body body schema.BalanceForm true "Request body" +// @Success 200 {object} util.ResponseResult +// @Failure 400 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/balances/{id} [put] +func (a *Balance) Update(c *gin.Context) { + ctx := c.Request.Context() + item := new(schema.BalanceForm) + if err := util.ParseJSON(c, item); err != nil { + util.ResError(c, err) + return + } else if err := item.Validate(); err != nil { + util.ResError(c, err) + return + } + u64, err := strconv.ParseUint(c.Param("id"), 10, 32) + if err != nil { + util.ResError(c, err) + return + } + err = a.BalanceBIZ.Update(ctx, uint(u64), item) + if err != nil { + util.ResError(c, err) + return + } + util.ResOK(c) +} + +// @Tags 积分模块 +// @Security ApiKeyAuth +// @Summary Delete balance record by ID +// @Param id path string true "unique id" +// @Success 200 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/balances/{id} [delete] +func (a *Balance) Delete(c *gin.Context) { + ctx := c.Request.Context() + u64, err := strconv.ParseUint(c.Param("id"), 10, 32) + if err != nil { + util.ResError(c, err) + return + } + err = a.BalanceBIZ.Delete(ctx, uint(u64)) + if err != nil { + util.ResError(c, err) + return + } + util.ResOK(c) +} diff --git a/internal/mods/customer/api/customer.api.go b/internal/mods/customer/api/customer.api.go new file mode 100644 index 0000000..d92e7cf --- /dev/null +++ b/internal/mods/customer/api/customer.api.go @@ -0,0 +1,146 @@ +package api + +import ( + "github.com/gin-gonic/gin" + "github.guxuan/haibei/internal/mods/customer/biz" + "github.guxuan/haibei/internal/mods/customer/schema" + "github.guxuan/haibei/pkg/util" + "strconv" +) + +// Defining the `Customer` api. +type Customer struct { + CustomerBIZ *biz.Customer +} + +// @Tags 客户模块 +// @Security ApiKeyAuth +// @Summary Query customer list +// @Param current query int true "pagination index" default(1) +// @Param pageSize query int true "pagination size" default(10) +// @Success 200 {object} util.ResponseResult{data=[]schema.Customer} +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/customers [get] +func (a *Customer) Query(c *gin.Context) { + ctx := c.Request.Context() + var params schema.CustomerQueryParam + if err := util.ParseQuery(c, ¶ms); err != nil { + util.ResError(c, err) + return + } + + result, err := a.CustomerBIZ.Query(ctx, params) + if err != nil { + util.ResError(c, err) + return + } + util.ResPage(c, result.Data, result.PageResult) +} + +// @Tags 客户模块 +// @Security ApiKeyAuth +// @Summary Get customer record by ID +// @Param id path string true "unique id" +// @Success 200 {object} util.ResponseResult{data=schema.Customer} +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/customers/{id} [get] +func (a *Customer) Get(c *gin.Context) { + ctx := c.Request.Context() + u64, err := strconv.ParseUint(c.Param("id"), 10, 32) + if err != nil { + util.ResError(c, err) + return + } + item, err := a.CustomerBIZ.Get(ctx, uint(u64)) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, item) +} + +// @Tags 客户模块 +// @Security ApiKeyAuth +// @Summary Create customer record +// @Param body body schema.CustomerForm true "Request body" +// @Success 200 {object} util.ResponseResult{data=schema.Customer} +// @Failure 400 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/customers [post] +func (a *Customer) Create(c *gin.Context) { + ctx := c.Request.Context() + item := new(schema.CustomerForm) + if err := util.ParseJSON(c, item); err != nil { + util.ResError(c, err) + return + } else if err := item.Validate(); err != nil { + util.ResError(c, err) + return + } + + result, err := a.CustomerBIZ.Create(ctx, item) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, result) +} + +// @Tags 客户模块 +// @Security ApiKeyAuth +// @Summary Update customer record by ID +// @Param id path string true "unique id" +// @Param body body schema.CustomerForm true "Request body" +// @Success 200 {object} util.ResponseResult +// @Failure 400 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/customers/{id} [put] +func (a *Customer) Update(c *gin.Context) { + ctx := c.Request.Context() + item := new(schema.CustomerForm) + if err := util.ParseJSON(c, item); err != nil { + util.ResError(c, err) + return + } else if err := item.Validate(); err != nil { + util.ResError(c, err) + return + } + u64, err := strconv.ParseUint(c.Param("id"), 10, 32) + if err != nil { + util.ResError(c, err) + return + } + err = a.CustomerBIZ.Update(ctx, uint(u64), item) + if err != nil { + util.ResError(c, err) + return + } + util.ResOK(c) +} + +// @Tags 客户模块 +// @Security ApiKeyAuth +// @Summary Delete customer record by ID +// @Param id path string true "unique id" +// @Success 200 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/customers/{id} [delete] +func (a *Customer) Delete(c *gin.Context) { + ctx := c.Request.Context() + u64, err := strconv.ParseUint(c.Param("id"), 10, 32) + if err != nil { + util.ResError(c, err) + return + } + err = a.CustomerBIZ.Delete(ctx, uint(u64)) + if err != nil { + util.ResError(c, err) + return + } + util.ResOK(c) +} diff --git a/internal/mods/customer/api/product_order.api.go b/internal/mods/customer/api/product_order.api.go new file mode 100644 index 0000000..17bfd20 --- /dev/null +++ b/internal/mods/customer/api/product_order.api.go @@ -0,0 +1,145 @@ +package api + +import ( + "github.com/gin-gonic/gin" + "github.guxuan/haibei/internal/mods/customer/biz" + "github.guxuan/haibei/internal/mods/customer/schema" + "github.guxuan/haibei/pkg/util" +) + +// Defining the `ProductOrder` api. +type ProductOrder struct { + ProductOrderBIZ *biz.ProductOrder +} + +// @Tags ProductOrderAPI +// @Security ApiKeyAuth +// @Summary Query product order list +// @Param current query int true "pagination index" default(1) +// @Param pageSize query int true "pagination size" default(10) +// @Success 200 {object} util.ResponseResult{data=[]schema.ProductOrder} +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/product-orders [get] +func (a *ProductOrder) Query(c *gin.Context) { + ctx := c.Request.Context() + var params schema.ProductOrderQueryParam + if err := util.ParseQuery(c, ¶ms); err != nil { + util.ResError(c, err) + return + } + + result, err := a.ProductOrderBIZ.Query(ctx, params) + if err != nil { + util.ResError(c, err) + return + } + util.ResPage(c, result.Data, result.PageResult) +} + +// @Tags ProductOrderAPI +// @Security ApiKeyAuth +// @Summary Get product order record by ID +// @Param id path string true "unique id" +// @Success 200 {object} util.ResponseResult{data=schema.ProductOrder} +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/product-orders/{id} [get] +func (a *ProductOrder) Get(c *gin.Context) { + ctx := c.Request.Context() + id, err := util.GetQueryID(c) + if err != nil { + util.ResError(c, err) + return + } + item, err := a.ProductOrderBIZ.Get(ctx, id) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, item) +} + +// @Tags ProductOrderAPI +// @Security ApiKeyAuth +// @Summary Create product order record +// @Param body body schema.ProductOrderForm true "Request body" +// @Success 200 {object} util.ResponseResult{data=schema.ProductOrder} +// @Failure 400 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/product-orders [post] +func (a *ProductOrder) Create(c *gin.Context) { + ctx := c.Request.Context() + item := new(schema.ProductOrderForm) + if err := util.ParseJSON(c, item); err != nil { + util.ResError(c, err) + return + } else if err := item.Validate(); err != nil { + util.ResError(c, err) + return + } + + result, err := a.ProductOrderBIZ.Create(ctx, item) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, result) +} + +// @Tags ProductOrderAPI +// @Security ApiKeyAuth +// @Summary Update product order record by ID +// @Param id path string true "unique id" +// @Param body body schema.ProductOrderForm true "Request body" +// @Success 200 {object} util.ResponseResult +// @Failure 400 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/product-orders/{id} [put] +func (a *ProductOrder) Update(c *gin.Context) { + ctx := c.Request.Context() + item := new(schema.ProductOrderForm) + if err := util.ParseJSON(c, item); err != nil { + util.ResError(c, err) + return + } else if err := item.Validate(); err != nil { + util.ResError(c, err) + return + } + id, err := util.GetQueryID(c) + if err != nil { + util.ResError(c, err) + return + } + err = a.ProductOrderBIZ.Update(ctx, id, item) + if err != nil { + util.ResError(c, err) + return + } + util.ResOK(c) +} + +// @Tags ProductOrderAPI +// @Security ApiKeyAuth +// @Summary Delete product order record by ID +// @Param id path string true "unique id" +// @Success 200 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/product-orders/{id} [delete] +func (a *ProductOrder) Delete(c *gin.Context) { + ctx := c.Request.Context() + id, err := util.GetQueryID(c) + if err != nil { + util.ResError(c, err) + return + } + err = a.ProductOrderBIZ.Delete(ctx, id) + if err != nil { + util.ResError(c, err) + return + } + util.ResOK(c) +} diff --git a/internal/mods/customer/biz/balance.biz.go b/internal/mods/customer/biz/balance.biz.go new file mode 100644 index 0000000..1b81b06 --- /dev/null +++ b/internal/mods/customer/biz/balance.biz.go @@ -0,0 +1,104 @@ +package biz + +import ( + "context" + "time" + + "github.guxuan/haibei/internal/mods/customer/dal" + "github.guxuan/haibei/internal/mods/customer/schema" + "github.guxuan/haibei/pkg/errors" + "github.guxuan/haibei/pkg/util" +) + +// Defining the `Balance` business logic. +type Balance struct { + Trans *util.Trans + BalanceDAL *dal.Balance +} + +// Query balances from the data access object based on the provided parameters and options. +func (a *Balance) Query(ctx context.Context, params schema.BalanceQueryParam) (*schema.BalanceQueryResult, error) { + params.Pagination = true + + result, err := a.BalanceDAL.Query(ctx, params, schema.BalanceQueryOptions{ + QueryOptions: util.QueryOptions{ + OrderFields: []util.OrderByParam{ + {Field: "created_at", Direction: util.DESC}, + }, + }, + }) + if err != nil { + return nil, err + } + return result, nil +} + +// Get the specified balance from the data access object. +func (a *Balance) Get(ctx context.Context, id uint) (*schema.Balance, error) { + balance, err := a.BalanceDAL.Get(ctx, id) + if err != nil { + return nil, err + } else if balance == nil { + return nil, errors.NotFound("", "Balance not found") + } + return balance, nil +} + +// Create a new balance in the data access object. +func (a *Balance) Create(ctx context.Context, formItem *schema.BalanceForm) (*schema.Balance, error) { + balance := &schema.Balance{} + + if err := formItem.FillTo(balance); err != nil { + return nil, err + } + + err := a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.BalanceDAL.Create(ctx, balance); err != nil { + return err + } + return nil + }) + if err != nil { + return nil, err + } + return balance, nil +} + +// Update the specified balance in the data access object. +func (a *Balance) Update(ctx context.Context, id uint, formItem *schema.BalanceForm) error { + balance, err := a.BalanceDAL.Get(ctx, id) + if err != nil { + return err + } else if balance == nil { + return errors.NotFound("", "Balance not found") + } + + if err := formItem.FillTo(balance); err != nil { + return err + } + balance.UpdatedAt = time.Now() + + return a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.BalanceDAL.Update(ctx, balance); err != nil { + return err + } + return nil + }) +} + +// Delete the specified balance from the data access object. +func (a *Balance) Delete(ctx context.Context, id uint) error { + exists, err := a.BalanceDAL.Exists(ctx, id) + if err != nil { + return err + } else if !exists { + return errors.NotFound("", "Balance not found") + } + + return a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.BalanceDAL.Delete(ctx, id); err != nil { + return err + } + return nil + }) +} diff --git a/internal/mods/customer/biz/customer.biz.go b/internal/mods/customer/biz/customer.biz.go new file mode 100644 index 0000000..dd702d5 --- /dev/null +++ b/internal/mods/customer/biz/customer.biz.go @@ -0,0 +1,239 @@ +package biz + +import ( + "context" + "encoding/json" + "fmt" + "github.guxuan/haibei/internal/config" + "github.guxuan/haibei/pkg/cachex" + "github.guxuan/haibei/pkg/jwtx" + "github.guxuan/haibei/pkg/logging" + "go.uber.org/zap" + "math/rand" + "strconv" + "time" + + "github.guxuan/haibei/internal/mods/customer/dal" + "github.guxuan/haibei/internal/mods/customer/schema" + "github.guxuan/haibei/pkg/errors" + "github.guxuan/haibei/pkg/util" +) + +const ( + REGISTER_SMS_CODE = "REGISTER_SMS_CODE" + CHECK_SMS_CODE = "CHECK_SMS_CODE" +) + +// Defining the `Customer` business logic. +type Customer struct { + Cache cachex.Cacher + Trans *util.Trans + CustomerDAL *dal.Customer + Auth jwtx.Auther +} + +func (a *Customer) Login(ctx context.Context, formItem *schema.CustomerLoginForm) (*schema.CustomerLoginToken, error) { + ctx = logging.NewTag(ctx, logging.TagKeyAppLogin) + + // get user info + user, err := a.CustomerDAL.GetByWxSign(ctx, formItem.WxSign, schema.CustomerQueryOptions{}) + if err != nil { + return nil, err + } + if user == nil { + customer := &schema.Customer{} + customer.WxSign = formItem.WxSign + err := a.CustomerDAL.Create(ctx, customer) + if err != nil { + return nil, err + } + user = customer + } + userID := fmt.Sprintf("%d", user.ID) + var subject jwtx.JwtSubject + subject.ID = userID + subject.Typer = "APP" + marshal, err := json.Marshal(subject) + if err != nil { + return nil, err + } + // generate token + ctx = logging.NewUserID(ctx, userID) + + userCache := util.UserCache{RoleIDs: []string{}} + err = a.Cache.Set(ctx, config.CacheNSForUser, userID, userCache.String(), + time.Duration(config.C.Dictionary.UserCacheExp)*time.Hour) + if err != nil { + logging.Context(ctx).Error("Failed to set cache", zap.Error(err)) + } + logging.Context(ctx).Info("Login success", zap.String("WxSign", formItem.WxSign)) + reload, err := a.genCustomerToken(ctx, string(marshal)) + reload.Phone = user.Phone + return reload, err +} +func (a *Customer) BindPhone(ctx context.Context, formItem *schema.CustomerBindPhoneForm) (bool, error) { + userID := util.FromUserID(ctx) + num, err := strconv.ParseUint(fmt.Sprintf("%s", userID), 10, 64) + if err != nil { + fmt.Println("转换错误:", err) + return false, err + } + userIDNum := uint(num) + code, b, err := a.Cache.Get(ctx, REGISTER_SMS_CODE, formItem.Phone) + if err != nil { + return false, err + } + if !b { + return false, errors.NotFound("", "验证码过期!") + } + if code != formItem.Code { + return false, errors.NotFound("", "验证码失败!") + } + get, err := a.CustomerDAL.Get(ctx, userIDNum, schema.CustomerQueryOptions{}) + if err != nil || get == nil { + return false, err + } + get.Phone = formItem.Phone + get.Avatar = formItem.Avatar + err = a.CustomerDAL.Update(ctx, get) + if err != nil { + return false, err + } + return true, nil +} + +func (a *Customer) SendSms(ctx context.Context, phone string) (string, error) { + code := generateVerificationCode() + exists, err := a.Cache.Exists(ctx, REGISTER_SMS_CODE, phone) + if err != nil { + return "", err + } + if exists { + return "", errors.NotFound("", "验证码不能重复获取 等待90秒") + } + err = sendVerificationCode(phone, code) + if err != nil { + return "", err + } + err = a.Cache.Set(ctx, REGISTER_SMS_CODE, phone, code, 90*time.Second) + if err != nil { + return "", err + } + return code, nil + +} +func sendVerificationCode(phone, code string) error { + // 这里应该是调用短信服务商的API发送短信 + // 这里只是模拟 + fmt.Printf("向手机号 %s 发送验证码: %s\n", phone, code) + return nil +} +func generateVerificationCode() string { + rand.Seed(time.Now().UnixNano()) + return fmt.Sprintf("%06d", rand.Intn(1000000)) +} +func (a *Customer) genCustomerToken(ctx context.Context, suject string) (*schema.CustomerLoginToken, error) { + token, err := a.Auth.GenerateToken(ctx, suject) + if err != nil { + return nil, err + } + + tokenBuf, err := token.EncodeToJSON() + if err != nil { + return nil, err + } + logging.Context(ctx).Info("Generate user token", zap.Any("token", string(tokenBuf))) + + return &schema.CustomerLoginToken{ + AccessToken: token.GetAccessToken(), + TokenType: token.GetTokenType(), + ExpiresAt: token.GetExpiresAt(), + }, nil +} + +// Query customers from the data access object based on the provided parameters and options. +func (a *Customer) Query(ctx context.Context, params schema.CustomerQueryParam) (*schema.CustomerQueryResult, error) { + params.Pagination = true + + result, err := a.CustomerDAL.Query(ctx, params, schema.CustomerQueryOptions{ + QueryOptions: util.QueryOptions{ + OrderFields: []util.OrderByParam{ + {Field: "created_at", Direction: util.DESC}, + }, + }, + }) + if err != nil { + return nil, err + } + return result, nil +} + +// Get the specified customer from the data access object. +func (a *Customer) Get(ctx context.Context, id uint) (*schema.Customer, error) { + customer, err := a.CustomerDAL.Get(ctx, id) + if err != nil { + return nil, err + } else if customer == nil { + return nil, errors.NotFound("", "Customer not found") + } + return customer, nil +} + +// Create a new customer in the data access object. +func (a *Customer) Create(ctx context.Context, formItem *schema.CustomerForm) (*schema.Customer, error) { + customer := &schema.Customer{} + + if err := formItem.FillTo(customer); err != nil { + return nil, err + } + + err := a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.CustomerDAL.Create(ctx, customer); err != nil { + return err + } + return nil + }) + if err != nil { + return nil, err + } + return customer, nil +} + +// Update the specified customer in the data access object. +func (a *Customer) Update(ctx context.Context, id uint, formItem *schema.CustomerForm) error { + customer, err := a.CustomerDAL.Get(ctx, id) + if err != nil { + return err + } else if customer == nil { + return errors.NotFound("", "Customer not found") + } + + if err := formItem.FillTo(customer); err != nil { + return err + } + customer.UpdatedAt = time.Now() + + return a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.CustomerDAL.Update(ctx, customer); err != nil { + return err + } + return nil + }) +} + +// Delete the specified customer from the data access object. +func (a *Customer) Delete(ctx context.Context, id uint) error { + exists, err := a.CustomerDAL.Exists(ctx, id) + if err != nil { + return err + } else if !exists { + return errors.NotFound("", "Customer not found") + } + + return a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.CustomerDAL.Delete(ctx, id); err != nil { + return err + } + return nil + }) +} diff --git a/internal/mods/customer/biz/product_order.biz.go b/internal/mods/customer/biz/product_order.biz.go new file mode 100644 index 0000000..153efed --- /dev/null +++ b/internal/mods/customer/biz/product_order.biz.go @@ -0,0 +1,104 @@ +package biz + +import ( + "context" + "time" + + "github.guxuan/haibei/internal/mods/customer/dal" + "github.guxuan/haibei/internal/mods/customer/schema" + "github.guxuan/haibei/pkg/errors" + "github.guxuan/haibei/pkg/util" +) + +// Defining the `ProductOrder` business logic. +type ProductOrder struct { + Trans *util.Trans + ProductOrderDAL *dal.ProductOrder +} + +// Query product orders from the data access object based on the provided parameters and options. +func (a *ProductOrder) Query(ctx context.Context, params schema.ProductOrderQueryParam) (*schema.ProductOrderQueryResult, error) { + params.Pagination = true + + result, err := a.ProductOrderDAL.Query(ctx, params, schema.ProductOrderQueryOptions{ + QueryOptions: util.QueryOptions{ + OrderFields: []util.OrderByParam{ + {Field: "created_at", Direction: util.DESC}, + }, + }, + }) + if err != nil { + return nil, err + } + return result, nil +} + +// Get the specified product order from the data access object. +func (a *ProductOrder) Get(ctx context.Context, id uint) (*schema.ProductOrder, error) { + productOrder, err := a.ProductOrderDAL.Get(ctx, id) + if err != nil { + return nil, err + } else if productOrder == nil { + return nil, errors.NotFound("", "Product order not found") + } + return productOrder, nil +} + +// Create a new product order in the data access object. +func (a *ProductOrder) Create(ctx context.Context, formItem *schema.ProductOrderForm) (*schema.ProductOrder, error) { + productOrder := &schema.ProductOrder{} + + if err := formItem.FillTo(productOrder); err != nil { + return nil, err + } + + err := a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.ProductOrderDAL.Create(ctx, productOrder); err != nil { + return err + } + return nil + }) + if err != nil { + return nil, err + } + return productOrder, nil +} + +// Update the specified product order in the data access object. +func (a *ProductOrder) Update(ctx context.Context, id uint, formItem *schema.ProductOrderForm) error { + productOrder, err := a.ProductOrderDAL.Get(ctx, id) + if err != nil { + return err + } else if productOrder == nil { + return errors.NotFound("", "Product order not found") + } + + if err := formItem.FillTo(productOrder); err != nil { + return err + } + productOrder.UpdatedAt = time.Now() + + return a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.ProductOrderDAL.Update(ctx, productOrder); err != nil { + return err + } + return nil + }) +} + +// Delete the specified product order from the data access object. +func (a *ProductOrder) Delete(ctx context.Context, id uint) error { + exists, err := a.ProductOrderDAL.Exists(ctx, id) + if err != nil { + return err + } else if !exists { + return errors.NotFound("", "Product order not found") + } + + return a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.ProductOrderDAL.Delete(ctx, id); err != nil { + return err + } + return nil + }) +} diff --git a/internal/mods/customer/dal/balance.dal.go b/internal/mods/customer/dal/balance.dal.go new file mode 100644 index 0000000..d12a2ca --- /dev/null +++ b/internal/mods/customer/dal/balance.dal.go @@ -0,0 +1,83 @@ +package dal + +import ( + "context" + + "github.guxuan/haibei/internal/mods/customer/schema" + "github.guxuan/haibei/pkg/errors" + "github.guxuan/haibei/pkg/util" + "gorm.io/gorm" +) + +// Get balance storage instance +func GetBalanceDB(ctx context.Context, defDB *gorm.DB) *gorm.DB { + return util.GetDB(ctx, defDB).Model(new(schema.Balance)) +} + +// Defining the `Balance` data access object. +type Balance struct { + DB *gorm.DB +} + +// Query balances from the database based on the provided parameters and options. +func (a *Balance) Query(ctx context.Context, params schema.BalanceQueryParam, opts ...schema.BalanceQueryOptions) (*schema.BalanceQueryResult, error) { + var opt schema.BalanceQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + db := GetBalanceDB(ctx, a.DB) + + var list schema.Balances + pageResult, err := util.WrapPageQuery(ctx, db, params.PaginationParam, opt.QueryOptions, &list) + if err != nil { + return nil, errors.WithStack(err) + } + + queryResult := &schema.BalanceQueryResult{ + PageResult: pageResult, + Data: list, + } + return queryResult, nil +} + +// Get the specified balance from the database. +func (a *Balance) Get(ctx context.Context, id uint, opts ...schema.BalanceQueryOptions) (*schema.Balance, error) { + var opt schema.BalanceQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + item := new(schema.Balance) + ok, err := util.FindOne(ctx, GetBalanceDB(ctx, a.DB).Where("id=?", id), opt.QueryOptions, item) + if err != nil { + return nil, errors.WithStack(err) + } else if !ok { + return nil, nil + } + return item, nil +} + +// Exists checks if the specified balance exists in the database. +func (a *Balance) Exists(ctx context.Context, id uint) (bool, error) { + ok, err := util.Exists(ctx, GetBalanceDB(ctx, a.DB).Where("id=?", id)) + return ok, errors.WithStack(err) +} + +// Create a new balance. +func (a *Balance) Create(ctx context.Context, item *schema.Balance) error { + result := GetBalanceDB(ctx, a.DB).Create(item) + return errors.WithStack(result.Error) +} + +// Update the specified balance in the database. +func (a *Balance) Update(ctx context.Context, item *schema.Balance) error { + result := GetBalanceDB(ctx, a.DB).Where("id=?", item.ID).Select("*").Omit("created_at").Updates(item) + return errors.WithStack(result.Error) +} + +// Delete the specified balance from the database. +func (a *Balance) Delete(ctx context.Context, id uint) error { + result := GetBalanceDB(ctx, a.DB).Where("id=?", id).Delete(new(schema.Balance)) + return errors.WithStack(result.Error) +} diff --git a/internal/mods/customer/dal/customer.dal.go b/internal/mods/customer/dal/customer.dal.go new file mode 100644 index 0000000..8b918a1 --- /dev/null +++ b/internal/mods/customer/dal/customer.dal.go @@ -0,0 +1,99 @@ +package dal + +import ( + "context" + + "github.guxuan/haibei/internal/mods/customer/schema" + "github.guxuan/haibei/pkg/errors" + "github.guxuan/haibei/pkg/util" + "gorm.io/gorm" +) + +// Get customer storage instance +func GetCustomerDB(ctx context.Context, defDB *gorm.DB) *gorm.DB { + return util.GetDB(ctx, defDB).Model(new(schema.Customer)) +} + +// Defining the `Customer` data access object. +type Customer struct { + DB *gorm.DB +} + +// Query customers from the database based on the provided parameters and options. +func (a *Customer) Query(ctx context.Context, params schema.CustomerQueryParam, opts ...schema.CustomerQueryOptions) (*schema.CustomerQueryResult, error) { + var opt schema.CustomerQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + db := GetCustomerDB(ctx, a.DB) + + var list schema.Customers + pageResult, err := util.WrapPageQuery(ctx, db, params.PaginationParam, opt.QueryOptions, &list) + if err != nil { + return nil, errors.WithStack(err) + } + + queryResult := &schema.CustomerQueryResult{ + PageResult: pageResult, + Data: list, + } + return queryResult, nil +} + +// Get the specified customer from the database. +func (a *Customer) Get(ctx context.Context, id uint, opts ...schema.CustomerQueryOptions) (*schema.Customer, error) { + var opt schema.CustomerQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + item := new(schema.Customer) + ok, err := util.FindOne(ctx, GetCustomerDB(ctx, a.DB).Where("id=?", id), opt.QueryOptions, item) + if err != nil { + return nil, errors.WithStack(err) + } else if !ok { + return nil, nil + } + return item, nil +} + +func (a *Customer) GetByWxSign(ctx context.Context, wxSgin string, opts ...schema.CustomerQueryOptions) (*schema.Customer, error) { + var opt schema.CustomerQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + item := new(schema.Customer) + ok, err := util.FindOne(ctx, GetCustomerDB(ctx, a.DB).Where("wx_sign=?", wxSgin), opt.QueryOptions, item) + if err != nil { + return nil, errors.WithStack(err) + } else if !ok { + return nil, nil + } + return item, nil +} + +// Exists checks if the specified customer exists in the database. +func (a *Customer) Exists(ctx context.Context, id uint) (bool, error) { + ok, err := util.Exists(ctx, GetCustomerDB(ctx, a.DB).Where("id=?", id)) + return ok, errors.WithStack(err) +} + +// Create a new customer. +func (a *Customer) Create(ctx context.Context, item *schema.Customer) error { + result := GetCustomerDB(ctx, a.DB).Create(item) + return errors.WithStack(result.Error) +} + +// Update the specified customer in the database. +func (a *Customer) Update(ctx context.Context, item *schema.Customer) error { + result := GetCustomerDB(ctx, a.DB).Where("id=?", item.ID).Select("*").Omit("created_at").Updates(item) + return errors.WithStack(result.Error) +} + +// Delete the specified customer from the database. +func (a *Customer) Delete(ctx context.Context, id uint) error { + result := GetCustomerDB(ctx, a.DB).Where("id=?", id).Delete(new(schema.Customer)) + return errors.WithStack(result.Error) +} diff --git a/internal/mods/customer/dal/product_order.dal.go b/internal/mods/customer/dal/product_order.dal.go new file mode 100644 index 0000000..f991b59 --- /dev/null +++ b/internal/mods/customer/dal/product_order.dal.go @@ -0,0 +1,93 @@ +package dal + +import ( + "context" + + "github.guxuan/haibei/internal/mods/customer/schema" + "github.guxuan/haibei/pkg/errors" + "github.guxuan/haibei/pkg/util" + "gorm.io/gorm" +) + +// Get product order storage instance +func GetProductOrderDB(ctx context.Context, defDB *gorm.DB) *gorm.DB { + return util.GetDB(ctx, defDB).Model(new(schema.ProductOrder)) +} + +// Defining the `ProductOrder` data access object. +type ProductOrder struct { + DB *gorm.DB +} + +// Query product orders from the database based on the provided parameters and options. +func (a *ProductOrder) Query(ctx context.Context, params schema.ProductOrderQueryParam, opts ...schema.ProductOrderQueryOptions) (*schema.ProductOrderQueryResult, error) { + var opt schema.ProductOrderQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + db := GetProductOrderDB(ctx, a.DB) + + var list schema.ProductOrders + pageResult, err := util.WrapPageQuery(ctx, db, params.PaginationParam, opt.QueryOptions, &list) + if err != nil { + return nil, errors.WithStack(err) + } + + queryResult := &schema.ProductOrderQueryResult{ + PageResult: pageResult, + Data: list, + } + return queryResult, nil +} + +// Get the specified product order from the database. +func (a *ProductOrder) Get(ctx context.Context, id uint, opts ...schema.ProductOrderQueryOptions) (*schema.ProductOrder, error) { + var opt schema.ProductOrderQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + item := new(schema.ProductOrder) + ok, err := util.FindOne(ctx, GetProductOrderDB(ctx, a.DB).Where("id=?", id), opt.QueryOptions, item) + if err != nil { + return nil, errors.WithStack(err) + } else if !ok { + return nil, nil + } + return item, nil +} + +// Exists checks if the specified product order exists in the database. +func (a *ProductOrder) ExistsByCustomerID(ctx context.Context, customerID, productId uint) (*schema.ProductOrders, error) { + list := new(schema.ProductOrders) + tx := GetProductOrderDB(ctx, a.DB).Where("customer_id = ? AND product_id = ? ", customerID, productId).Find(list) + if err := tx.Error; err != nil { + return nil, errors.WithStack(err) + } + return list, nil +} + +// Exists checks if the specified product order exists in the database. +func (a *ProductOrder) Exists(ctx context.Context, id uint) (bool, error) { + ok, err := util.Exists(ctx, GetProductOrderDB(ctx, a.DB).Where("id=?", id)) + return ok, errors.WithStack(err) +} + +// Create a new product order. +func (a *ProductOrder) Create(ctx context.Context, item *schema.ProductOrder) error { + result := GetProductOrderDB(ctx, a.DB).Create(item) + return errors.WithStack(result.Error) +} + +// Update the specified product order in the database. +func (a *ProductOrder) Update(ctx context.Context, item *schema.ProductOrder) error { + result := GetProductOrderDB(ctx, a.DB).Where("id=?", item.ID).Select("*").Omit("created_at").Updates(item) + return errors.WithStack(result.Error) +} + +// Delete the specified product order from the database. +func (a *ProductOrder) Delete(ctx context.Context, id uint) error { + result := GetProductOrderDB(ctx, a.DB).Where("id=?", id).Delete(new(schema.ProductOrder)) + return errors.WithStack(result.Error) +} diff --git a/internal/mods/customer/main.go b/internal/mods/customer/main.go new file mode 100644 index 0000000..c9c4062 --- /dev/null +++ b/internal/mods/customer/main.go @@ -0,0 +1,64 @@ +package customer + +import ( + "context" + + "github.guxuan/haibei/internal/config" + + "github.com/gin-gonic/gin" + "github.guxuan/haibei/internal/mods/customer/api" + "github.guxuan/haibei/internal/mods/customer/schema" + "gorm.io/gorm" +) + +type Customer struct { + DB *gorm.DB + CustomerAPI *api.Customer + BalanceAPI *api.Balance + ProductOrderAPI *api.ProductOrder +} + +func (a *Customer) AutoMigrate(ctx context.Context) error { + return a.DB.AutoMigrate(new(schema.Customer), new(schema.Balance), new(schema.ProductOrder)) +} + +func (a *Customer) Init(ctx context.Context) error { + if config.C.Storage.DB.AutoMigrate { + if err := a.AutoMigrate(ctx); err != nil { + return err + } + } + return nil +} + +func (a *Customer) RegisterV1Routers(ctx context.Context, v1 *gin.RouterGroup) error { + customer := v1.Group("customers") + { + customer.GET("", a.CustomerAPI.Query) + customer.GET(":id", a.CustomerAPI.Get) + customer.POST("", a.CustomerAPI.Create) + customer.PUT(":id", a.CustomerAPI.Update) + customer.DELETE(":id", a.CustomerAPI.Delete) + } + balance := v1.Group("balances") + { + balance.GET("", a.BalanceAPI.Query) + balance.GET(":id", a.BalanceAPI.Get) + balance.POST("", a.BalanceAPI.Create) + balance.PUT(":id", a.BalanceAPI.Update) + balance.DELETE(":id", a.BalanceAPI.Delete) + } + productOrder := v1.Group("product-orders") + { + productOrder.GET("", a.ProductOrderAPI.Query) + productOrder.GET(":id", a.ProductOrderAPI.Get) + productOrder.POST("", a.ProductOrderAPI.Create) + productOrder.PUT(":id", a.ProductOrderAPI.Update) + productOrder.DELETE(":id", a.ProductOrderAPI.Delete) + } + return nil +} + +func (a *Customer) Release(ctx context.Context) error { + return nil +} diff --git a/internal/mods/customer/schema/balance.go b/internal/mods/customer/schema/balance.go new file mode 100644 index 0000000..793b15d --- /dev/null +++ b/internal/mods/customer/schema/balance.go @@ -0,0 +1,70 @@ +package schema + +import ( + "github.guxuan/haibei/pkg/util" +) + +// Defining the `Balance` struct. +type Balance struct { + util.BaseModel + CustomerID uint `json:"customerId" gorm:"index;comment:客户id"` + Before int `json:"before" gorm:"not null;comment:改变钱余额" ` + After int `json:"after" gorm:"not null;comment:改变后余额" ` + Change int `json:"change" gorm:"not null;comment:变化量" ` + Reason string `json:"reason" gorm:"size:1024;comment:原因说明" ` + Typer string `json:"type" gorm:"not null;comment:类型" ` + OperatorID uint `json:"operatorId" gorm:"index;comment:关联id"` + CreatorID string `json:"creatorId" gorm:"index;comment:操作人id"` +} + +// Defining the query parameters for the `Balance` struct. +type BalanceQueryParam struct { + util.PaginationParam + CustomerID uint `form:"customerId" ` + Typer string `form:"type" ` + CreatorID string `form:"creatorId" ` +} + +// Defining the query options for the `Balance` struct. +type BalanceQueryOptions struct { + util.QueryOptions +} + +// Defining the query result for the `Balance` struct. +type BalanceQueryResult struct { + Data Balances + PageResult *util.PaginationResult +} + +// Defining the slice of `Balance` struct. +type Balances []*Balance + +// Defining the data structure for creating a `Balance` struct. +type BalanceForm struct { + CustomerID uint `json:"customerId" ` + Before int `json:"before" ` + After int `json:"after" ` + Change int `json:"change" ` + Reason string `json:"reason" ` + Typer string `json:"type" ` + OperatorID uint `json:"operatorId" ` + CreatorID string `json:"creatorId" ` +} + +// A validation function for the `BalanceForm` struct. +func (a *BalanceForm) Validate() error { + return nil +} + +// Convert `BalanceForm` to `Balance` object. +func (a *BalanceForm) FillTo(balance *Balance) error { + balance.CustomerID = a.CustomerID + balance.Before = a.Before + balance.After = a.After + balance.Change = a.Change + balance.Reason = a.Reason + balance.Typer = a.Typer + balance.OperatorID = a.OperatorID + balance.CreatorID = a.CreatorID + return nil +} diff --git a/internal/mods/customer/schema/customer.go b/internal/mods/customer/schema/customer.go new file mode 100644 index 0000000..c6c97f1 --- /dev/null +++ b/internal/mods/customer/schema/customer.go @@ -0,0 +1,105 @@ +package schema + +import ( + "github.guxuan/haibei/pkg/util" +) + +type Customer struct { + util.BaseModel + Name string `json:"name" gorm:"size:128;index;comment:姓名"` + Phone string `json:"phone" gorm:"size:128;index;comment:手机号"` + InviterID uint `json:"inviterId" gorm:"index;comment:邀请人id"` + Birthday string `json:"birthday" gorm:"size:128;index;comment:生日"` + CompanyID uint `json:"companyId" gorm:"index;comment:绑定社区id"` + AreaID uint `json:"areaId" gorm:"index;comment:城市id"` + WxSign string `json:"wxSign" gorm:"size:1024;comment:微信签名token"` + Avatar string `json:"avatar" gorm:"size:1024;comment:头像"` + Grade int `json:"grade" gorm:"not null;default:0;comment:会员等级"` + Balance int `json:"balance" gorm:"not null;default:0;comment:积分余额"` + MentorID string `json:"mentorId" gorm:"index;comment:顾问id"` + Status string `json:"status" gorm:"size:20;index;comment:状态"` +} + +type CustomerQueryParam struct { + util.PaginationParam + LikeName string `form:"name" ` + LikePhone string `form:"phone" ` + CompanyID uint `form:"companyId" ` + Grade int `form:"grade" ` + InviterId uint `form:"inviterId"` + + Status string `form:"status" ` + AreaID uint `form:"areaId"` +} + +type CustomerQueryOptions struct { + util.QueryOptions +} + +// Defining the query result for the `Customer` struct. +type CustomerQueryResult struct { + Data Customers + PageResult *util.PaginationResult +} + +// Defining the slice of `Customer` struct. +type Customers []*Customer + +// Defining the data structure for creating a `Customer` struct. +type CustomerForm struct { + Name string `json:"name" ` + Phone string `json:"phone" ` + Birthday string `json:"birthday" ` + CompanyID uint `json:"companyId" ` + WxSign string `json:"wxSign" ` + AreaID uint `json:"areaId"` + InviterID uint `json:"inviterId"` + Avatar string `json:"avatar" ` + Grade int `json:"grade" ` + Balance int `json:"balance" ` + Status string `json:"status" ` +} + +type CustomerLoginForm struct { + WxSign string `json:"wxSign" binding:"required"` +} + +type CustomerBindPhoneForm struct { + Phone string `json:"phone" binding:"required"` + Code string `json:"code" binding:"required"` + Avatar string `json:"avatar" binding:"required"` + Lat float64 `json:"lat" binding:"required"` + Lon float64 `json:"lon" binding:"required"` +} + +type CustomerLoginToken struct { + AccessToken string `json:"access_token"` // Access token (JWT) + Phone string `json:"phone"` + TokenType string `json:"token_type"` // Token type (Usage: Authorization=${token_type} ${access_token}) + ExpiresAt int64 `json:"expires_at"` // Expired time (Unit: second) +} + +// A validation function for the `CustomerForm` struct. +func (a *CustomerForm) Validate() error { + return nil +} + +// Convert `CustomerForm` to `Customer` object. +func (a *CustomerForm) FillTo(customer *Customer) error { + customer.Name = a.Name + customer.Phone = a.Phone + customer.Birthday = a.Birthday + customer.CompanyID = a.CompanyID + customer.WxSign = a.WxSign + customer.AreaID = a.AreaID + customer.Avatar = a.Avatar + customer.Grade = a.Grade + customer.InviterID = a.InviterID + customer.Balance = a.Balance + if a.Status != "" { + customer.Status = a.Status + } else { + customer.Status = "enabled" + } + return nil +} diff --git a/internal/mods/customer/schema/product_order.go b/internal/mods/customer/schema/product_order.go new file mode 100644 index 0000000..a9ffefd --- /dev/null +++ b/internal/mods/customer/schema/product_order.go @@ -0,0 +1,59 @@ +package schema + +import ( + "github.guxuan/haibei/pkg/util" +) + +// Defining the `ProductOrder` struct. +type ProductOrder struct { + util.BaseModel + CustomerID uint `json:"customerId" gorm:"index;comment:用户id"` + ProductID uint `json:"productId" gorm:"index;comment:商品id"` + MentorID string `json:"mentorId" gorm:"index;comment:顾问id"` + Status string `json:"status" gorm:"size:20;index;comment:状态"` +} + +// De OrderNo string `json:"orderNo" gorm:"size:128;index;comment:订单号"`fining the query parameters for the `ProductOrder` struct. +type ProductOrderQueryParam struct { + util.PaginationParam + CustomerID uint `form:"customerId" ` + ProductID uint `form:"productId" ` + MentorID string `form:"mentorId"` + Status string `form:"status"` +} + +// Defining the query options for the `ProductOrder` struct. +type ProductOrderQueryOptions struct { + util.QueryOptions +} + +// Defining the query result for the `ProductOrder` struct. +type ProductOrderQueryResult struct { + Data ProductOrders + PageResult *util.PaginationResult +} + +// Defining the slice of `ProductOrder` struct. +type ProductOrders []*ProductOrder + +// Defining the data structure for creating a `ProductOrder` struct. +type ProductOrderForm struct { + CustomerID uint `json:"customerId" ` + ProductID uint `json:"productId" ` + MentorID string `json:"mentorId"` + Status string `json:"status"` +} + +// A validation function for the `ProductOrderForm` struct. +func (a *ProductOrderForm) Validate() error { + return nil +} + +// Convert `ProductOrderForm` to `ProductOrder` object. +func (a *ProductOrderForm) FillTo(productOrder *ProductOrder) error { + productOrder.CustomerID = a.CustomerID + productOrder.ProductID = a.ProductID + productOrder.MentorID = a.MentorID + productOrder.Status = a.Status + return nil +} diff --git a/internal/mods/customer/wire.go b/internal/mods/customer/wire.go new file mode 100644 index 0000000..0db05fb --- /dev/null +++ b/internal/mods/customer/wire.go @@ -0,0 +1,21 @@ +package customer + +import ( + "github.com/google/wire" + "github.guxuan/haibei/internal/mods/customer/api" + "github.guxuan/haibei/internal/mods/customer/biz" + "github.guxuan/haibei/internal/mods/customer/dal" +) + +var Set = wire.NewSet( + wire.Struct(new(Customer), "*"), + wire.Struct(new(dal.Customer), "*"), + wire.Struct(new(biz.Customer), "*"), + wire.Struct(new(api.Customer), "*"), + wire.Struct(new(dal.Balance), "*"), + wire.Struct(new(biz.Balance), "*"), + wire.Struct(new(api.Balance), "*"), + wire.Struct(new(dal.ProductOrder), "*"), + wire.Struct(new(biz.ProductOrder), "*"), + wire.Struct(new(api.ProductOrder), "*"), +) diff --git a/internal/mods/mods.go b/internal/mods/mods.go new file mode 100644 index 0000000..a4cfc11 --- /dev/null +++ b/internal/mods/mods.go @@ -0,0 +1,139 @@ +package mods + +import ( + "context" + + "github.com/gin-gonic/gin" + "github.com/google/wire" + "github.guxuan/haibei/internal/mods/activity" + "github.guxuan/haibei/internal/mods/app" + "github.guxuan/haibei/internal/mods/common" + "github.guxuan/haibei/internal/mods/customer" + "github.guxuan/haibei/internal/mods/point" + "github.guxuan/haibei/internal/mods/product" + "github.guxuan/haibei/internal/mods/rbac" +) + +const ( + apiPrefix = "/api/" +) + +// Collection of wire providers +var Set = wire.NewSet( + wire.Struct(new(Mods), "*"), + rbac.Set, + customer.Set, + common.Set, + product.Set, + activity.Set, + point.Set, + app.Set, +) + +type Mods struct { + RBAC *rbac.RBAC + Customer *customer.Customer + Common *common.Common + Product *product.Product + Activity *activity.Activity + Point *point.Point + App *app.App +} + +func (a *Mods) Init(ctx context.Context) error { + if err := a.RBAC.Init(ctx); err != nil { + return err + } + if err := a.Customer.Init(ctx); err != nil { + return err + } + if err := a.Common.Init( + ctx); err != nil { + return err + } + if err := a.Product.Init(ctx); err != nil { + return err + } + if err := a.Activity.Init(ctx); err != nil { + return err + } + if err := a.Point.Init( + ctx, + ); err != nil { + return err + } + if err := a.App.Init(ctx); err != nil { + return err + } + + return nil +} + +func (a *Mods) RouterPrefixes() []string { + return []string{ + apiPrefix, + } +} + +func (a *Mods) RegisterRouters(ctx context.Context, e *gin.Engine) error { + gAPI := e.Group(apiPrefix) + v1 := gAPI.Group("v1") + + if err := a.RBAC.RegisterV1Routers(ctx, v1); err != nil { + return err + } + if err := a.Customer.RegisterV1Routers(ctx, + v1, + ); err != nil { + return err + } + if err := a.Common.RegisterV1Routers(ctx, v1); err != nil { + return err + } + if err := a.Product.RegisterV1Routers(ctx, v1); err != nil { + return err + } + if err := a.Activity.RegisterV1Routers(ctx, + v1, + ); err != nil { + return err + } + if err := a.Point.RegisterV1Routers(ctx, v1); err != nil { + return err + } + if err := a.App.RegisterV1Routers(ctx, v1); err != nil { + return err + } + + return nil +} + +func (a *Mods) Release(ctx context.Context) error { + if err := a.RBAC.Release(ctx); err != nil { + return err + } + if err := a.Customer.Release(ctx); err != nil { + return err + } + if err := a.Common.Release(ctx); err != nil { + return err + } + if err := a.Product.Release(ctx); err != nil { + return err + } + if err := a.Activity.Release(ctx); err != nil { + return err + } + if err := a.Point.Release(ctx); err != nil { + return err + } + if err := a.App.Release( + ctx); err != nil { + return err + } + if err := a.Product.Release(ctx); err != nil { + return err + } + + return nil +} diff --git a/internal/mods/point/api/grade.api.go b/internal/mods/point/api/grade.api.go new file mode 100644 index 0000000..e2ea94d --- /dev/null +++ b/internal/mods/point/api/grade.api.go @@ -0,0 +1,145 @@ +package api + +import ( + "github.com/gin-gonic/gin" + "github.guxuan/haibei/internal/mods/point/biz" + "github.guxuan/haibei/internal/mods/point/schema" + "github.guxuan/haibei/pkg/util" +) + +// Defining the `Grade` api. +type Grade struct { + GradeBIZ *biz.Grade +} + +// @Tags 权益模块 +// @Security ApiKeyAuth +// @Summary Query grade list +// @Param current query int true "pagination index" default(1) +// @Param pageSize query int true "pagination size" default(10) +// @Success 200 {object} util.ResponseResult{data=[]schema.Grade} +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/grades [get] +func (a *Grade) Query(c *gin.Context) { + ctx := c.Request.Context() + var params schema.GradeQueryParam + if err := util.ParseQuery(c, ¶ms); err != nil { + util.ResError(c, err) + return + } + + result, err := a.GradeBIZ.Query(ctx, params) + if err != nil { + util.ResError(c, err) + return + } + util.ResPage(c, result.Data, result.PageResult) +} + +// @Tags 权益模块 +// @Security ApiKeyAuth +// @Summary Get grade record by ID +// @Param id path string true "unique id" +// @Success 200 {object} util.ResponseResult{data=schema.Grade} +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/grades/{id} [get] +func (a *Grade) Get(c *gin.Context) { + ctx := c.Request.Context() + id, err := util.GetQueryID(c) + if err != nil { + util.ResError(c, err) + return + } + item, err := a.GradeBIZ.Get(ctx, id) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, item) +} + +// @Tags 权益模块 +// @Security ApiKeyAuth +// @Summary Create grade record +// @Param body body schema.GradeForm true "Request body" +// @Success 200 {object} util.ResponseResult{data=schema.Grade} +// @Failure 400 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/grades [post] +func (a *Grade) Create(c *gin.Context) { + ctx := c.Request.Context() + item := new(schema.GradeForm) + if err := util.ParseJSON(c, item); err != nil { + util.ResError(c, err) + return + } else if err := item.Validate(); err != nil { + util.ResError(c, err) + return + } + + result, err := a.GradeBIZ.Create(ctx, item) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, result) +} + +// @Tags 权益模块 +// @Security ApiKeyAuth +// @Summary Update grade record by ID +// @Param id path string true "unique id" +// @Param body body schema.GradeForm true "Request body" +// @Success 200 {object} util.ResponseResult +// @Failure 400 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/grades/{id} [put] +func (a *Grade) Update(c *gin.Context) { + ctx := c.Request.Context() + item := new(schema.GradeForm) + if err := util.ParseJSON(c, item); err != nil { + util.ResError(c, err) + return + } else if err := item.Validate(); err != nil { + util.ResError(c, err) + return + } + id, err := util.GetQueryID(c) + if err != nil { + util.ResError(c, err) + return + } + err = a.GradeBIZ.Update(ctx, id, item) + if err != nil { + util.ResError(c, err) + return + } + util.ResOK(c) +} + +// @Tags 权益模块 +// @Security ApiKeyAuth +// @Summary Delete grade record by ID +// @Param id path string true "unique id" +// @Success 200 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/grades/{id} [delete] +func (a *Grade) Delete(c *gin.Context) { + ctx := c.Request.Context() + id, err := util.GetQueryID(c) + if err != nil { + util.ResError(c, err) + return + } + err = a.GradeBIZ.Delete(ctx, id) + if err != nil { + util.ResError(c, err) + return + } + util.ResOK(c) +} diff --git a/internal/mods/point/api/point.api.go b/internal/mods/point/api/point.api.go new file mode 100644 index 0000000..95b7402 --- /dev/null +++ b/internal/mods/point/api/point.api.go @@ -0,0 +1,131 @@ +package api + +import ( + "github.com/gin-gonic/gin" + "github.guxuan/haibei/internal/mods/point/biz" + "github.guxuan/haibei/internal/mods/point/schema" + "github.guxuan/haibei/pkg/util" +) + +// Defining the `Point` api. +type Point struct { + PointBIZ *biz.Point +} + +// @Tags PointAPI +// @Security ApiKeyAuth +// @Summary Query point list +// @Param current query int true "pagination index" default(1) +// @Param pageSize query int true "pagination size" default(10) +// @Success 200 {object} util.ResponseResult{data=[]schema.Point} +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/points [get] +func (a *Point) Query(c *gin.Context) { + ctx := c.Request.Context() + var params schema.PointQueryParam + if err := util.ParseQuery(c, ¶ms); err != nil { + util.ResError(c, err) + return + } + + result, err := a.PointBIZ.Query(ctx, params) + if err != nil { + util.ResError(c, err) + return + } + util.ResPage(c, result.Data, result.PageResult) +} + +// @Tags PointAPI +// @Security ApiKeyAuth +// @Summary Get point record by ID +// @Param id path string true "unique id" +// @Success 200 {object} util.ResponseResult{data=schema.Point} +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/points/{id} [get] +func (a *Point) Get(c *gin.Context) { + ctx := c.Request.Context() + item, err := a.PointBIZ.Get(ctx, c.Param("id")) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, item) +} + +// @Tags PointAPI +// @Security ApiKeyAuth +// @Summary Create point record +// @Param body body schema.PointForm true "Request body" +// @Success 200 {object} util.ResponseResult{data=schema.Point} +// @Failure 400 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/points [post] +func (a *Point) Create(c *gin.Context) { + ctx := c.Request.Context() + item := new(schema.PointForm) + if err := util.ParseJSON(c, item); err != nil { + util.ResError(c, err) + return + } else if err := item.Validate(); err != nil { + util.ResError(c, err) + return + } + + result, err := a.PointBIZ.Create(ctx, item) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, result) +} + +// @Tags PointAPI +// @Security ApiKeyAuth +// @Summary Update point record by ID +// @Param id path string true "unique id" +// @Param body body schema.PointForm true "Request body" +// @Success 200 {object} util.ResponseResult +// @Failure 400 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/points/{id} [put] +func (a *Point) Update(c *gin.Context) { + ctx := c.Request.Context() + item := new(schema.PointForm) + if err := util.ParseJSON(c, item); err != nil { + util.ResError(c, err) + return + } else if err := item.Validate(); err != nil { + util.ResError(c, err) + return + } + + err := a.PointBIZ.Update(ctx, c.Param("id"), item) + if err != nil { + util.ResError(c, err) + return + } + util.ResOK(c) +} + +// @Tags PointAPI +// @Security ApiKeyAuth +// @Summary Delete point record by ID +// @Param id path string true "unique id" +// @Success 200 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/points/{id} [delete] +func (a *Point) Delete(c *gin.Context) { + ctx := c.Request.Context() + err := a.PointBIZ.Delete(ctx, c.Param("id")) + if err != nil { + util.ResError(c, err) + return + } + util.ResOK(c) +} diff --git a/internal/mods/point/biz/grade.biz.go b/internal/mods/point/biz/grade.biz.go new file mode 100644 index 0000000..9927446 --- /dev/null +++ b/internal/mods/point/biz/grade.biz.go @@ -0,0 +1,104 @@ +package biz + +import ( + "context" + "time" + + "github.guxuan/haibei/internal/mods/point/dal" + "github.guxuan/haibei/internal/mods/point/schema" + "github.guxuan/haibei/pkg/errors" + "github.guxuan/haibei/pkg/util" +) + +// Defining the `Grade` business logic. +type Grade struct { + Trans *util.Trans + GradeDAL *dal.Grade +} + +// Query grades from the data access object based on the provided parameters and options. +func (a *Grade) Query(ctx context.Context, params schema.GradeQueryParam) (*schema.GradeQueryResult, error) { + params.Pagination = true + + result, err := a.GradeDAL.Query(ctx, params, schema.GradeQueryOptions{ + QueryOptions: util.QueryOptions{ + OrderFields: []util.OrderByParam{ + {Field: "created_at", Direction: util.DESC}, + }, + }, + }) + if err != nil { + return nil, err + } + return result, nil +} + +// Get the specified grade from the data access object. +func (a *Grade) Get(ctx context.Context, id uint) (*schema.Grade, error) { + grade, err := a.GradeDAL.Get(ctx, id) + if err != nil { + return nil, err + } else if grade == nil { + return nil, errors.NotFound("", "Grade not found") + } + return grade, nil +} + +// Create a new grade in the data access object. +func (a *Grade) Create(ctx context.Context, formItem *schema.GradeForm) (*schema.Grade, error) { + grade := &schema.Grade{} + + if err := formItem.FillTo(grade); err != nil { + return nil, err + } + + err := a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.GradeDAL.Create(ctx, grade); err != nil { + return err + } + return nil + }) + if err != nil { + return nil, err + } + return grade, nil +} + +// Update the specified grade in the data access object. +func (a *Grade) Update(ctx context.Context, id uint, formItem *schema.GradeForm) error { + grade, err := a.GradeDAL.Get(ctx, id) + if err != nil { + return err + } else if grade == nil { + return errors.NotFound("", "Grade not found") + } + + if err := formItem.FillTo(grade); err != nil { + return err + } + grade.UpdatedAt = time.Now() + + return a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.GradeDAL.Update(ctx, grade); err != nil { + return err + } + return nil + }) +} + +// Delete the specified grade from the data access object. +func (a *Grade) Delete(ctx context.Context, id uint) error { + exists, err := a.GradeDAL.Exists(ctx, id) + if err != nil { + return err + } else if !exists { + return errors.NotFound("", "Grade not found") + } + + return a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.GradeDAL.Delete(ctx, id); err != nil { + return err + } + return nil + }) +} diff --git a/internal/mods/point/biz/point.biz.go b/internal/mods/point/biz/point.biz.go new file mode 100644 index 0000000..8f2374c --- /dev/null +++ b/internal/mods/point/biz/point.biz.go @@ -0,0 +1,107 @@ +package biz + +import ( + "context" + "time" + + "github.guxuan/haibei/internal/mods/point/dal" + "github.guxuan/haibei/internal/mods/point/schema" + "github.guxuan/haibei/pkg/errors" + "github.guxuan/haibei/pkg/util" +) + +// Defining the `Point` business logic. +type Point struct { + Trans *util.Trans + PointDAL *dal.Point +} + +// Query points from the data access object based on the provided parameters and options. +func (a *Point) Query(ctx context.Context, params schema.PointQueryParam) (*schema.PointQueryResult, error) { + params.Pagination = true + + result, err := a.PointDAL.Query(ctx, params, schema.PointQueryOptions{ + QueryOptions: util.QueryOptions{ + OrderFields: []util.OrderByParam{ + {Field: "created_at", Direction: util.DESC}, + }, + }, + }) + if err != nil { + return nil, err + } + return result, nil +} + +// Get the specified point from the data access object. +func (a *Point) Get(ctx context.Context, id string) (*schema.Point, error) { + point, err := a.PointDAL.Get(ctx, id) + if err != nil { + return nil, err + } else if point == nil { + return nil, errors.NotFound("", "Point not found") + } + return point, nil +} + +// Create a new point in the data access object. +func (a *Point) Create(ctx context.Context, formItem *schema.PointForm) (*schema.Point, error) { + point := &schema.Point{ + ID: util.NewXID(), + CreatedAt: time.Now(), + } + + if err := formItem.FillTo(point); err != nil { + return nil, err + } + + err := a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.PointDAL.Create(ctx, point); err != nil { + return err + } + return nil + }) + if err != nil { + return nil, err + } + return point, nil +} + +// Update the specified point in the data access object. +func (a *Point) Update(ctx context.Context, id string, formItem *schema.PointForm) error { + point, err := a.PointDAL.Get(ctx, id) + if err != nil { + return err + } else if point == nil { + return errors.NotFound("", "Point not found") + } + + if err := formItem.FillTo(point); err != nil { + return err + } + point.UpdatedAt = time.Now() + + return a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.PointDAL.Update(ctx, point); err != nil { + return err + } + return nil + }) +} + +// Delete the specified point from the data access object. +func (a *Point) Delete(ctx context.Context, id string) error { + exists, err := a.PointDAL.Exists(ctx, id) + if err != nil { + return err + } else if !exists { + return errors.NotFound("", "Point not found") + } + + return a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.PointDAL.Delete(ctx, id); err != nil { + return err + } + return nil + }) +} diff --git a/internal/mods/point/dal/grade.dal.go b/internal/mods/point/dal/grade.dal.go new file mode 100644 index 0000000..74dfa74 --- /dev/null +++ b/internal/mods/point/dal/grade.dal.go @@ -0,0 +1,83 @@ +package dal + +import ( + "context" + + "github.guxuan/haibei/internal/mods/point/schema" + "github.guxuan/haibei/pkg/errors" + "github.guxuan/haibei/pkg/util" + "gorm.io/gorm" +) + +// Get grade storage instance +func GetGradeDB(ctx context.Context, defDB *gorm.DB) *gorm.DB { + return util.GetDB(ctx, defDB).Model(new(schema.Grade)) +} + +// Defining the `Grade` data access object. +type Grade struct { + DB *gorm.DB +} + +// Query grades from the database based on the provided parameters and options. +func (a *Grade) Query(ctx context.Context, params schema.GradeQueryParam, opts ...schema.GradeQueryOptions) (*schema.GradeQueryResult, error) { + var opt schema.GradeQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + db := GetGradeDB(ctx, a.DB) + + var list schema.Grades + pageResult, err := util.WrapPageQuery(ctx, db, params.PaginationParam, opt.QueryOptions, &list) + if err != nil { + return nil, errors.WithStack(err) + } + + queryResult := &schema.GradeQueryResult{ + PageResult: pageResult, + Data: list, + } + return queryResult, nil +} + +// Get the specified grade from the database. +func (a *Grade) Get(ctx context.Context, id uint, opts ...schema.GradeQueryOptions) (*schema.Grade, error) { + var opt schema.GradeQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + item := new(schema.Grade) + ok, err := util.FindOne(ctx, GetGradeDB(ctx, a.DB).Where("id=?", id), opt.QueryOptions, item) + if err != nil { + return nil, errors.WithStack(err) + } else if !ok { + return nil, nil + } + return item, nil +} + +// Exists checks if the specified grade exists in the database. +func (a *Grade) Exists(ctx context.Context, id uint) (bool, error) { + ok, err := util.Exists(ctx, GetGradeDB(ctx, a.DB).Where("id=?", id)) + return ok, errors.WithStack(err) +} + +// Create a new grade. +func (a *Grade) Create(ctx context.Context, item *schema.Grade) error { + result := GetGradeDB(ctx, a.DB).Create(item) + return errors.WithStack(result.Error) +} + +// Update the specified grade in the database. +func (a *Grade) Update(ctx context.Context, item *schema.Grade) error { + result := GetGradeDB(ctx, a.DB).Where("id=?", item.ID).Select("*").Omit("created_at").Updates(item) + return errors.WithStack(result.Error) +} + +// Delete the specified grade from the database. +func (a *Grade) Delete(ctx context.Context, id uint) error { + result := GetGradeDB(ctx, a.DB).Where("id=?", id).Delete(new(schema.Grade)) + return errors.WithStack(result.Error) +} diff --git a/internal/mods/point/dal/point.dal.go b/internal/mods/point/dal/point.dal.go new file mode 100644 index 0000000..0e68bc0 --- /dev/null +++ b/internal/mods/point/dal/point.dal.go @@ -0,0 +1,83 @@ +package dal + +import ( + "context" + + "github.guxuan/haibei/internal/mods/point/schema" + "github.guxuan/haibei/pkg/errors" + "github.guxuan/haibei/pkg/util" + "gorm.io/gorm" +) + +// Get point storage instance +func GetPointDB(ctx context.Context, defDB *gorm.DB) *gorm.DB { + return util.GetDB(ctx, defDB).Model(new(schema.Point)) +} + +// Defining the `Point` data access object. +type Point struct { + DB *gorm.DB +} + +// Query points from the database based on the provided parameters and options. +func (a *Point) Query(ctx context.Context, params schema.PointQueryParam, opts ...schema.PointQueryOptions) (*schema.PointQueryResult, error) { + var opt schema.PointQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + db := GetPointDB(ctx, a.DB) + + var list schema.Points + pageResult, err := util.WrapPageQuery(ctx, db, params.PaginationParam, opt.QueryOptions, &list) + if err != nil { + return nil, errors.WithStack(err) + } + + queryResult := &schema.PointQueryResult{ + PageResult: pageResult, + Data: list, + } + return queryResult, nil +} + +// Get the specified point from the database. +func (a *Point) Get(ctx context.Context, id string, opts ...schema.PointQueryOptions) (*schema.Point, error) { + var opt schema.PointQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + item := new(schema.Point) + ok, err := util.FindOne(ctx, GetPointDB(ctx, a.DB).Where("id=?", id), opt.QueryOptions, item) + if err != nil { + return nil, errors.WithStack(err) + } else if !ok { + return nil, nil + } + return item, nil +} + +// Exists checks if the specified point exists in the database. +func (a *Point) Exists(ctx context.Context, id string) (bool, error) { + ok, err := util.Exists(ctx, GetPointDB(ctx, a.DB).Where("id=?", id)) + return ok, errors.WithStack(err) +} + +// Create a new point. +func (a *Point) Create(ctx context.Context, item *schema.Point) error { + result := GetPointDB(ctx, a.DB).Create(item) + return errors.WithStack(result.Error) +} + +// Update the specified point in the database. +func (a *Point) Update(ctx context.Context, item *schema.Point) error { + result := GetPointDB(ctx, a.DB).Where("id=?", item.ID).Select("*").Omit("created_at").Updates(item) + return errors.WithStack(result.Error) +} + +// Delete the specified point from the database. +func (a *Point) Delete(ctx context.Context, id string) error { + result := GetPointDB(ctx, a.DB).Where("id=?", id).Delete(new(schema.Point)) + return errors.WithStack(result.Error) +} diff --git a/internal/mods/point/main.go b/internal/mods/point/main.go new file mode 100644 index 0000000..b3a1383 --- /dev/null +++ b/internal/mods/point/main.go @@ -0,0 +1,55 @@ +package point + +import ( + "context" + + "github.guxuan/haibei/internal/config" + + "github.com/gin-gonic/gin" + "github.guxuan/haibei/internal/mods/point/api" + "github.guxuan/haibei/internal/mods/point/schema" + "gorm.io/gorm" +) + +type Point struct { + DB *gorm.DB + PointAPI *api.Point + GradeAPI *api.Grade +} + +func (a *Point) AutoMigrate(ctx context.Context) error { + return a.DB.AutoMigrate(new(schema.Point), new(schema.Grade)) +} + +func (a *Point) Init(ctx context.Context) error { + if config.C.Storage.DB.AutoMigrate { + if err := a.AutoMigrate(ctx); err != nil { + return err + } + } + return nil +} + +func (a *Point) RegisterV1Routers(ctx context.Context, v1 *gin.RouterGroup) error { + point := v1.Group("points") + { + point.GET("", a.PointAPI.Query) + point.GET(":id", a.PointAPI.Get) + point.POST("", a.PointAPI.Create) + point.PUT(":id", a.PointAPI.Update) + point.DELETE(":id", a.PointAPI.Delete) + } + grade := v1.Group("grades") + { + grade.GET("", a.GradeAPI.Query) + grade.GET(":id", a.GradeAPI.Get) + grade.POST("", a.GradeAPI.Create) + grade.PUT(":id", a.GradeAPI.Update) + grade.DELETE(":id", a.GradeAPI.Delete) + } + return nil +} + +func (a *Point) Release(ctx context.Context) error { + return nil +} diff --git a/internal/mods/point/schema/grade.go b/internal/mods/point/schema/grade.go new file mode 100644 index 0000000..47e825a --- /dev/null +++ b/internal/mods/point/schema/grade.go @@ -0,0 +1,62 @@ +package schema + +import ( + "github.guxuan/haibei/pkg/util" +) + +// Defining the `Grade` struct. +type Grade struct { + util.BaseModel + Name string `json:"name" gorm:"size:255;not null;index;comment:等级名称"` + Introduce string `json:"introduce" gorm:"size:2048;comment:等级介绍"` + Icon string `json:"icon" gorm:"size:2048;comment:等级icon"` + Score int `json:"score" gorm:"not null;unique;comment:等级积分"` + Lv string `json:"lv" gorm:"index;not null;unique;comment:等级级别"` + Status string `json:"status" gorm:"size:20;index;comment:等级状态"` +} + +// Defining the query parameters for the `Grade` struct. +type GradeQueryParam struct { + util.PaginationParam + Status string `form:"status" ` +} + +// Defining the query options for the `Grade` struct. +type GradeQueryOptions struct { + util.QueryOptions +} + +// Defining the query result for the `Grade` struct. +type GradeQueryResult struct { + Data Grades + PageResult *util.PaginationResult +} + +// Defining the slice of `Grade` struct. +type Grades []*Grade + +// Defining the data structure for creating a `Grade` struct. +type GradeForm struct { + Name string `json:"name"` + Introduce string `json:"introduce" ` + Icon string `json:"icon" ` + Score int `json:"score"` + Lv string `json:"lv"` + Status string `json:"status" ` +} + +// A validation function for the `GradeForm` struct. +func (a *GradeForm) Validate() error { + return nil +} + +// Convert `GradeForm` to `Grade` object. +func (a *GradeForm) FillTo(grade *Grade) error { + grade.Name = a.Name + grade.Introduce = a.Introduce + grade.Icon = a.Icon + grade.Score = a.Score + grade.Lv = a.Lv + grade.Status = a.Status + return nil +} diff --git a/internal/mods/point/schema/point.go b/internal/mods/point/schema/point.go new file mode 100644 index 0000000..17befef --- /dev/null +++ b/internal/mods/point/schema/point.go @@ -0,0 +1,47 @@ +package schema + +import ( + "time" + + "github.guxuan/haibei/pkg/util" +) + +// Defining the `Point` struct. +type Point struct { + ID string `json:"id" gorm:"size:20;primaryKey;"` // Unique ID + CreatedAt time.Time `json:"created_at" gorm:"index;"` // Create time + UpdatedAt time.Time `json:"updated_at" gorm:"index;"` // Update time +} + +// Defining the query parameters for the `Point` struct. +type PointQueryParam struct { + util.PaginationParam +} + +// Defining the query options for the `Point` struct. +type PointQueryOptions struct { + util.QueryOptions +} + +// Defining the query result for the `Point` struct. +type PointQueryResult struct { + Data Points + PageResult *util.PaginationResult +} + +// Defining the slice of `Point` struct. +type Points []*Point + +// Defining the data structure for creating a `Point` struct. +type PointForm struct { +} + +// A validation function for the `PointForm` struct. +func (a *PointForm) Validate() error { + return nil +} + +// Convert `PointForm` to `Point` object. +func (a *PointForm) FillTo(point *Point) error { + return nil +} diff --git a/internal/mods/point/wire.go b/internal/mods/point/wire.go new file mode 100644 index 0000000..f4fe071 --- /dev/null +++ b/internal/mods/point/wire.go @@ -0,0 +1,18 @@ +package point + +import ( + "github.com/google/wire" + "github.guxuan/haibei/internal/mods/point/api" + "github.guxuan/haibei/internal/mods/point/biz" + "github.guxuan/haibei/internal/mods/point/dal" +) + +var Set = wire.NewSet( + wire.Struct(new(Point), "*"), + wire.Struct(new(dal.Point), "*"), + wire.Struct(new(biz.Point), "*"), + wire.Struct(new(api.Point), "*"), + wire.Struct(new(dal.Grade), "*"), + wire.Struct(new(biz.Grade), "*"), + wire.Struct(new(api.Grade), "*"), +) diff --git a/internal/mods/product/api/product.api.go b/internal/mods/product/api/product.api.go new file mode 100644 index 0000000..4d2ef90 --- /dev/null +++ b/internal/mods/product/api/product.api.go @@ -0,0 +1,145 @@ +package api + +import ( + "github.com/gin-gonic/gin" + "github.guxuan/haibei/internal/mods/product/biz" + "github.guxuan/haibei/internal/mods/product/schema" + "github.guxuan/haibei/pkg/util" +) + +// Defining the `Product` api. +type Product struct { + ProductBIZ *biz.Product +} + +// @Tags 权益产品模块 +// @Security ApiKeyAuth +// @Summary Query product list +// @Param current query int true "pagination index" default(1) +// @Param pageSize query int true "pagination size" default(10) +// @Success 200 {object} util.ResponseResult{data=[]schema.Product} +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/products [get] +func (a *Product) Query(c *gin.Context) { + ctx := c.Request.Context() + var params schema.ProductQueryParam + if err := util.ParseQuery(c, ¶ms); err != nil { + util.ResError(c, err) + return + } + + result, err := a.ProductBIZ.Query(ctx, params) + if err != nil { + util.ResError(c, err) + return + } + util.ResPage(c, result.Data, result.PageResult) +} + +// @Tags 权益产品模块 +// @Security ApiKeyAuth +// @Summary Get product record by ID +// @Param id path string true "unique id" +// @Success 200 {object} util.ResponseResult{data=schema.Product} +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/products/{id} [get] +func (a *Product) Get(c *gin.Context) { + ctx := c.Request.Context() + id, err := util.GetQueryID(c) + if err != nil { + util.ResError(c, err) + return + } + item, err := a.ProductBIZ.Get(ctx, id) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, item) +} + +// @Tags 权益产品模块 +// @Security ApiKeyAuth +// @Summary Create product record +// @Param body body schema.ProductForm true "Request body" +// @Success 200 {object} util.ResponseResult{data=schema.Product} +// @Failure 400 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/products [post] +func (a *Product) Create(c *gin.Context) { + ctx := c.Request.Context() + item := new(schema.ProductForm) + if err := util.ParseJSON(c, item); err != nil { + util.ResError(c, err) + return + } else if err := item.Validate(); err != nil { + util.ResError(c, err) + return + } + + result, err := a.ProductBIZ.Create(ctx, item) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, result) +} + +// @Tags 权益产品模块 +// @Security ApiKeyAuth +// @Summary Update product record by ID +// @Param id path string true "unique id" +// @Param body body schema.ProductForm true "Request body" +// @Success 200 {object} util.ResponseResult +// @Failure 400 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/products/{id} [put] +func (a *Product) Update(c *gin.Context) { + ctx := c.Request.Context() + item := new(schema.ProductForm) + if err := util.ParseJSON(c, item); err != nil { + util.ResError(c, err) + return + } else if err := item.Validate(); err != nil { + util.ResError(c, err) + return + } + id, err := util.GetQueryID(c) + if err != nil { + util.ResError(c, err) + return + } + err = a.ProductBIZ.Update(ctx, id, item) + if err != nil { + util.ResError(c, err) + return + } + util.ResOK(c) +} + +// @Tags 权益产品模块 +// @Security ApiKeyAuth +// @Summary Delete product record by ID +// @Param id path string true "unique id" +// @Success 200 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/products/{id} [delete] +func (a *Product) Delete(c *gin.Context) { + ctx := c.Request.Context() + id, err := util.GetQueryID(c) + if err != nil { + util.ResError(c, err) + return + } + err = a.ProductBIZ.Delete(ctx, id) + if err != nil { + util.ResError(c, err) + return + } + util.ResOK(c) +} diff --git a/internal/mods/product/api/product_category.api.go b/internal/mods/product/api/product_category.api.go new file mode 100644 index 0000000..9999ff0 --- /dev/null +++ b/internal/mods/product/api/product_category.api.go @@ -0,0 +1,145 @@ +package api + +import ( + "github.com/gin-gonic/gin" + "github.guxuan/haibei/internal/mods/product/biz" + "github.guxuan/haibei/internal/mods/product/schema" + "github.guxuan/haibei/pkg/util" +) + +// Defining the `ProductCategory` api. +type ProductCategory struct { + ProductCategoryBIZ *biz.ProductCategory +} + +// @Tags 权益产品分类模块 +// @Security ApiKeyAuth +// @Summary Query product category list +// @Param current query int true "pagination index" default(1) +// @Param pageSize query int true "pagination size" default(10) +// @Success 200 {object} util.ResponseResult{data=[]schema.ProductCategory} +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/product-categories [get] +func (a *ProductCategory) Query(c *gin.Context) { + ctx := c.Request.Context() + var params schema.ProductCategoryQueryParam + if err := util.ParseQuery(c, ¶ms); err != nil { + util.ResError(c, err) + return + } + + result, err := a.ProductCategoryBIZ.Query(ctx, params) + if err != nil { + util.ResError(c, err) + return + } + util.ResPage(c, result.Data, result.PageResult) +} + +// @Tags 权益产品分类模块 +// @Security ApiKeyAuth +// @Summary Get product category record by ID +// @Param id path string true "unique id" +// @Success 200 {object} util.ResponseResult{data=schema.ProductCategory} +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/product-categories/{id} [get] +func (a *ProductCategory) Get(c *gin.Context) { + ctx := c.Request.Context() + id, err := util.GetQueryID(c) + if err != nil { + util.ResError(c, err) + return + } + item, err := a.ProductCategoryBIZ.Get(ctx, id) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, item) +} + +// @Tags 权益产品分类模块 +// @Security ApiKeyAuth +// @Summary Create product category record +// @Param body body schema.ProductCategoryForm true "Request body" +// @Success 200 {object} util.ResponseResult{data=schema.ProductCategory} +// @Failure 400 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/product-categories [post] +func (a *ProductCategory) Create(c *gin.Context) { + ctx := c.Request.Context() + item := new(schema.ProductCategoryForm) + if err := util.ParseJSON(c, item); err != nil { + util.ResError(c, err) + return + } else if err := item.Validate(); err != nil { + util.ResError(c, err) + return + } + + result, err := a.ProductCategoryBIZ.Create(ctx, item) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, result) +} + +// @Tags 权益产品分类模块 +// @Security ApiKeyAuth +// @Summary Update product category record by ID +// @Param id path string true "unique id" +// @Param body body schema.ProductCategoryForm true "Request body" +// @Success 200 {object} util.ResponseResult +// @Failure 400 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/product-categories/{id} [put] +func (a *ProductCategory) Update(c *gin.Context) { + ctx := c.Request.Context() + item := new(schema.ProductCategoryForm) + if err := util.ParseJSON(c, item); err != nil { + util.ResError(c, err) + return + } else if err := item.Validate(); err != nil { + util.ResError(c, err) + return + } + id, err := util.GetQueryID(c) + if err != nil { + util.ResError(c, err) + return + } + err = a.ProductCategoryBIZ.Update(ctx, id, item) + if err != nil { + util.ResError(c, err) + return + } + util.ResOK(c) +} + +// @Tags 权益产品分类模块 +// @Security ApiKeyAuth +// @Summary Delete product category record by ID +// @Param id path string true "unique id" +// @Success 200 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/product-categories/{id} [delete] +func (a *ProductCategory) Delete(c *gin.Context) { + ctx := c.Request.Context() + id, err := util.GetQueryID(c) + if err != nil { + util.ResError(c, err) + return + } + err = a.ProductCategoryBIZ.Delete(ctx, id) + if err != nil { + util.ResError(c, err) + return + } + util.ResOK(c) +} diff --git a/internal/mods/product/biz/product.biz.go b/internal/mods/product/biz/product.biz.go new file mode 100644 index 0000000..16ec00d --- /dev/null +++ b/internal/mods/product/biz/product.biz.go @@ -0,0 +1,104 @@ +package biz + +import ( + "context" + "time" + + "github.guxuan/haibei/internal/mods/product/dal" + "github.guxuan/haibei/internal/mods/product/schema" + "github.guxuan/haibei/pkg/errors" + "github.guxuan/haibei/pkg/util" +) + +// Defining the `Product` business logic. +type Product struct { + Trans *util.Trans + ProductDAL *dal.Product +} + +// Query products from the data access object based on the provided parameters and options. +func (a *Product) Query(ctx context.Context, params schema.ProductQueryParam) (*schema.ProductQueryResult, error) { + params.Pagination = true + + result, err := a.ProductDAL.Query(ctx, params, schema.ProductQueryOptions{ + QueryOptions: util.QueryOptions{ + OrderFields: []util.OrderByParam{ + {Field: "created_at", Direction: util.DESC}, + }, + }, + }) + if err != nil { + return nil, err + } + return result, nil +} + +// Get the specified product from the data access object. +func (a *Product) Get(ctx context.Context, id uint) (*schema.Product, error) { + product, err := a.ProductDAL.Get(ctx, id) + if err != nil { + return nil, err + } else if product == nil { + return nil, errors.NotFound("", "Product not found") + } + return product, nil +} + +// Create a new product in the data access object. +func (a *Product) Create(ctx context.Context, formItem *schema.ProductForm) (*schema.Product, error) { + product := &schema.Product{} + + if err := formItem.FillTo(product); err != nil { + return nil, err + } + + err := a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.ProductDAL.Create(ctx, product); err != nil { + return err + } + return nil + }) + if err != nil { + return nil, err + } + return product, nil +} + +// Update the specified product in the data access object. +func (a *Product) Update(ctx context.Context, id uint, formItem *schema.ProductForm) error { + product, err := a.ProductDAL.Get(ctx, id) + if err != nil { + return err + } else if product == nil { + return errors.NotFound("", "Product not found") + } + + if err := formItem.FillTo(product); err != nil { + return err + } + product.UpdatedAt = time.Now() + + return a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.ProductDAL.Update(ctx, product); err != nil { + return err + } + return nil + }) +} + +// Delete the specified product from the data access object. +func (a *Product) Delete(ctx context.Context, id uint) error { + exists, err := a.ProductDAL.Exists(ctx, id) + if err != nil { + return err + } else if !exists { + return errors.NotFound("", "Product not found") + } + + return a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.ProductDAL.Delete(ctx, id); err != nil { + return err + } + return nil + }) +} diff --git a/internal/mods/product/biz/product_category.biz.go b/internal/mods/product/biz/product_category.biz.go new file mode 100644 index 0000000..e77866b --- /dev/null +++ b/internal/mods/product/biz/product_category.biz.go @@ -0,0 +1,104 @@ +package biz + +import ( + "context" + "time" + + "github.guxuan/haibei/internal/mods/product/dal" + "github.guxuan/haibei/internal/mods/product/schema" + "github.guxuan/haibei/pkg/errors" + "github.guxuan/haibei/pkg/util" +) + +// Defining the `ProductCategory` business logic. +type ProductCategory struct { + Trans *util.Trans + ProductCategoryDAL *dal.ProductCategory +} + +// Query product categories from the data access object based on the provided parameters and options. +func (a *ProductCategory) Query(ctx context.Context, params schema.ProductCategoryQueryParam) (*schema.ProductCategoryQueryResult, error) { + params.Pagination = true + + result, err := a.ProductCategoryDAL.Query(ctx, params, schema.ProductCategoryQueryOptions{ + QueryOptions: util.QueryOptions{ + OrderFields: []util.OrderByParam{ + {Field: "created_at", Direction: util.DESC}, + }, + }, + }) + if err != nil { + return nil, err + } + return result, nil +} + +// Get the specified product category from the data access object. +func (a *ProductCategory) Get(ctx context.Context, id uint) (*schema.ProductCategory, error) { + productCategory, err := a.ProductCategoryDAL.Get(ctx, id) + if err != nil { + return nil, err + } else if productCategory == nil { + return nil, errors.NotFound("", "Product category not found") + } + return productCategory, nil +} + +// Create a new product category in the data access object. +func (a *ProductCategory) Create(ctx context.Context, formItem *schema.ProductCategoryForm) (*schema.ProductCategory, error) { + productCategory := &schema.ProductCategory{} + + if err := formItem.FillTo(productCategory); err != nil { + return nil, err + } + + err := a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.ProductCategoryDAL.Create(ctx, productCategory); err != nil { + return err + } + return nil + }) + if err != nil { + return nil, err + } + return productCategory, nil +} + +// Update the specified product category in the data access object. +func (a *ProductCategory) Update(ctx context.Context, id uint, formItem *schema.ProductCategoryForm) error { + productCategory, err := a.ProductCategoryDAL.Get(ctx, id) + if err != nil { + return err + } else if productCategory == nil { + return errors.NotFound("", "Product category not found") + } + + if err := formItem.FillTo(productCategory); err != nil { + return err + } + productCategory.UpdatedAt = time.Now() + + return a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.ProductCategoryDAL.Update(ctx, productCategory); err != nil { + return err + } + return nil + }) +} + +// Delete the specified product category from the data access object. +func (a *ProductCategory) Delete(ctx context.Context, id uint) error { + exists, err := a.ProductCategoryDAL.Exists(ctx, id) + if err != nil { + return err + } else if !exists { + return errors.NotFound("", "Product category not found") + } + + return a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.ProductCategoryDAL.Delete(ctx, id); err != nil { + return err + } + return nil + }) +} diff --git a/internal/mods/product/dal/product.dal.go b/internal/mods/product/dal/product.dal.go new file mode 100644 index 0000000..95a312a --- /dev/null +++ b/internal/mods/product/dal/product.dal.go @@ -0,0 +1,83 @@ +package dal + +import ( + "context" + + "github.guxuan/haibei/internal/mods/product/schema" + "github.guxuan/haibei/pkg/errors" + "github.guxuan/haibei/pkg/util" + "gorm.io/gorm" +) + +// Get product storage instance +func GetProductDB(ctx context.Context, defDB *gorm.DB) *gorm.DB { + return util.GetDB(ctx, defDB).Model(new(schema.Product)) +} + +// Defining the `Product` data access object. +type Product struct { + DB *gorm.DB +} + +// Query products from the database based on the provided parameters and options. +func (a *Product) Query(ctx context.Context, params schema.ProductQueryParam, opts ...schema.ProductQueryOptions) (*schema.ProductQueryResult, error) { + var opt schema.ProductQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + db := GetProductDB(ctx, a.DB).Preload("Category") + + var list schema.Products + pageResult, err := util.WrapPageQuery(ctx, db, params.PaginationParam, opt.QueryOptions, &list) + if err != nil { + return nil, errors.WithStack(err) + } + + queryResult := &schema.ProductQueryResult{ + PageResult: pageResult, + Data: list, + } + return queryResult, nil +} + +// Get the specified product from the database. +func (a *Product) Get(ctx context.Context, id uint, opts ...schema.ProductQueryOptions) (*schema.Product, error) { + var opt schema.ProductQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + item := new(schema.Product) + ok, err := util.FindOne(ctx, GetProductDB(ctx, a.DB).Where("id=?", id), opt.QueryOptions, item) + if err != nil { + return nil, errors.WithStack(err) + } else if !ok { + return nil, nil + } + return item, nil +} + +// Exists checks if the specified product exists in the database. +func (a *Product) Exists(ctx context.Context, id uint) (bool, error) { + ok, err := util.Exists(ctx, GetProductDB(ctx, a.DB).Where("id=?", id)) + return ok, errors.WithStack(err) +} + +// Create a new product. +func (a *Product) Create(ctx context.Context, item *schema.Product) error { + result := GetProductDB(ctx, a.DB).Create(item) + return errors.WithStack(result.Error) +} + +// Update the specified product in the database. +func (a *Product) Update(ctx context.Context, item *schema.Product) error { + result := GetProductDB(ctx, a.DB).Where("id=?", item.ID).Select("*").Omit("created_at").Updates(item) + return errors.WithStack(result.Error) +} + +// Delete the specified product from the database. +func (a *Product) Delete(ctx context.Context, id uint) error { + result := GetProductDB(ctx, a.DB).Where("id=?", id).Delete(new(schema.Product)) + return errors.WithStack(result.Error) +} diff --git a/internal/mods/product/dal/product_category.dal.go b/internal/mods/product/dal/product_category.dal.go new file mode 100644 index 0000000..e208353 --- /dev/null +++ b/internal/mods/product/dal/product_category.dal.go @@ -0,0 +1,83 @@ +package dal + +import ( + "context" + + "github.guxuan/haibei/internal/mods/product/schema" + "github.guxuan/haibei/pkg/errors" + "github.guxuan/haibei/pkg/util" + "gorm.io/gorm" +) + +// Get product category storage instance +func GetProductCategoryDB(ctx context.Context, defDB *gorm.DB) *gorm.DB { + return util.GetDB(ctx, defDB).Model(new(schema.ProductCategory)) +} + +// Defining the `ProductCategory` data access object. +type ProductCategory struct { + DB *gorm.DB +} + +// Query product categories from the database based on the provided parameters and options. +func (a *ProductCategory) Query(ctx context.Context, params schema.ProductCategoryQueryParam, opts ...schema.ProductCategoryQueryOptions) (*schema.ProductCategoryQueryResult, error) { + var opt schema.ProductCategoryQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + db := GetProductCategoryDB(ctx, a.DB) + + var list schema.ProductCategories + pageResult, err := util.WrapPageQuery(ctx, db, params.PaginationParam, opt.QueryOptions, &list) + if err != nil { + return nil, errors.WithStack(err) + } + + queryResult := &schema.ProductCategoryQueryResult{ + PageResult: pageResult, + Data: list, + } + return queryResult, nil +} + +// Get the specified product category from the database. +func (a *ProductCategory) Get(ctx context.Context, id uint, opts ...schema.ProductCategoryQueryOptions) (*schema.ProductCategory, error) { + var opt schema.ProductCategoryQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + item := new(schema.ProductCategory) + ok, err := util.FindOne(ctx, GetProductCategoryDB(ctx, a.DB).Where("id=?", id), opt.QueryOptions, item) + if err != nil { + return nil, errors.WithStack(err) + } else if !ok { + return nil, nil + } + return item, nil +} + +// Exists checks if the specified product category exists in the database. +func (a *ProductCategory) Exists(ctx context.Context, id uint) (bool, error) { + ok, err := util.Exists(ctx, GetProductCategoryDB(ctx, a.DB).Where("id=?", id)) + return ok, errors.WithStack(err) +} + +// Create a new product category. +func (a *ProductCategory) Create(ctx context.Context, item *schema.ProductCategory) error { + result := GetProductCategoryDB(ctx, a.DB).Create(item) + return errors.WithStack(result.Error) +} + +// Update the specified product category in the database. +func (a *ProductCategory) Update(ctx context.Context, item *schema.ProductCategory) error { + result := GetProductCategoryDB(ctx, a.DB).Where("id=?", item.ID).Select("*").Omit("created_at").Updates(item) + return errors.WithStack(result.Error) +} + +// Delete the specified product category from the database. +func (a *ProductCategory) Delete(ctx context.Context, id uint) error { + result := GetProductCategoryDB(ctx, a.DB).Where("id=?", id).Delete(new(schema.ProductCategory)) + return errors.WithStack(result.Error) +} diff --git a/internal/mods/product/main.go b/internal/mods/product/main.go new file mode 100644 index 0000000..caad01f --- /dev/null +++ b/internal/mods/product/main.go @@ -0,0 +1,55 @@ +package product + +import ( + "context" + + "github.guxuan/haibei/internal/config" + + "github.com/gin-gonic/gin" + "github.guxuan/haibei/internal/mods/product/api" + "github.guxuan/haibei/internal/mods/product/schema" + "gorm.io/gorm" +) + +type Product struct { + DB *gorm.DB + ProductAPI *api.Product + ProductCategoryAPI *api.ProductCategory +} + +func (a *Product) AutoMigrate(ctx context.Context) error { + return a.DB.AutoMigrate(new(schema.Product), new(schema.ProductCategory)) +} + +func (a *Product) Init(ctx context.Context) error { + if config.C.Storage.DB.AutoMigrate { + if err := a.AutoMigrate(ctx); err != nil { + return err + } + } + return nil +} + +func (a *Product) RegisterV1Routers(ctx context.Context, v1 *gin.RouterGroup) error { + product := v1.Group("products") + { + product.GET("", a.ProductAPI.Query) + product.GET(":id", a.ProductAPI.Get) + product.POST("", a.ProductAPI.Create) + product.PUT(":id", a.ProductAPI.Update) + product.DELETE(":id", a.ProductAPI.Delete) + } + productCategory := v1.Group("product-categories") + { + productCategory.GET("", a.ProductCategoryAPI.Query) + productCategory.GET(":id", a.ProductCategoryAPI.Get) + productCategory.POST("", a.ProductCategoryAPI.Create) + productCategory.PUT(":id", a.ProductCategoryAPI.Update) + productCategory.DELETE(":id", a.ProductCategoryAPI.Delete) + } + return nil +} + +func (a *Product) Release(ctx context.Context) error { + return nil +} diff --git a/internal/mods/product/schema/product.go b/internal/mods/product/schema/product.go new file mode 100644 index 0000000..76b7713 --- /dev/null +++ b/internal/mods/product/schema/product.go @@ -0,0 +1,98 @@ +package schema + +import ( + "github.guxuan/haibei/pkg/util" + "time" +) + +// Defining the `Product` struct. +type Product struct { + util.BaseModel + CategoryID uint `json:"categoryId" gorm:"index;comment:分类id"` + Typer string `json:"type" gorm:"index;comment:产品类别 product-产品,activity-活动"` + Name string `json:"name" gorm:"size:255;not null;index;comment:名称"` + Price int `json:"price" gorm:"not null;comment:兑换积分金额"` + Stock int `json:"stock" gorm:"not null;default:0;comment:库存"` + MaxNum int `json:"maxNum" gorm:"not null;default:0;comment:最大兑换数量"` + Cover string `json:"cover" gorm:"size:2048;not null;comment:封面图"` + BusinessImgs []string `json:"businessImgs" gorm:"serializer:json;comment:商详图数组"` + Details string `json:"details" gorm:"type:text;comment:详情"` + Instructions string `json:"instructions" gorm:"type:text;comment:使用说明"` + Policy string `json:"policy" gorm:"type:text;comment:退货说明"` + IsHot bool `json:"isHot" gorm:"comment:是否热门"` + AreaID uint `json:"areaId" gorm:"index;comment:城市id"` + Status string `json:"status" gorm:"size:20;index;comment:分类状态"` + ExpireAt *time.Time `json:"expireAt" gorm:"comment:兑换有效期"` + Category ProductCategory `gorm:"foreignKey:CategoryID;references:ID"` // 关联分类 + +} + +// Defining the query parameters for the `Product` struct. +type ProductQueryParam struct { + util.PaginationParam + CategoryID uint `form:"categoryId"` + LikeName string `form:"name"` + IsHot bool `form:"isHot"` + Typer string `form:"type"` + Status string `form:"status"` + AreaID uint `form:"areaId"` +} + +// Defining the query options for the `Product` struct. +type ProductQueryOptions struct { + util.QueryOptions +} + +// Defining the query result for the `Product` struct. +type ProductQueryResult struct { + Data Products + PageResult *util.PaginationResult +} + +// Defining the slice of `Product` struct. +type Products []*Product + +// Defining the data structure for creating a `Product` struct. +type ProductForm struct { + CategoryID uint `json:"categoryId" ` + Name string `json:"name" ` + Typer string `json:"type"` + Price int `json:"price" ` + Stock int `json:"stock" ` + Cover string `json:"cover"` + BusinessImgs []string `json:"businessImgs" ` + IsHot bool `json:"isHot" ` + MaxNum int `json:"maxNum" ` + Details string `json:"details" ` + AreaID uint `json:"areaId"` + ExpireAt *time.Time `json:"expireAt" ` + + Instructions string `json:"instructions" ` + Policy string `json:"policy" ` + Status string `json:"status" ` +} + +// A validation function for the `ProductForm` struct. +func (a *ProductForm) Validate() error { + return nil +} + +// Convert `ProductForm` to `Product` object. +func (a *ProductForm) FillTo(product *Product) error { + product.CategoryID = a.CategoryID + product.Name = a.Name + product.Price = a.Price + product.MaxNum = a.MaxNum + product.Stock = a.Stock + product.Cover = a.Cover + product.Typer = a.Typer + product.ExpireAt = a.ExpireAt + product.BusinessImgs = a.BusinessImgs + product.IsHot = a.IsHot + product.Details = a.Details + product.Instructions = a.Instructions + product.Policy = a.Policy + product.AreaID = a.AreaID + product.Status = a.Status + return nil +} diff --git a/internal/mods/product/schema/product_category.go b/internal/mods/product/schema/product_category.go new file mode 100644 index 0000000..2f80f29 --- /dev/null +++ b/internal/mods/product/schema/product_category.go @@ -0,0 +1,59 @@ +package schema + +import ( + "github.guxuan/haibei/pkg/util" +) + +// Defining the `ProductCategory` struct. +type ProductCategory struct { + util.BaseModel + ParentID uint `json:"parentId" gorm:"index;comment:上级分类id"` + Name string `json:"name" gorm:"size:255;not null;index;comment:分类名称"` + Introduce string `json:"introduce" gorm:"size:2048;comment:分类介绍"` + Icon string `json:"icon" gorm:"size:2048;comment:分类icon"` + Status string `json:"status" gorm:"size:20;index;comment:分类状态"` +} + +// Defining the query parameters for the `ProductCategory` struct. +type ProductCategoryQueryParam struct { + util.PaginationParam +} + +// Defining the query options for the `ProductCategory` struct. +type ProductCategoryQueryOptions struct { + util.QueryOptions + Status string `json:"status" ` +} + +// Defining the query result for the `ProductCategory` struct. +type ProductCategoryQueryResult struct { + Data ProductCategories + PageResult *util.PaginationResult +} + +// Defining the slice of `ProductCategory` struct. +type ProductCategories []*ProductCategory + +// Defining the data structure for creating a `ProductCategory` struct. +type ProductCategoryForm struct { + ParentID uint `json:"parentId" ` + Name string `json:"name" ` + Introduce string `json:"introduce" ` + Icon string `json:"icon"` + Status string `json:"status" ` +} + +// A validation function for the `ProductCategoryForm` struct. +func (a *ProductCategoryForm) Validate() error { + return nil +} + +// Convert `ProductCategoryForm` to `ProductCategory` object. +func (a *ProductCategoryForm) FillTo(productCategory *ProductCategory) error { + productCategory.ParentID = a.ParentID + productCategory.Name = a.Name + productCategory.Introduce = a.Introduce + productCategory.Icon = a.Icon + productCategory.Status = a.Status + return nil +} diff --git a/internal/mods/product/wire.go b/internal/mods/product/wire.go new file mode 100644 index 0000000..6b73cb8 --- /dev/null +++ b/internal/mods/product/wire.go @@ -0,0 +1,18 @@ +package product + +import ( + "github.com/google/wire" + "github.guxuan/haibei/internal/mods/product/api" + "github.guxuan/haibei/internal/mods/product/biz" + "github.guxuan/haibei/internal/mods/product/dal" +) + +var Set = wire.NewSet( + wire.Struct(new(Product), "*"), + wire.Struct(new(dal.Product), "*"), + wire.Struct(new(biz.Product), "*"), + wire.Struct(new(api.Product), "*"), + wire.Struct(new(dal.ProductCategory), "*"), + wire.Struct(new(biz.ProductCategory), "*"), + wire.Struct(new(api.ProductCategory), "*"), +) diff --git a/internal/mods/rbac/api/logger.api.go b/internal/mods/rbac/api/logger.api.go new file mode 100644 index 0000000..7531140 --- /dev/null +++ b/internal/mods/rbac/api/logger.api.go @@ -0,0 +1,45 @@ +package api + +import ( + "github.com/gin-gonic/gin" + "github.guxuan/haibei/internal/mods/rbac/biz" + "github.guxuan/haibei/internal/mods/rbac/schema" + "github.guxuan/haibei/pkg/util" +) + +// Logger management +type Logger struct { + LoggerBIZ *biz.Logger +} + +// @Tags LoggerAPI +// @Security ApiKeyAuth +// @Summary Query logger list +// @Param current query int true "pagination index" default(1) +// @Param pageSize query int true "pagination size" default(10) +// @Param level query string false "log level" +// @Param traceID query string false "trace ID" +// @Param userName query string false "user name" +// @Param tag query string false "log tag" +// @Param message query string false "log message" +// @Param startTime query string false "start time" +// @Param endTime query string false "end time" +// @Success 200 {object} util.ResponseResult{data=[]schema.Logger} +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/loggers [get] +func (a *Logger) Query(c *gin.Context) { + ctx := c.Request.Context() + var params schema.LoggerQueryParam + if err := util.ParseQuery(c, ¶ms); err != nil { + util.ResError(c, err) + return + } + + result, err := a.LoggerBIZ.Query(ctx, params) + if err != nil { + util.ResError(c, err) + return + } + util.ResPage(c, result.Data, result.PageResult) +} diff --git a/internal/mods/rbac/api/login.api.go b/internal/mods/rbac/api/login.api.go new file mode 100644 index 0000000..22afe58 --- /dev/null +++ b/internal/mods/rbac/api/login.api.go @@ -0,0 +1,182 @@ +package api + +import ( + "github.com/gin-gonic/gin" + "github.guxuan/haibei/internal/mods/rbac/biz" + "github.guxuan/haibei/internal/mods/rbac/schema" + "github.guxuan/haibei/pkg/util" +) + +type Login struct { + LoginBIZ *biz.Login +} + +// @Tags LoginAPI +// @Summary Get captcha ID +// @Success 200 {object} util.ResponseResult{data=schema.Captcha} +// @Router /api/v1/captcha/id [get] +func (a *Login) GetCaptcha(c *gin.Context) { + ctx := c.Request.Context() + data, err := a.LoginBIZ.GetCaptcha(ctx) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, data) +} + +// @Tags LoginAPI +// @Summary Response captcha image +// @Param id query string true "Captcha ID" +// @Param reload query number false "Reload captcha image (reload=1)" +// @Produce image/png +// @Success 200 "Captcha image" +// @Failure 404 {object} util.ResponseResult +// @Router /api/v1/captcha/image [get] +func (a *Login) ResponseCaptcha(c *gin.Context) { + ctx := c.Request.Context() + err := a.LoginBIZ.ResponseCaptcha(ctx, c.Writer, c.Query("id"), c.Query("reload") == "1") + if err != nil { + util.ResError(c, err) + } +} + +// @Tags LoginAPI +// @Summary Login system with username and password +// @Param body body schema.LoginForm true "Request body" +// @Success 200 {object} util.ResponseResult{data=schema.LoginToken} +// @Failure 400 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/login [post] +func (a *Login) Login(c *gin.Context) { + ctx := c.Request.Context() + item := new(schema.LoginForm) + if err := util.ParseJSON(c, item); err != nil { + util.ResError(c, err) + return + } + + data, err := a.LoginBIZ.Login(ctx, item.Trim()) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, data) +} + +// @Tags LoginAPI +// @Security ApiKeyAuth +// @Summary Logout system +// @Success 200 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/current/logout [post] +func (a *Login) Logout(c *gin.Context) { + ctx := c.Request.Context() + err := a.LoginBIZ.Logout(ctx) + if err != nil { + util.ResError(c, err) + return + } + util.ResOK(c) +} + +// @Tags LoginAPI +// @Security ApiKeyAuth +// @Summary Refresh current access token +// @Success 200 {object} util.ResponseResult{data=schema.LoginToken} +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/current/refresh-token [post] +func (a *Login) RefreshToken(c *gin.Context) { + ctx := c.Request.Context() + data, err := a.LoginBIZ.RefreshToken(ctx) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, data) +} + +// @Tags LoginAPI +// @Security ApiKeyAuth +// @Summary Get current user info +// @Success 200 {object} util.ResponseResult{data=schema.User} +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/current/user [get] +func (a *Login) GetUserInfo(c *gin.Context) { + ctx := c.Request.Context() + data, err := a.LoginBIZ.GetUserInfo(ctx) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, data) +} + +// @Tags LoginAPI +// @Security ApiKeyAuth +// @Summary Change current user password +// @Param body body schema.UpdateLoginPassword true "Request body" +// @Success 200 {object} util.ResponseResult +// @Failure 400 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/current/password [put] +func (a *Login) UpdatePassword(c *gin.Context) { + ctx := c.Request.Context() + item := new(schema.UpdateLoginPassword) + if err := util.ParseJSON(c, item); err != nil { + util.ResError(c, err) + return + } + + err := a.LoginBIZ.UpdatePassword(ctx, item) + if err != nil { + util.ResError(c, err) + return + } + util.ResOK(c) +} + +// @Tags LoginAPI +// @Security ApiKeyAuth +// @Summary Query current user menus based on the current user role +// @Success 200 {object} util.ResponseResult{data=[]schema.Menu} +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/current/menus [get] +func (a *Login) QueryMenus(c *gin.Context) { + ctx := c.Request.Context() + data, err := a.LoginBIZ.QueryMenus(ctx) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, data) +} + +// @Tags LoginAPI +// @Security ApiKeyAuth +// @Summary Update current user info +// @Param body body schema.UpdateCurrentUser true "Request body" +// @Success 200 {object} util.ResponseResult +// @Failure 400 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/current/user [put] +func (a *Login) UpdateUser(c *gin.Context) { + ctx := c.Request.Context() + item := new(schema.UpdateCurrentUser) + if err := util.ParseJSON(c, item); err != nil { + util.ResError(c, err) + return + } + + err := a.LoginBIZ.UpdateUser(ctx, item) + if err != nil { + util.ResError(c, err) + return + } + util.ResOK(c) +} diff --git a/internal/mods/rbac/api/menu.api.go b/internal/mods/rbac/api/menu.api.go new file mode 100644 index 0000000..8f3b73b --- /dev/null +++ b/internal/mods/rbac/api/menu.api.go @@ -0,0 +1,132 @@ +package api + +import ( + "github.com/gin-gonic/gin" + "github.guxuan/haibei/internal/mods/rbac/biz" + "github.guxuan/haibei/internal/mods/rbac/schema" + "github.guxuan/haibei/pkg/util" +) + +// Menu management for RBAC +type Menu struct { + MenuBIZ *biz.Menu +} + +// @Tags MenuAPI +// @Security ApiKeyAuth +// @Summary Query menu tree data +// @Param code query string false "Code path of menu (like xxx.xxx.xxx)" +// @Param name query string false "Name of menu" +// @Param includeResources query bool false "Whether to include menu resources" +// @Success 200 {object} util.ResponseResult{data=[]schema.Menu} +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/menus [get] +func (a *Menu) Query(c *gin.Context) { + ctx := c.Request.Context() + var params schema.MenuQueryParam + if err := util.ParseQuery(c, ¶ms); err != nil { + util.ResError(c, err) + return + } + + result, err := a.MenuBIZ.Query(ctx, params) + if err != nil { + util.ResError(c, err) + return + } + util.ResPage(c, result.Data, result.PageResult) +} + +// @Tags MenuAPI +// @Security ApiKeyAuth +// @Summary Get menu record by ID +// @Param id path string true "unique id" +// @Success 200 {object} util.ResponseResult{data=schema.Menu} +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/menus/{id} [get] +func (a *Menu) Get(c *gin.Context) { + ctx := c.Request.Context() + item, err := a.MenuBIZ.Get(ctx, c.Param("id")) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, item) +} + +// @Tags MenuAPI +// @Security ApiKeyAuth +// @Summary Create menu record +// @Param body body schema.MenuForm true "Request body" +// @Success 200 {object} util.ResponseResult{data=schema.Menu} +// @Failure 400 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/menus [post] +func (a *Menu) Create(c *gin.Context) { + ctx := c.Request.Context() + item := new(schema.MenuForm) + if err := util.ParseJSON(c, item); err != nil { + util.ResError(c, err) + return + } else if err := item.Validate(); err != nil { + util.ResError(c, err) + return + } + + result, err := a.MenuBIZ.Create(ctx, item) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, result) +} + +// @Tags MenuAPI +// @Security ApiKeyAuth +// @Summary Update menu record by ID +// @Param id path string true "unique id" +// @Param body body schema.MenuForm true "Request body" +// @Success 200 {object} util.ResponseResult +// @Failure 400 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/menus/{id} [put] +func (a *Menu) Update(c *gin.Context) { + ctx := c.Request.Context() + item := new(schema.MenuForm) + if err := util.ParseJSON(c, item); err != nil { + util.ResError(c, err) + return + } else if err := item.Validate(); err != nil { + util.ResError(c, err) + return + } + + err := a.MenuBIZ.Update(ctx, c.Param("id"), item) + if err != nil { + util.ResError(c, err) + return + } + util.ResOK(c) +} + +// @Tags MenuAPI +// @Security ApiKeyAuth +// @Summary Delete menu record by ID +// @Param id path string true "unique id" +// @Success 200 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/menus/{id} [delete] +func (a *Menu) Delete(c *gin.Context) { + ctx := c.Request.Context() + err := a.MenuBIZ.Delete(ctx, c.Param("id")) + if err != nil { + util.ResError(c, err) + return + } + util.ResOK(c) +} diff --git a/internal/mods/rbac/api/role.api.go b/internal/mods/rbac/api/role.api.go new file mode 100644 index 0000000..bfcc411 --- /dev/null +++ b/internal/mods/rbac/api/role.api.go @@ -0,0 +1,133 @@ +package api + +import ( + "github.com/gin-gonic/gin" + "github.guxuan/haibei/internal/mods/rbac/biz" + "github.guxuan/haibei/internal/mods/rbac/schema" + "github.guxuan/haibei/pkg/util" +) + +// Role management for RBAC +type Role struct { + RoleBIZ *biz.Role +} + +// @Tags RoleAPI +// @Security ApiKeyAuth +// @Summary Query role list +// @Param current query int true "pagination index" default(1) +// @Param pageSize query int true "pagination size" default(10) +// @Param name query string false "Display name of role" +// @Param status query string false "Status of role (disabled, enabled)" +// @Success 200 {object} util.ResponseResult{data=[]schema.Role} +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/roles [get] +func (a *Role) Query(c *gin.Context) { + ctx := c.Request.Context() + var params schema.RoleQueryParam + if err := util.ParseQuery(c, ¶ms); err != nil { + util.ResError(c, err) + return + } + + result, err := a.RoleBIZ.Query(ctx, params) + if err != nil { + util.ResError(c, err) + return + } + util.ResPage(c, result.Data, result.PageResult) +} + +// @Tags RoleAPI +// @Security ApiKeyAuth +// @Summary Get role record by ID +// @Param id path string true "unique id" +// @Success 200 {object} util.ResponseResult{data=schema.Role} +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/roles/{id} [get] +func (a *Role) Get(c *gin.Context) { + ctx := c.Request.Context() + item, err := a.RoleBIZ.Get(ctx, c.Param("id")) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, item) +} + +// @Tags RoleAPI +// @Security ApiKeyAuth +// @Summary Create role record +// @Param body body schema.RoleForm true "Request body" +// @Success 200 {object} util.ResponseResult{data=schema.Role} +// @Failure 400 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/roles [post] +func (a *Role) Create(c *gin.Context) { + ctx := c.Request.Context() + item := new(schema.RoleForm) + if err := util.ParseJSON(c, item); err != nil { + util.ResError(c, err) + return + } else if err := item.Validate(); err != nil { + util.ResError(c, err) + return + } + + result, err := a.RoleBIZ.Create(ctx, item) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, result) +} + +// @Tags RoleAPI +// @Security ApiKeyAuth +// @Summary Update role record by ID +// @Param id path string true "unique id" +// @Param body body schema.RoleForm true "Request body" +// @Success 200 {object} util.ResponseResult +// @Failure 400 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/roles/{id} [put] +func (a *Role) Update(c *gin.Context) { + ctx := c.Request.Context() + item := new(schema.RoleForm) + if err := util.ParseJSON(c, item); err != nil { + util.ResError(c, err) + return + } else if err := item.Validate(); err != nil { + util.ResError(c, err) + return + } + + err := a.RoleBIZ.Update(ctx, c.Param("id"), item) + if err != nil { + util.ResError(c, err) + return + } + util.ResOK(c) +} + +// @Tags RoleAPI +// @Security ApiKeyAuth +// @Summary Delete role record by ID +// @Param id path string true "unique id" +// @Success 200 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/roles/{id} [delete] +func (a *Role) Delete(c *gin.Context) { + ctx := c.Request.Context() + err := a.RoleBIZ.Delete(ctx, c.Param("id")) + if err != nil { + util.ResError(c, err) + return + } + util.ResOK(c) +} diff --git a/internal/mods/rbac/api/user.api.go b/internal/mods/rbac/api/user.api.go new file mode 100644 index 0000000..d34ea00 --- /dev/null +++ b/internal/mods/rbac/api/user.api.go @@ -0,0 +1,152 @@ +package api + +import ( + "github.com/gin-gonic/gin" + "github.guxuan/haibei/internal/mods/rbac/biz" + "github.guxuan/haibei/internal/mods/rbac/schema" + "github.guxuan/haibei/pkg/util" +) + +// User management for RBAC +type User struct { + UserBIZ *biz.User +} + +// @Tags UserAPI +// @Security ApiKeyAuth +// @Summary Query user list +// @Param current query int true "pagination index" default(1) +// @Param pageSize query int true "pagination size" default(10) +// @Param username query string false "Username for login" +// @Param name query string false "Name of user" +// @Param status query string false "Status of user (activated, freezed)" +// @Success 200 {object} util.ResponseResult{data=[]schema.User} +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/users [get] +func (a *User) Query(c *gin.Context) { + ctx := c.Request.Context() + var params schema.UserQueryParam + if err := util.ParseQuery(c, ¶ms); err != nil { + util.ResError(c, err) + return + } + + result, err := a.UserBIZ.Query(ctx, params) + if err != nil { + util.ResError(c, err) + return + } + util.ResPage(c, result.Data, result.PageResult) +} + +// @Tags UserAPI +// @Security ApiKeyAuth +// @Summary Get user record by ID +// @Param id path string true "unique id" +// @Success 200 {object} util.ResponseResult{data=schema.User} +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/users/{id} [get] +func (a *User) Get(c *gin.Context) { + ctx := c.Request.Context() + item, err := a.UserBIZ.Get(ctx, c.Param("id")) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, item) +} + +// @Tags UserAPI +// @Security ApiKeyAuth +// @Summary Create user record +// @Param body body schema.UserForm true "Request body" +// @Success 200 {object} util.ResponseResult{data=schema.User} +// @Failure 400 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/users [post] +func (a *User) Create(c *gin.Context) { + ctx := c.Request.Context() + item := new(schema.UserForm) + if err := util.ParseJSON(c, item); err != nil { + util.ResError(c, err) + return + } else if err := item.Validate(); err != nil { + util.ResError(c, err) + return + } + + result, err := a.UserBIZ.Create(ctx, item) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, result) +} + +// @Tags UserAPI +// @Security ApiKeyAuth +// @Summary Update user record by ID +// @Param id path string true "unique id" +// @Param body body schema.UserForm true "Request body" +// @Success 200 {object} util.ResponseResult +// @Failure 400 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/users/{id} [put] +func (a *User) Update(c *gin.Context) { + ctx := c.Request.Context() + item := new(schema.UserForm) + if err := util.ParseJSON(c, item); err != nil { + util.ResError(c, err) + return + } else if err := item.Validate(); err != nil { + util.ResError(c, err) + return + } + + err := a.UserBIZ.Update(ctx, c.Param("id"), item) + if err != nil { + util.ResError(c, err) + return + } + util.ResOK(c) +} + +// @Tags UserAPI +// @Security ApiKeyAuth +// @Summary Delete user record by ID +// @Param id path string true "unique id" +// @Success 200 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/users/{id} [delete] +func (a *User) Delete(c *gin.Context) { + ctx := c.Request.Context() + err := a.UserBIZ.Delete(ctx, c.Param("id")) + if err != nil { + util.ResError(c, err) + return + } + util.ResOK(c) +} + +// @Tags UserAPI +// @Security ApiKeyAuth +// @Summary Reset user password by ID +// @Param id path string true "unique id" +// @Success 200 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/users/{id}/reset-pwd [patch] +func (a *User) ResetPassword(c *gin.Context) { + ctx := c.Request.Context() + err := a.UserBIZ.ResetPassword(ctx, c.Param("id")) + if err != nil { + util.ResError(c, err) + return + } + util.ResOK(c) +} diff --git a/internal/mods/rbac/biz/logger.biz.go b/internal/mods/rbac/biz/logger.biz.go new file mode 100644 index 0000000..06ae5f7 --- /dev/null +++ b/internal/mods/rbac/biz/logger.biz.go @@ -0,0 +1,31 @@ +package biz + +import ( + "context" + + "github.guxuan/haibei/internal/mods/rbac/dal" + "github.guxuan/haibei/internal/mods/rbac/schema" + "github.guxuan/haibei/pkg/util" +) + +// Logger management +type Logger struct { + LoggerDAL *dal.Logger +} + +// Query loggers from the data access object based on the provided parameters and options. +func (a *Logger) Query(ctx context.Context, params schema.LoggerQueryParam) (*schema.LoggerQueryResult, error) { + params.Pagination = true + + result, err := a.LoggerDAL.Query(ctx, params, schema.LoggerQueryOptions{ + QueryOptions: util.QueryOptions{ + OrderFields: []util.OrderByParam{ + {Field: "created_at", Direction: util.DESC}, + }, + }, + }) + if err != nil { + return nil, err + } + return result, nil +} diff --git a/internal/mods/rbac/biz/login.biz.go b/internal/mods/rbac/biz/login.biz.go new file mode 100644 index 0000000..2f392b1 --- /dev/null +++ b/internal/mods/rbac/biz/login.biz.go @@ -0,0 +1,408 @@ +package biz + +import ( + "context" + "encoding/json" + dal2 "github.guxuan/haibei/internal/mods/customer/dal" + "net/http" + "sort" + "time" + + "github.com/LyricTian/captcha" + "github.com/gin-gonic/gin" + "github.guxuan/haibei/internal/config" + "github.guxuan/haibei/internal/mods/rbac/dal" + "github.guxuan/haibei/internal/mods/rbac/schema" + "github.guxuan/haibei/pkg/cachex" + "github.guxuan/haibei/pkg/crypto/hash" + "github.guxuan/haibei/pkg/errors" + "github.guxuan/haibei/pkg/jwtx" + "github.guxuan/haibei/pkg/logging" + "github.guxuan/haibei/pkg/util" + "go.uber.org/zap" +) + +// Login management for RBAC +type Login struct { + Cache cachex.Cacher + Auth jwtx.Auther + UserDAL *dal.User + UserRoleDAL *dal.UserRole + CustomerDAL *dal2.Customer + MenuDAL *dal.Menu + UserBIZ *User +} +type JwtResp struct { + ID string `json:"id"` + Typer string `json:"typer"` +} + +func (a *Login) ParseUserID(c *gin.Context) (string, error) { + rootID := config.C.General.Root.ID + var subject jwtx.JwtSubject + subject.Typer = "SYSTEM" + subject.ID = rootID + marshal, _ := json.Marshal(subject) + if config.C.Middleware.Auth.Disable { + return string(marshal), nil + } + + invalidToken := errors.Unauthorized(config.ErrInvalidTokenID, "Invalid access token") + token := util.GetToken(c) + if token == "" { + return "", invalidToken + } + + ctx := c.Request.Context() + ctx = util.NewUserToken(ctx, token) + + jwtStr, err := a.Auth.ParseSubject(ctx, token) + var userSubject JwtResp + err = json.Unmarshal([]byte(jwtStr), &userSubject) + userID := userSubject.ID + if err != nil { + if errors.Is(err, jwtx.ErrInvalidToken) { + return "", invalidToken + } + return "", err + } else if userSubject.ID == rootID { + c.Request = c.Request.WithContext(util.NewIsRootUser(ctx)) + return jwtStr, nil + } + + userCacheVal, ok, err := a.Cache.Get(ctx, config.CacheNSForUser, userID) + if err != nil { + return "", err + } else if ok { + userCache := util.ParseUserCache(userCacheVal) + c.Request = c.Request.WithContext(util.NewUserCache(ctx, userCache)) + return jwtStr, nil + } + if userSubject.Typer == "SYSTEM" { + // Check user status, if not activated, force to logout + user, err := a.UserDAL.Get(ctx, userID, schema.UserQueryOptions{ + QueryOptions: util.QueryOptions{SelectFields: []string{"status"}}, + }) + if err != nil { + return "", err + } else if user == nil || user.Status != schema.UserStatusActivated { + return "", invalidToken + } + + roleIDs, err := a.UserBIZ.GetRoleIDs(ctx, userID) + if err != nil { + return "", err + } + + userCache := util.UserCache{ + RoleIDs: roleIDs, + } + err = a.Cache.Set(ctx, config.CacheNSForUser, userID, userCache.String()) + if err != nil { + return "", err + } + + c.Request = c.Request.WithContext(util.NewUserCache(ctx, userCache)) + } else { + userCache := util.UserCache{ + RoleIDs: []string{}, + } + err = a.Cache.Set(ctx, config.CacheNSForUser, userID, userCache.String()) + if err != nil { + return "", err + } + + c.Request = c.Request.WithContext(util.NewUserCache(ctx, userCache)) + } + + return jwtStr, nil +} + +// This function generates a new captcha ID and returns it as a `schema.Captcha` struct. The length of +// the captcha is determined by the `config.C.Util.Captcha.Length` configuration value. +func (a *Login) GetCaptcha(ctx context.Context) (*schema.Captcha, error) { + return &schema.Captcha{ + CaptchaID: captcha.NewLen(config.C.Util.Captcha.Length), + }, nil +} + +// Response captcha image +func (a *Login) ResponseCaptcha(ctx context.Context, w http.ResponseWriter, id string, reload bool) error { + if reload && !captcha.Reload(id) { + return errors.NotFound("", "Captcha id not found") + } + + err := captcha.WriteImage(w, id, config.C.Util.Captcha.Width, config.C.Util.Captcha.Height) + if err != nil { + if err == captcha.ErrNotFound { + return errors.NotFound("", "Captcha id not found") + } + return err + } + + w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate") + w.Header().Set("Pragma", "no-cache") + w.Header().Set("Expires", "0") + w.Header().Set("Content-Type", "image/png") + return nil +} + +func (a *Login) genUserToken(ctx context.Context, userID string) (*schema.LoginToken, error) { + logging.Context(ctx).Info("Login by root") + var subject jwtx.JwtSubject + subject.Typer = "SYSTEM" + subject.ID = userID + marshal, err := json.Marshal(subject) + if err != nil { + return nil, err + } + token, err := a.Auth.GenerateToken(ctx, string(marshal)) + if err != nil { + return nil, err + } + + tokenBuf, err := token.EncodeToJSON() + if err != nil { + return nil, err + } + logging.Context(ctx).Info("Generate user token", zap.Any("token", string(tokenBuf))) + + return &schema.LoginToken{ + AccessToken: token.GetAccessToken(), + TokenType: token.GetTokenType(), + ExpiresAt: token.GetExpiresAt(), + }, nil +} + +func (a *Login) Login(ctx context.Context, formItem *schema.LoginForm) (*schema.LoginToken, error) { + // verify captcha + //if !captcha.VerifyString(formItem.CaptchaID, formItem.CaptchaCode) { + // return nil, errors.BadRequest(config.ErrInvalidCaptchaID, "Incorrect captcha") + //} + + ctx = logging.NewTag(ctx, logging.TagKeyLogin) + + // login by root + if formItem.Username == config.C.General.Root.Username { + if formItem.Password != config.C.General.Root.Password { + return nil, errors.BadRequest(config.ErrInvalidUsernameOrPassword, "Incorrect username or password") + } + + userID := config.C.General.Root.ID + ctx = logging.NewUserID(ctx, userID) + + return a.genUserToken(ctx, userID) + } + + // get user info + user, err := a.UserDAL.GetByUsername(ctx, formItem.Username, schema.UserQueryOptions{ + QueryOptions: util.QueryOptions{ + SelectFields: []string{"id", "password", "status"}, + }, + }) + if err != nil { + return nil, err + } else if user == nil { + return nil, errors.BadRequest(config.ErrInvalidUsernameOrPassword, "Incorrect username or password") + } else if user.Status != schema.UserStatusActivated { + return nil, errors.BadRequest("", "User status is not activated, please contact the administrator") + } + + // check password + if err := hash.CompareHashAndPassword(user.Password, formItem.Password); err != nil { + return nil, errors.BadRequest(config.ErrInvalidUsernameOrPassword, "Incorrect username or password") + } + + userID := user.ID + ctx = logging.NewUserID(ctx, userID) + + // set user cache with role ids + roleIDs, err := a.UserBIZ.GetRoleIDs(ctx, userID) + if err != nil { + return nil, err + } + + userCache := util.UserCache{RoleIDs: roleIDs} + err = a.Cache.Set(ctx, config.CacheNSForUser, userID, userCache.String(), + time.Duration(config.C.Dictionary.UserCacheExp)*time.Hour) + if err != nil { + logging.Context(ctx).Error("Failed to set cache", zap.Error(err)) + } + logging.Context(ctx).Info("Login success", zap.String("username", formItem.Username)) + + // generate token + return a.genUserToken(ctx, userID) +} + +func (a *Login) RefreshToken(ctx context.Context) (*schema.LoginToken, error) { + userID := util.FromUserID(ctx) + + user, err := a.UserDAL.Get(ctx, userID.(string), schema.UserQueryOptions{ + QueryOptions: util.QueryOptions{ + SelectFields: []string{"status"}, + }, + }) + if err != nil { + return nil, err + } else if user == nil { + return nil, errors.BadRequest("", "Incorrect user") + } else if user.Status != schema.UserStatusActivated { + return nil, errors.BadRequest("", "User status is not activated, please contact the administrator") + } + + return a.genUserToken(ctx, userID.(string)) +} + +func (a *Login) Logout(ctx context.Context) error { + userToken := util.FromUserToken(ctx) + if userToken == "" { + return nil + } + + ctx = logging.NewTag(ctx, logging.TagKeyLogout) + if err := a.Auth.DestroyToken(ctx, userToken); err != nil { + return err + } + + userID := util.FromUserID(ctx) + err := a.Cache.Delete(ctx, config.CacheNSForUser, userID.(string)) + if err != nil { + logging.Context(ctx).Error("Failed to delete user cache", zap.Error(err)) + } + logging.Context(ctx).Info("Logout success") + + return nil +} + +// Get user info +func (a *Login) GetUserInfo(ctx context.Context) (*schema.User, error) { + if util.FromIsRootUser(ctx) { + return &schema.User{ + ID: config.C.General.Root.ID, + Username: config.C.General.Root.Username, + Name: config.C.General.Root.Name, + Status: schema.UserStatusActivated, + }, nil + } + + userID := util.FromUserID(ctx) + user, err := a.UserDAL.Get(ctx, userID.(string), schema.UserQueryOptions{ + QueryOptions: util.QueryOptions{ + OmitFields: []string{"password"}, + }, + }) + if err != nil { + return nil, err + } else if user == nil { + return nil, errors.NotFound("", "User not found") + } + + userRoleResult, err := a.UserRoleDAL.Query(ctx, schema.UserRoleQueryParam{ + UserID: userID.(string), + }, schema.UserRoleQueryOptions{ + JoinRole: true, + }) + if err != nil { + return nil, err + } + user.Roles = userRoleResult.Data + + return user, nil +} + +// Change login password +func (a *Login) UpdatePassword(ctx context.Context, updateItem *schema.UpdateLoginPassword) error { + if util.FromIsRootUser(ctx) { + return errors.BadRequest("", "Root user cannot change password") + } + + userID := util.FromUserID(ctx) + user, err := a.UserDAL.Get(ctx, userID.(string), schema.UserQueryOptions{ + QueryOptions: util.QueryOptions{ + SelectFields: []string{"password"}, + }, + }) + if err != nil { + return err + } else if user == nil { + return errors.NotFound("", "User not found") + } + + // check old password + if err := hash.CompareHashAndPassword(user.Password, updateItem.OldPassword); err != nil { + return errors.BadRequest("", "Incorrect old password") + } + + // update password + newPassword, err := hash.GeneratePassword(updateItem.NewPassword) + if err != nil { + return err + } + return a.UserDAL.UpdatePasswordByID(ctx, userID.(string), newPassword) +} + +// Query menus based on user permissions +func (a *Login) QueryMenus(ctx context.Context) (schema.Menus, error) { + menuQueryParams := schema.MenuQueryParam{ + Status: schema.MenuStatusEnabled, + } + + isRoot := util.FromIsRootUser(ctx) + if !isRoot { + menuQueryParams.UserID = util.FromUserID(ctx).(string) + } + menuResult, err := a.MenuDAL.Query(ctx, menuQueryParams, schema.MenuQueryOptions{ + QueryOptions: util.QueryOptions{ + OrderFields: schema.MenusOrderParams, + }, + }) + if err != nil { + return nil, err + } else if isRoot { + return menuResult.Data.ToTree(), nil + } + + // fill parent menus + if parentIDs := menuResult.Data.SplitParentIDs(); len(parentIDs) > 0 { + var missMenusIDs []string + menuIDMapper := menuResult.Data.ToMap() + for _, parentID := range parentIDs { + if _, ok := menuIDMapper[parentID]; !ok { + missMenusIDs = append(missMenusIDs, parentID) + } + } + if len(missMenusIDs) > 0 { + parentResult, err := a.MenuDAL.Query(ctx, schema.MenuQueryParam{ + InIDs: missMenusIDs, + }) + if err != nil { + return nil, err + } + menuResult.Data = append(menuResult.Data, parentResult.Data...) + sort.Sort(menuResult.Data) + } + } + + return menuResult.Data.ToTree(), nil +} + +// Update current user info +func (a *Login) UpdateUser(ctx context.Context, updateItem *schema.UpdateCurrentUser) error { + if util.FromIsRootUser(ctx) { + return errors.BadRequest("", "Root user cannot update") + } + + userID := util.FromUserID(ctx) + user, err := a.UserDAL.Get(ctx, userID.(string)) + if err != nil { + return err + } else if user == nil { + return errors.NotFound("", "User not found") + } + + user.Name = updateItem.Name + user.Phone = updateItem.Phone + user.Email = updateItem.Email + user.Remark = updateItem.Remark + return a.UserDAL.Update(ctx, user, "name", "phone", "email", "remark") +} diff --git a/internal/mods/rbac/biz/menu.biz.go b/internal/mods/rbac/biz/menu.biz.go new file mode 100644 index 0000000..33916dd --- /dev/null +++ b/internal/mods/rbac/biz/menu.biz.go @@ -0,0 +1,515 @@ +package biz + +import ( + "context" + "fmt" + "os" + "path/filepath" + "sort" + "strings" + "time" + + "github.guxuan/haibei/internal/config" + "github.guxuan/haibei/internal/mods/rbac/dal" + "github.guxuan/haibei/internal/mods/rbac/schema" + "github.guxuan/haibei/pkg/cachex" + "github.guxuan/haibei/pkg/encoding/json" + "github.guxuan/haibei/pkg/encoding/yaml" + "github.guxuan/haibei/pkg/errors" + "github.guxuan/haibei/pkg/logging" + "github.guxuan/haibei/pkg/util" + "go.uber.org/zap" +) + +// Menu management for RBAC +type Menu struct { + Cache cachex.Cacher + Trans *util.Trans + MenuDAL *dal.Menu + MenuResourceDAL *dal.MenuResource + RoleMenuDAL *dal.RoleMenu +} + +func (a *Menu) InitFromFile(ctx context.Context, menuFile string) error { + f, err := os.ReadFile(menuFile) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + logging.Context(ctx).Warn("Menu data file not found, skip init menu data from file", zap.String("file", menuFile)) + return nil + } + return err + } + + var menus schema.Menus + if ext := filepath.Ext(menuFile); ext == ".json" { + if err := json.Unmarshal(f, &menus); err != nil { + return errors.Wrapf(err, "Unmarshal JSON file '%s' failed", menuFile) + } + } else if ext == ".yaml" || ext == ".yml" { + if err := yaml.Unmarshal(f, &menus); err != nil { + return errors.Wrapf(err, "Unmarshal YAML file '%s' failed", menuFile) + } + } else { + return errors.Errorf("Unsupported file type '%s'", ext) + } + + return a.Trans.Exec(ctx, func(ctx context.Context) error { + return a.createInBatchByParent(ctx, menus, nil) + }) +} + +func (a *Menu) createInBatchByParent(ctx context.Context, items schema.Menus, parent *schema.Menu) error { + total := len(items) + + for i, item := range items { + var parentID string + if parent != nil { + parentID = parent.ID + } + + var ( + menuItem *schema.Menu + err error + ) + + if item.ID != "" { + menuItem, err = a.MenuDAL.Get(ctx, item.ID) + } else if item.Code != "" { + menuItem, err = a.MenuDAL.GetByCodeAndParentID(ctx, item.Code, parentID) + } else if item.Name != "" { + menuItem, err = a.MenuDAL.GetByNameAndParentID(ctx, item.Name, parentID) + } + + if err != nil { + return err + } + + if item.Status == "" { + item.Status = schema.MenuStatusEnabled + } + + if menuItem != nil { + changed := false + if menuItem.Name != item.Name { + menuItem.Name = item.Name + changed = true + } + if menuItem.Description != item.Description { + menuItem.Description = item.Description + changed = true + } + if menuItem.Path != item.Path { + menuItem.Path = item.Path + changed = true + } + if menuItem.Type != item.Type { + menuItem.Type = item.Type + changed = true + } + if menuItem.Sequence != item.Sequence { + menuItem.Sequence = item.Sequence + changed = true + } + if menuItem.Status != item.Status { + menuItem.Status = item.Status + changed = true + } + if changed { + menuItem.UpdatedAt = time.Now() + if err := a.MenuDAL.Update(ctx, menuItem); err != nil { + return err + } + } + } else { + if item.ID == "" { + item.ID = util.NewXID() + } + if item.Sequence == 0 { + item.Sequence = total - i + } + item.ParentID = parentID + if parent != nil { + item.ParentPath = parent.ParentPath + parentID + util.TreePathDelimiter + } + menuItem = item + if err := a.MenuDAL.Create(ctx, item); err != nil { + return err + } + } + + for _, res := range item.Resources { + if res.ID != "" { + exists, err := a.MenuResourceDAL.Exists(ctx, res.ID) + if err != nil { + return err + } else if exists { + continue + } + } + + if res.Path != "" { + exists, err := a.MenuResourceDAL.ExistsMethodPathByMenuID(ctx, res.Method, res.Path, menuItem.ID) + if err != nil { + return err + } else if exists { + continue + } + } + if res.ID == "" { + res.ID = util.NewXID() + } + res.MenuID = menuItem.ID + if err := a.MenuResourceDAL.Create(ctx, res); err != nil { + return err + } + } + + if item.Children != nil { + if err := a.createInBatchByParent(ctx, *item.Children, menuItem); err != nil { + return err + } + } + } + return nil +} + +// Query menus from the data access object based on the provided parameters and options. +func (a *Menu) Query(ctx context.Context, params schema.MenuQueryParam) (*schema.MenuQueryResult, error) { + params.Pagination = false + + if err := a.fillQueryParam(ctx, ¶ms); err != nil { + return nil, err + } + + result, err := a.MenuDAL.Query(ctx, params, schema.MenuQueryOptions{ + QueryOptions: util.QueryOptions{ + OrderFields: schema.MenusOrderParams, + }, + }) + if err != nil { + return nil, err + } + + if params.LikeName != "" || params.CodePath != "" { + result.Data, err = a.appendChildren(ctx, result.Data) + if err != nil { + return nil, err + } + } + + if params.IncludeResources { + for i, item := range result.Data { + resResult, err := a.MenuResourceDAL.Query(ctx, schema.MenuResourceQueryParam{ + MenuID: item.ID, + }) + if err != nil { + return nil, err + } + result.Data[i].Resources = resResult.Data + } + } + + result.Data = result.Data.ToTree() + return result, nil +} + +func (a *Menu) fillQueryParam(ctx context.Context, params *schema.MenuQueryParam) error { + if params.CodePath != "" { + var ( + codes []string + lastMenu schema.Menu + ) + for _, code := range strings.Split(params.CodePath, util.TreePathDelimiter) { + if code == "" { + continue + } + codes = append(codes, code) + menu, err := a.MenuDAL.GetByCodeAndParentID(ctx, code, lastMenu.ParentID, schema.MenuQueryOptions{ + QueryOptions: util.QueryOptions{ + SelectFields: []string{"id", "parent_id", "parent_path"}, + }, + }) + if err != nil { + return err + } else if menu == nil { + return errors.NotFound("", "Menu not found by code '%s'", strings.Join(codes, util.TreePathDelimiter)) + } + lastMenu = *menu + } + params.ParentPathPrefix = lastMenu.ParentPath + lastMenu.ID + util.TreePathDelimiter + } + return nil +} + +func (a *Menu) appendChildren(ctx context.Context, data schema.Menus) (schema.Menus, error) { + if len(data) == 0 { + return data, nil + } + + existsInData := func(id string) bool { + for _, item := range data { + if item.ID == id { + return true + } + } + return false + } + + for _, item := range data { + childResult, err := a.MenuDAL.Query(ctx, schema.MenuQueryParam{ + ParentPathPrefix: item.ParentPath + item.ID + util.TreePathDelimiter, + }) + if err != nil { + return nil, err + } + for _, child := range childResult.Data { + if existsInData(child.ID) { + continue + } + data = append(data, child) + } + } + + if parentIDs := data.SplitParentIDs(); len(parentIDs) > 0 { + parentResult, err := a.MenuDAL.Query(ctx, schema.MenuQueryParam{ + InIDs: parentIDs, + }) + if err != nil { + return nil, err + } + for _, p := range parentResult.Data { + if existsInData(p.ID) { + continue + } + data = append(data, p) + } + } + sort.Sort(data) + + return data, nil +} + +// Get the specified menu from the data access object. +func (a *Menu) Get(ctx context.Context, id string) (*schema.Menu, error) { + menu, err := a.MenuDAL.Get(ctx, id) + if err != nil { + return nil, err + } else if menu == nil { + return nil, errors.NotFound("", "Menu not found") + } + + menuResResult, err := a.MenuResourceDAL.Query(ctx, schema.MenuResourceQueryParam{ + MenuID: menu.ID, + }) + if err != nil { + return nil, err + } + menu.Resources = menuResResult.Data + + return menu, nil +} + +// Create a new menu in the data access object. +func (a *Menu) Create(ctx context.Context, formItem *schema.MenuForm) (*schema.Menu, error) { + if config.C.General.DenyOperateMenu { + return nil, errors.BadRequest("", "Menu creation is not allowed") + } + + menu := &schema.Menu{ + ID: util.NewXID(), + CreatedAt: time.Now(), + } + + if parentID := formItem.ParentID; parentID != "" { + parent, err := a.MenuDAL.Get(ctx, parentID) + if err != nil { + return nil, err + } else if parent == nil { + return nil, errors.NotFound("", "Parent not found") + } + menu.ParentPath = parent.ParentPath + parent.ID + util.TreePathDelimiter + } + + if exists, err := a.MenuDAL.ExistsCodeByParentID(ctx, formItem.Code, formItem.ParentID); err != nil { + return nil, err + } else if exists { + return nil, errors.BadRequest("", "Menu code already exists at the same level") + } + + if err := formItem.FillTo(menu); err != nil { + return nil, err + } + + err := a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.MenuDAL.Create(ctx, menu); err != nil { + return err + } + + for _, res := range formItem.Resources { + res.ID = util.NewXID() + res.MenuID = menu.ID + res.CreatedAt = time.Now() + if err := a.MenuResourceDAL.Create(ctx, res); err != nil { + return err + } + } + + return nil + }) + if err != nil { + return nil, err + } + return menu, nil +} + +// Update the specified menu in the data access object. +func (a *Menu) Update(ctx context.Context, id string, formItem *schema.MenuForm) error { + if config.C.General.DenyOperateMenu { + return errors.BadRequest("", "Menu update is not allowed") + } + + menu, err := a.MenuDAL.Get(ctx, id) + if err != nil { + return err + } else if menu == nil { + return errors.NotFound("", "Menu not found") + } + + oldParentPath := menu.ParentPath + oldStatus := menu.Status + var childData schema.Menus + if menu.ParentID != formItem.ParentID { + if parentID := formItem.ParentID; parentID != "" { + parent, err := a.MenuDAL.Get(ctx, parentID) + if err != nil { + return err + } else if parent == nil { + return errors.NotFound("", "Parent not found") + } + menu.ParentPath = parent.ParentPath + parent.ID + util.TreePathDelimiter + } else { + menu.ParentPath = "" + } + + childResult, err := a.MenuDAL.Query(ctx, schema.MenuQueryParam{ + ParentPathPrefix: oldParentPath + menu.ID + util.TreePathDelimiter, + }, schema.MenuQueryOptions{ + QueryOptions: util.QueryOptions{ + SelectFields: []string{"id", "parent_path"}, + }, + }) + if err != nil { + return err + } + childData = childResult.Data + } + + if menu.Code != formItem.Code { + if exists, err := a.MenuDAL.ExistsCodeByParentID(ctx, formItem.Code, formItem.ParentID); err != nil { + return err + } else if exists { + return errors.BadRequest("", "Menu code already exists at the same level") + } + } + + if err := formItem.FillTo(menu); err != nil { + return err + } + + return a.Trans.Exec(ctx, func(ctx context.Context) error { + if oldStatus != formItem.Status { + oldPath := oldParentPath + menu.ID + util.TreePathDelimiter + if err := a.MenuDAL.UpdateStatusByParentPath(ctx, oldPath, formItem.Status); err != nil { + return err + } + } + + for _, child := range childData { + oldPath := oldParentPath + menu.ID + util.TreePathDelimiter + newPath := menu.ParentPath + menu.ID + util.TreePathDelimiter + err := a.MenuDAL.UpdateParentPath(ctx, child.ID, strings.Replace(child.ParentPath, oldPath, newPath, 1)) + if err != nil { + return err + } + } + + if err := a.MenuDAL.Update(ctx, menu); err != nil { + return err + } + + if err := a.MenuResourceDAL.DeleteByMenuID(ctx, id); err != nil { + return err + } + for _, res := range formItem.Resources { + if res.ID == "" { + res.ID = util.NewXID() + } + res.MenuID = id + if res.CreatedAt.IsZero() { + res.CreatedAt = time.Now() + } + res.UpdatedAt = time.Now() + if err := a.MenuResourceDAL.Create(ctx, res); err != nil { + return err + } + } + + return a.syncToCasbin(ctx) + }) +} + +// Delete the specified menu from the data access object. +func (a *Menu) Delete(ctx context.Context, id string) error { + if config.C.General.DenyOperateMenu { + return errors.BadRequest("", "Menu deletion is not allowed") + } + + menu, err := a.MenuDAL.Get(ctx, id) + if err != nil { + return err + } else if menu == nil { + return errors.NotFound("", "Menu not found") + } + + childResult, err := a.MenuDAL.Query(ctx, schema.MenuQueryParam{ + ParentPathPrefix: menu.ParentPath + menu.ID + util.TreePathDelimiter, + }, schema.MenuQueryOptions{ + QueryOptions: util.QueryOptions{ + SelectFields: []string{"id"}, + }, + }) + if err != nil { + return err + } + + return a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.delete(ctx, id); err != nil { + return err + } + + for _, child := range childResult.Data { + if err := a.delete(ctx, child.ID); err != nil { + return err + } + } + + return a.syncToCasbin(ctx) + }) +} + +func (a *Menu) delete(ctx context.Context, id string) error { + if err := a.MenuDAL.Delete(ctx, id); err != nil { + return err + } + if err := a.MenuResourceDAL.DeleteByMenuID(ctx, id); err != nil { + return err + } + if err := a.RoleMenuDAL.DeleteByMenuID(ctx, id); err != nil { + return err + } + return nil +} + +func (a *Menu) syncToCasbin(ctx context.Context) error { + return a.Cache.Set(ctx, config.CacheNSForRole, config.CacheKeyForSyncToCasbin, fmt.Sprintf("%d", time.Now().Unix())) +} diff --git a/internal/mods/rbac/biz/role.biz.go b/internal/mods/rbac/biz/role.biz.go new file mode 100644 index 0000000..ad661dd --- /dev/null +++ b/internal/mods/rbac/biz/role.biz.go @@ -0,0 +1,179 @@ +package biz + +import ( + "context" + "fmt" + "time" + + "github.guxuan/haibei/internal/config" + "github.guxuan/haibei/internal/mods/rbac/dal" + "github.guxuan/haibei/internal/mods/rbac/schema" + "github.guxuan/haibei/pkg/cachex" + "github.guxuan/haibei/pkg/errors" + "github.guxuan/haibei/pkg/util" +) + +// Role management for RBAC +type Role struct { + Cache cachex.Cacher + Trans *util.Trans + RoleDAL *dal.Role + RoleMenuDAL *dal.RoleMenu + UserRoleDAL *dal.UserRole +} + +// Query roles from the data access object based on the provided parameters and options. +func (a *Role) Query(ctx context.Context, params schema.RoleQueryParam) (*schema.RoleQueryResult, error) { + params.Pagination = true + + var selectFields []string + if params.ResultType == schema.RoleResultTypeSelect { + params.Pagination = false + selectFields = []string{"id", "name"} + } + + result, err := a.RoleDAL.Query(ctx, params, schema.RoleQueryOptions{ + QueryOptions: util.QueryOptions{ + OrderFields: []util.OrderByParam{ + {Field: "sequence", Direction: util.DESC}, + {Field: "created_at", Direction: util.DESC}, + }, + SelectFields: selectFields, + }, + }) + if err != nil { + return nil, err + } + return result, nil +} + +// Get the specified role from the data access object. +func (a *Role) Get(ctx context.Context, id string) (*schema.Role, error) { + role, err := a.RoleDAL.Get(ctx, id) + if err != nil { + return nil, err + } else if role == nil { + return nil, errors.NotFound("", "Role not found") + } + + roleMenuResult, err := a.RoleMenuDAL.Query(ctx, schema.RoleMenuQueryParam{ + RoleID: id, + }) + if err != nil { + return nil, err + } + role.Menus = roleMenuResult.Data + + return role, nil +} + +// Create a new role in the data access object. +func (a *Role) Create(ctx context.Context, formItem *schema.RoleForm) (*schema.Role, error) { + if exists, err := a.RoleDAL.ExistsCode(ctx, formItem.Code); err != nil { + return nil, err + } else if exists { + return nil, errors.BadRequest("", "Role code already exists") + } + + role := &schema.Role{ + ID: util.NewXID(), + CreatedAt: time.Now(), + } + if err := formItem.FillTo(role); err != nil { + return nil, err + } + + err := a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.RoleDAL.Create(ctx, role); err != nil { + return err + } + + for _, roleMenu := range formItem.Menus { + roleMenu.ID = util.NewXID() + roleMenu.RoleID = role.ID + roleMenu.CreatedAt = time.Now() + if err := a.RoleMenuDAL.Create(ctx, roleMenu); err != nil { + return err + } + } + return a.syncToCasbin(ctx) + }) + if err != nil { + return nil, err + } + role.Menus = formItem.Menus + + return role, nil +} + +// Update the specified role in the data access object. +func (a *Role) Update(ctx context.Context, id string, formItem *schema.RoleForm) error { + role, err := a.RoleDAL.Get(ctx, id) + if err != nil { + return err + } else if role == nil { + return errors.NotFound("", "Role not found") + } else if role.Code != formItem.Code { + if exists, err := a.RoleDAL.ExistsCode(ctx, formItem.Code); err != nil { + return err + } else if exists { + return errors.BadRequest("", "Role code already exists") + } + } + + if err := formItem.FillTo(role); err != nil { + return err + } + role.UpdatedAt = time.Now() + + return a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.RoleDAL.Update(ctx, role); err != nil { + return err + } + if err := a.RoleMenuDAL.DeleteByRoleID(ctx, id); err != nil { + return err + } + for _, roleMenu := range formItem.Menus { + if roleMenu.ID == "" { + roleMenu.ID = util.NewXID() + } + roleMenu.RoleID = role.ID + if roleMenu.CreatedAt.IsZero() { + roleMenu.CreatedAt = time.Now() + } + roleMenu.UpdatedAt = time.Now() + if err := a.RoleMenuDAL.Create(ctx, roleMenu); err != nil { + return err + } + } + return a.syncToCasbin(ctx) + }) +} + +// Delete the specified role from the data access object. +func (a *Role) Delete(ctx context.Context, id string) error { + exists, err := a.RoleDAL.Exists(ctx, id) + if err != nil { + return err + } else if !exists { + return errors.NotFound("", "Role not found") + } + + return a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.RoleDAL.Delete(ctx, id); err != nil { + return err + } + if err := a.RoleMenuDAL.DeleteByRoleID(ctx, id); err != nil { + return err + } + if err := a.UserRoleDAL.DeleteByRoleID(ctx, id); err != nil { + return err + } + + return a.syncToCasbin(ctx) + }) +} + +func (a *Role) syncToCasbin(ctx context.Context) error { + return a.Cache.Set(ctx, config.CacheNSForRole, config.CacheKeyForSyncToCasbin, fmt.Sprintf("%d", time.Now().Unix())) +} diff --git a/internal/mods/rbac/biz/user.biz.go b/internal/mods/rbac/biz/user.biz.go new file mode 100644 index 0000000..2f9fe60 --- /dev/null +++ b/internal/mods/rbac/biz/user.biz.go @@ -0,0 +1,227 @@ +package biz + +import ( + "context" + "time" + + "github.guxuan/haibei/internal/config" + "github.guxuan/haibei/internal/mods/rbac/dal" + "github.guxuan/haibei/internal/mods/rbac/schema" + "github.guxuan/haibei/pkg/cachex" + "github.guxuan/haibei/pkg/crypto/hash" + "github.guxuan/haibei/pkg/errors" + "github.guxuan/haibei/pkg/util" +) + +// User management for RBAC +type User struct { + Cache cachex.Cacher + Trans *util.Trans + UserDAL *dal.User + UserRoleDAL *dal.UserRole +} + +// Query users from the data access object based on the provided parameters and options. +func (a *User) Query(ctx context.Context, params schema.UserQueryParam) (*schema.UserQueryResult, error) { + params.Pagination = true + + result, err := a.UserDAL.Query(ctx, params, schema.UserQueryOptions{ + QueryOptions: util.QueryOptions{ + OrderFields: []util.OrderByParam{ + {Field: "created_at", Direction: util.DESC}, + }, + OmitFields: []string{"password"}, + }, + }) + if err != nil { + return nil, err + } + + if userIDs := result.Data.ToIDs(); len(userIDs) > 0 { + userRoleResult, err := a.UserRoleDAL.Query(ctx, schema.UserRoleQueryParam{ + InUserIDs: userIDs, + }, schema.UserRoleQueryOptions{ + JoinRole: true, + }) + if err != nil { + return nil, err + } + userRolesMap := userRoleResult.Data.ToUserIDMap() + for _, user := range result.Data { + user.Roles = userRolesMap[user.ID] + } + } + + return result, nil +} + +// Get the specified user from the data access object. +func (a *User) Get(ctx context.Context, id string) (*schema.User, error) { + user, err := a.UserDAL.Get(ctx, id, schema.UserQueryOptions{ + QueryOptions: util.QueryOptions{ + OmitFields: []string{"password"}, + }, + }) + if err != nil { + return nil, err + } else if user == nil { + return nil, errors.NotFound("", "User not found") + } + + userRoleResult, err := a.UserRoleDAL.Query(ctx, schema.UserRoleQueryParam{ + UserID: id, + }) + if err != nil { + return nil, err + } + user.Roles = userRoleResult.Data + + return user, nil +} + +// Create a new user in the data access object. +func (a *User) Create(ctx context.Context, formItem *schema.UserForm) (*schema.User, error) { + existsUsername, err := a.UserDAL.ExistsUsername(ctx, formItem.Username) + if err != nil { + return nil, err + } else if existsUsername { + return nil, errors.BadRequest("", "Username already exists") + } + + user := &schema.User{ + ID: util.NewXID(), + CreatedAt: time.Now(), + } + + if formItem.Password == "" { + formItem.Password = config.C.General.DefaultLoginPwd + } + + if err := formItem.FillTo(user); err != nil { + return nil, err + } + + err = a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.UserDAL.Create(ctx, user); err != nil { + return err + } + + for _, userRole := range formItem.Roles { + userRole.ID = util.NewXID() + userRole.UserID = user.ID + userRole.CreatedAt = time.Now() + if err := a.UserRoleDAL.Create(ctx, userRole); err != nil { + return err + } + } + return nil + }) + if err != nil { + return nil, err + } + user.Roles = formItem.Roles + + return user, nil +} + +// Update the specified user in the data access object. +func (a *User) Update(ctx context.Context, id string, formItem *schema.UserForm) error { + user, err := a.UserDAL.Get(ctx, id) + if err != nil { + return err + } else if user == nil { + return errors.NotFound("", "User not found") + } else if user.Username != formItem.Username { + existsUsername, err := a.UserDAL.ExistsUsername(ctx, formItem.Username) + if err != nil { + return err + } else if existsUsername { + return errors.BadRequest("", "Username already exists") + } + } + + if err := formItem.FillTo(user); err != nil { + return err + } + user.UpdatedAt = time.Now() + + return a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.UserDAL.Update(ctx, user); err != nil { + return err + } + + if err := a.UserRoleDAL.DeleteByUserID(ctx, id); err != nil { + return err + } + for _, userRole := range formItem.Roles { + if userRole.ID == "" { + userRole.ID = util.NewXID() + } + userRole.UserID = user.ID + if userRole.CreatedAt.IsZero() { + userRole.CreatedAt = time.Now() + } + userRole.UpdatedAt = time.Now() + if err := a.UserRoleDAL.Create(ctx, userRole); err != nil { + return err + } + } + + return a.Cache.Delete(ctx, config.CacheNSForUser, id) + }) +} + +// Delete the specified user from the data access object. +func (a *User) Delete(ctx context.Context, id string) error { + exists, err := a.UserDAL.Exists(ctx, id) + if err != nil { + return err + } else if !exists { + return errors.NotFound("", "User not found") + } + + return a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.UserDAL.Delete(ctx, id); err != nil { + return err + } + if err := a.UserRoleDAL.DeleteByUserID(ctx, id); err != nil { + return err + } + return a.Cache.Delete(ctx, config.CacheNSForUser, id) + }) +} + +func (a *User) ResetPassword(ctx context.Context, id string) error { + exists, err := a.UserDAL.Exists(ctx, id) + if err != nil { + return err + } else if !exists { + return errors.NotFound("", "User not found") + } + + hashPass, err := hash.GeneratePassword(config.C.General.DefaultLoginPwd) + if err != nil { + return errors.BadRequest("", "Failed to generate hash password: %s", err.Error()) + } + + return a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.UserDAL.UpdatePasswordByID(ctx, id, hashPass); err != nil { + return err + } + return nil + }) +} + +func (a *User) GetRoleIDs(ctx context.Context, id string) ([]string, error) { + userRoleResult, err := a.UserRoleDAL.Query(ctx, schema.UserRoleQueryParam{ + UserID: id, + }, schema.UserRoleQueryOptions{ + QueryOptions: util.QueryOptions{ + SelectFields: []string{"role_id"}, + }, + }) + if err != nil { + return nil, err + } + return userRoleResult.Data.ToRoleIDs(), nil +} diff --git a/internal/mods/rbac/casbin.go b/internal/mods/rbac/casbin.go new file mode 100644 index 0000000..f4a2631 --- /dev/null +++ b/internal/mods/rbac/casbin.go @@ -0,0 +1,222 @@ +package rbac + +import ( + "bytes" + "context" + "fmt" + "github.guxuan/haibei/pkg/util" + "os" + "path/filepath" + "strconv" + "strings" + "sync" + "sync/atomic" + "time" + + "github.com/casbin/casbin/v2" + "github.guxuan/haibei/internal/config" + "github.guxuan/haibei/internal/mods/rbac/dal" + "github.guxuan/haibei/internal/mods/rbac/schema" + "github.guxuan/haibei/pkg/cachex" + "github.guxuan/haibei/pkg/logging" + "go.uber.org/zap" +) + +// Load rbac permissions to casbin +type Casbinx struct { + enforcer *atomic.Value `wire:"-"` + ticker *time.Ticker `wire:"-"` + Cache cachex.Cacher + MenuDAL *dal.Menu + MenuResourceDAL *dal.MenuResource + RoleDAL *dal.Role +} + +func (a *Casbinx) GetEnforcer() *casbin.Enforcer { + if v := a.enforcer.Load(); v != nil { + return v.(*casbin.Enforcer) + } + return nil +} + +type policyQueueItem struct { + RoleID string + Resources schema.MenuResources +} + +func (a *Casbinx) Load(ctx context.Context) error { + if config.C.Middleware.Casbin.Disable { + return nil + } + + a.enforcer = new(atomic.Value) + if err := a.load(ctx); err != nil { + return err + } + + go a.autoLoad(ctx) + return nil +} + +func (a *Casbinx) load(ctx context.Context) error { + start := time.Now() + roleResult, err := a.RoleDAL.Query(ctx, schema.RoleQueryParam{ + Status: schema.RoleStatusEnabled, + }, schema.RoleQueryOptions{ + QueryOptions: util.QueryOptions{SelectFields: []string{"id"}}, + }) + if err != nil { + return err + } else if len(roleResult.Data) == 0 { + return nil + } + + var resCount int32 + queue := make(chan *policyQueueItem, len(roleResult.Data)) + threadNum := config.C.Middleware.Casbin.LoadThread + lock := new(sync.Mutex) + buf := new(bytes.Buffer) + + wg := new(sync.WaitGroup) + wg.Add(threadNum) + for i := 0; i < threadNum; i++ { + go func() { + defer wg.Done() + ibuf := new(bytes.Buffer) + for item := range queue { + for _, res := range item.Resources { + _, _ = ibuf.WriteString(fmt.Sprintf("p, %s, %s, %s \n", item.RoleID, res.Path, res.Method)) + } + } + lock.Lock() + _, _ = buf.Write(ibuf.Bytes()) + lock.Unlock() + }() + } + + for _, item := range roleResult.Data { + resources, err := a.queryRoleResources(ctx, item.ID) + if err != nil { + logging.Context(ctx).Error("Failed to query role resources", zap.Error(err)) + continue + } + atomic.AddInt32(&resCount, int32(len(resources))) + queue <- &policyQueueItem{ + RoleID: item.ID, + Resources: resources, + } + } + close(queue) + wg.Wait() + + if buf.Len() > 0 { + policyFile := filepath.Join(config.C.General.WorkDir, config.C.Middleware.Casbin.GenPolicyFile) + _ = os.Rename(policyFile, policyFile+".bak") + _ = os.MkdirAll(filepath.Dir(policyFile), 0755) + if err := os.WriteFile(policyFile, buf.Bytes(), 0666); err != nil { + logging.Context(ctx).Error("Failed to write policy file", zap.Error(err)) + return err + } + // set readonly + _ = os.Chmod(policyFile, 0444) + + modelFile := filepath.Join(config.C.General.WorkDir, config.C.Middleware.Casbin.ModelFile) + e, err := casbin.NewEnforcer(modelFile, policyFile) + if err != nil { + logging.Context(ctx).Error("Failed to create casbin enforcer", zap.Error(err)) + return err + } + e.EnableLog(config.C.IsDebug()) + a.enforcer.Store(e) + } + + logging.Context(ctx).Info("Casbin load policy", + zap.Duration("cost", time.Since(start)), + zap.Int("roles", len(roleResult.Data)), + zap.Int32("resources", resCount), + zap.Int("bytes", buf.Len()), + ) + return nil +} + +func (a *Casbinx) queryRoleResources(ctx context.Context, roleID string) (schema.MenuResources, error) { + menuResult, err := a.MenuDAL.Query(ctx, schema.MenuQueryParam{ + RoleID: roleID, + Status: schema.MenuStatusEnabled, + }, schema.MenuQueryOptions{ + QueryOptions: util.QueryOptions{ + SelectFields: []string{"id", "parent_id", "parent_path"}, + }, + }) + if err != nil { + return nil, err + } else if len(menuResult.Data) == 0 { + return nil, nil + } + + menuIDs := make([]string, 0, len(menuResult.Data)) + menuIDMapper := make(map[string]struct{}) + for _, item := range menuResult.Data { + if _, ok := menuIDMapper[item.ID]; ok { + continue + } + menuIDs = append(menuIDs, item.ID) + menuIDMapper[item.ID] = struct{}{} + if pp := item.ParentPath; pp != "" { + for _, pid := range strings.Split(pp, util.TreePathDelimiter) { + if pid == "" { + continue + } + if _, ok := menuIDMapper[pid]; ok { + continue + } + menuIDs = append(menuIDs, pid) + menuIDMapper[pid] = struct{}{} + } + } + } + + menuResourceResult, err := a.MenuResourceDAL.Query(ctx, schema.MenuResourceQueryParam{ + MenuIDs: menuIDs, + }) + if err != nil { + return nil, err + } + + return menuResourceResult.Data, nil +} + +func (a *Casbinx) autoLoad(ctx context.Context) { + var lastUpdated int64 + a.ticker = time.NewTicker(time.Duration(config.C.Middleware.Casbin.AutoLoadInterval) * time.Second) + for range a.ticker.C { + val, ok, err := a.Cache.Get(ctx, config.CacheNSForRole, config.CacheKeyForSyncToCasbin) + if err != nil { + logging.Context(ctx).Error("Failed to get cache", zap.Error(err), zap.String("key", config.CacheKeyForSyncToCasbin)) + continue + } else if !ok { + continue + } + + updated, err := strconv.ParseInt(val, 10, 64) + if err != nil { + logging.Context(ctx).Error("Failed to parse cache value", zap.Error(err), zap.String("val", val)) + continue + } + + if lastUpdated < updated { + if err := a.load(ctx); err != nil { + logging.Context(ctx).Error("Failed to load casbin policy", zap.Error(err)) + } else { + lastUpdated = updated + } + } + } +} + +func (a *Casbinx) Release(ctx context.Context) error { + if a.ticker != nil { + a.ticker.Stop() + } + return nil +} diff --git a/internal/mods/rbac/dal/logger.dal.go b/internal/mods/rbac/dal/logger.dal.go new file mode 100644 index 0000000..2db2a01 --- /dev/null +++ b/internal/mods/rbac/dal/logger.dal.go @@ -0,0 +1,64 @@ +package dal + +import ( + "context" + "fmt" + + "github.guxuan/haibei/internal/mods/rbac/schema" + "github.guxuan/haibei/pkg/errors" + "github.guxuan/haibei/pkg/util" + "gorm.io/gorm" +) + +// Get logger storage instance +func GetLoggerDB(ctx context.Context, defDB *gorm.DB) *gorm.DB { + return util.GetDB(ctx, defDB).Model(new(schema.Logger)) +} + +// Logger management +type Logger struct { + DB *gorm.DB +} + +// Query loggers from the database based on the provided parameters and options. +func (a *Logger) Query(ctx context.Context, params schema.LoggerQueryParam, opts ...schema.LoggerQueryOptions) (*schema.LoggerQueryResult, error) { + var opt schema.LoggerQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + db := a.DB.Table(fmt.Sprintf("%s AS a", new(schema.Logger).TableName())) + db = db.Joins(fmt.Sprintf("left join %s b on a.user_id=b.id", new(schema.User).TableName())) + db = db.Select("a.*,b.name as user_name,b.username as login_name") + + if v := params.Level; v != "" { + db = db.Where("a.level = ?", v) + } + if v := params.LikeMessage; len(v) > 0 { + db = db.Where("a.message LIKE ?", "%"+v+"%") + } + if v := params.TraceID; v != "" { + db = db.Where("a.trace_id = ?", v) + } + if v := params.LikeUserName; v != "" { + db = db.Where("b.username LIKE ?", "%"+v+"%") + } + if v := params.Tag; v != "" { + db = db.Where("a.tag = ?", v) + } + if start, end := params.StartTime, params.EndTime; start != "" && end != "" { + db = db.Where("a.created_at BETWEEN ? AND ?", start, end) + } + + var list schema.Loggers + pageResult, err := util.WrapPageQuery(ctx, db, params.PaginationParam, opt.QueryOptions, &list) + if err != nil { + return nil, errors.WithStack(err) + } + + queryResult := &schema.LoggerQueryResult{ + PageResult: pageResult, + Data: list, + } + return queryResult, nil +} diff --git a/internal/mods/rbac/dal/menu.dal.go b/internal/mods/rbac/dal/menu.dal.go new file mode 100644 index 0000000..82d0532 --- /dev/null +++ b/internal/mods/rbac/dal/menu.dal.go @@ -0,0 +1,165 @@ +package dal + +import ( + "context" + + "github.guxuan/haibei/internal/mods/rbac/schema" + "github.guxuan/haibei/pkg/errors" + "github.guxuan/haibei/pkg/util" + "gorm.io/gorm" +) + +// Get menu storage instance +func GetMenuDB(ctx context.Context, defDB *gorm.DB) *gorm.DB { + return util.GetDB(ctx, defDB).Model(new(schema.Menu)) +} + +// Menu management for RBAC +type Menu struct { + DB *gorm.DB +} + +// Query menus from the database based on the provided parameters and options. +func (a *Menu) Query(ctx context.Context, params schema.MenuQueryParam, opts ...schema.MenuQueryOptions) (*schema.MenuQueryResult, error) { + var opt schema.MenuQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + db := GetMenuDB(ctx, a.DB) + + if v := params.InIDs; len(v) > 0 { + db = db.Where("id IN ?", v) + } + if v := params.LikeName; len(v) > 0 { + db = db.Where("name LIKE ?", "%"+v+"%") + } + if v := params.Status; len(v) > 0 { + db = db.Where("status = ?", v) + } + if v := params.ParentID; len(v) > 0 { + db = db.Where("parent_id = ?", v) + } + if v := params.ParentPathPrefix; len(v) > 0 { + db = db.Where("parent_path LIKE ?", v+"%") + } + if v := params.UserID; len(v) > 0 { + userRoleQuery := GetUserRoleDB(ctx, a.DB).Where("user_id = ?", v).Select("role_id") + roleMenuQuery := GetRoleMenuDB(ctx, a.DB).Where("role_id IN (?)", userRoleQuery).Select("menu_id") + db = db.Where("id IN (?)", roleMenuQuery) + } + if v := params.RoleID; len(v) > 0 { + roleMenuQuery := GetRoleMenuDB(ctx, a.DB).Where("role_id = ?", v).Select("menu_id") + db = db.Where("id IN (?)", roleMenuQuery) + } + + var list schema.Menus + pageResult, err := util.WrapPageQuery(ctx, db, params.PaginationParam, opt.QueryOptions, &list) + if err != nil { + return nil, errors.WithStack(err) + } + + queryResult := &schema.MenuQueryResult{ + PageResult: pageResult, + Data: list, + } + return queryResult, nil +} + +// Get the specified menu from the database. +func (a *Menu) Get(ctx context.Context, id string, opts ...schema.MenuQueryOptions) (*schema.Menu, error) { + var opt schema.MenuQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + item := new(schema.Menu) + ok, err := util.FindOne(ctx, GetMenuDB(ctx, a.DB).Where("id=?", id), opt.QueryOptions, item) + if err != nil { + return nil, errors.WithStack(err) + } else if !ok { + return nil, nil + } + return item, nil +} + +func (a *Menu) GetByCodeAndParentID(ctx context.Context, code, parentID string, opts ...schema.MenuQueryOptions) (*schema.Menu, error) { + var opt schema.MenuQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + item := new(schema.Menu) + ok, err := util.FindOne(ctx, GetMenuDB(ctx, a.DB).Where("code=? AND parent_id=?", code, parentID), opt.QueryOptions, item) + if err != nil { + return nil, errors.WithStack(err) + } else if !ok { + return nil, nil + } + return item, nil +} + +// GetByNameAndParentID get the specified menu from the database. +func (a *Menu) GetByNameAndParentID(ctx context.Context, name, parentID string, opts ...schema.MenuQueryOptions) (*schema.Menu, error) { + var opt schema.MenuQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + item := new(schema.Menu) + ok, err := util.FindOne(ctx, GetMenuDB(ctx, a.DB).Where("name=? AND parent_id=?", name, parentID), opt.QueryOptions, item) + if err != nil { + return nil, errors.WithStack(err) + } else if !ok { + return nil, nil + } + return item, nil +} + +// Checks if the specified menu exists in the database. +func (a *Menu) Exists(ctx context.Context, id string) (bool, error) { + ok, err := util.Exists(ctx, GetMenuDB(ctx, a.DB).Where("id=?", id)) + return ok, errors.WithStack(err) +} + +// Checks if a menu with the specified `code` exists under the specified `parentID` in the database. +func (a *Menu) ExistsCodeByParentID(ctx context.Context, code, parentID string) (bool, error) { + ok, err := util.Exists(ctx, GetMenuDB(ctx, a.DB).Where("code=? AND parent_id=?", code, parentID)) + return ok, errors.WithStack(err) +} + +// Checks if a menu with the specified `name` exists under the specified `parentID` in the database. +func (a *Menu) ExistsNameByParentID(ctx context.Context, name, parentID string) (bool, error) { + ok, err := util.Exists(ctx, GetMenuDB(ctx, a.DB).Where("name=? AND parent_id=?", name, parentID)) + return ok, errors.WithStack(err) +} + +// Create a new menu. +func (a *Menu) Create(ctx context.Context, item *schema.Menu) error { + result := GetMenuDB(ctx, a.DB).Create(item) + return errors.WithStack(result.Error) +} + +// Update the specified menu in the database. +func (a *Menu) Update(ctx context.Context, item *schema.Menu) error { + result := GetMenuDB(ctx, a.DB).Where("id=?", item.ID).Select("*").Omit("created_at").Updates(item) + return errors.WithStack(result.Error) +} + +// Delete the specified menu from the database. +func (a *Menu) Delete(ctx context.Context, id string) error { + result := GetMenuDB(ctx, a.DB).Where("id=?", id).Delete(new(schema.Menu)) + return errors.WithStack(result.Error) +} + +// Updates the parent path of the specified menu. +func (a *Menu) UpdateParentPath(ctx context.Context, id, parentPath string) error { + result := GetMenuDB(ctx, a.DB).Where("id=?", id).Update("parent_path", parentPath) + return errors.WithStack(result.Error) +} + +// Updates the status of all menus whose parent path starts with the provided parent path. +func (a *Menu) UpdateStatusByParentPath(ctx context.Context, parentPath, status string) error { + result := GetMenuDB(ctx, a.DB).Where("parent_path like ?", parentPath+"%").Update("status", status) + return errors.WithStack(result.Error) +} diff --git a/internal/mods/rbac/dal/menu_resource.dal.go b/internal/mods/rbac/dal/menu_resource.dal.go new file mode 100644 index 0000000..7968e91 --- /dev/null +++ b/internal/mods/rbac/dal/menu_resource.dal.go @@ -0,0 +1,101 @@ +package dal + +import ( + "context" + + "github.guxuan/haibei/internal/mods/rbac/schema" + "github.guxuan/haibei/pkg/errors" + "github.guxuan/haibei/pkg/util" + "gorm.io/gorm" +) + +// Get menu resource storage instance +func GetMenuResourceDB(ctx context.Context, defDB *gorm.DB) *gorm.DB { + return util.GetDB(ctx, defDB).Model(new(schema.MenuResource)) +} + +// Menu resource management for RBAC +type MenuResource struct { + DB *gorm.DB +} + +// Query menu resources from the database based on the provided parameters and options. +func (a *MenuResource) Query(ctx context.Context, params schema.MenuResourceQueryParam, opts ...schema.MenuResourceQueryOptions) (*schema.MenuResourceQueryResult, error) { + var opt schema.MenuResourceQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + db := GetMenuResourceDB(ctx, a.DB) + if v := params.MenuID; len(v) > 0 { + db = db.Where("menu_id = ?", v) + } + if v := params.MenuIDs; len(v) > 0 { + db = db.Where("menu_id IN ?", v) + } + + var list schema.MenuResources + pageResult, err := util.WrapPageQuery(ctx, db, params.PaginationParam, opt.QueryOptions, &list) + if err != nil { + return nil, errors.WithStack(err) + } + + queryResult := &schema.MenuResourceQueryResult{ + PageResult: pageResult, + Data: list, + } + return queryResult, nil +} + +// Get the specified menu resource from the database. +func (a *MenuResource) Get(ctx context.Context, id string, opts ...schema.MenuResourceQueryOptions) (*schema.MenuResource, error) { + var opt schema.MenuResourceQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + item := new(schema.MenuResource) + ok, err := util.FindOne(ctx, GetMenuResourceDB(ctx, a.DB).Where("id=?", id), opt.QueryOptions, item) + if err != nil { + return nil, errors.WithStack(err) + } else if !ok { + return nil, nil + } + return item, nil +} + +// Exist checks if the specified menu resource exists in the database. +func (a *MenuResource) Exists(ctx context.Context, id string) (bool, error) { + ok, err := util.Exists(ctx, GetMenuResourceDB(ctx, a.DB).Where("id=?", id)) + return ok, errors.WithStack(err) +} + +// ExistsMethodPathByMenuID checks if the specified menu resource exists in the database. +func (a *MenuResource) ExistsMethodPathByMenuID(ctx context.Context, method, path, menuID string) (bool, error) { + ok, err := util.Exists(ctx, GetMenuResourceDB(ctx, a.DB).Where("method=? AND path=? AND menu_id=?", method, path, menuID)) + return ok, errors.WithStack(err) +} + +// Create a new menu resource. +func (a *MenuResource) Create(ctx context.Context, item *schema.MenuResource) error { + result := GetMenuResourceDB(ctx, a.DB).Create(item) + return errors.WithStack(result.Error) +} + +// Update the specified menu resource in the database. +func (a *MenuResource) Update(ctx context.Context, item *schema.MenuResource) error { + result := GetMenuResourceDB(ctx, a.DB).Where("id=?", item.ID).Select("*").Omit("created_at").Updates(item) + return errors.WithStack(result.Error) +} + +// Delete the specified menu resource from the database. +func (a *MenuResource) Delete(ctx context.Context, id string) error { + result := GetMenuResourceDB(ctx, a.DB).Where("id=?", id).Delete(new(schema.MenuResource)) + return errors.WithStack(result.Error) +} + +// Deletes the menu resource by menu id. +func (a *MenuResource) DeleteByMenuID(ctx context.Context, menuID string) error { + result := GetMenuResourceDB(ctx, a.DB).Where("menu_id=?", menuID).Delete(new(schema.MenuResource)) + return errors.WithStack(result.Error) +} diff --git a/internal/mods/rbac/dal/role.dal.go b/internal/mods/rbac/dal/role.dal.go new file mode 100644 index 0000000..02d3dc3 --- /dev/null +++ b/internal/mods/rbac/dal/role.dal.go @@ -0,0 +1,100 @@ +package dal + +import ( + "context" + + "github.guxuan/haibei/internal/mods/rbac/schema" + "github.guxuan/haibei/pkg/errors" + "github.guxuan/haibei/pkg/util" + "gorm.io/gorm" +) + +// Get role storage instance +func GetRoleDB(ctx context.Context, defDB *gorm.DB) *gorm.DB { + return util.GetDB(ctx, defDB).Model(new(schema.Role)) +} + +// Role management for RBAC +type Role struct { + DB *gorm.DB +} + +// Query roles from the database based on the provided parameters and options. +func (a *Role) Query(ctx context.Context, params schema.RoleQueryParam, opts ...schema.RoleQueryOptions) (*schema.RoleQueryResult, error) { + var opt schema.RoleQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + db := GetRoleDB(ctx, a.DB) + if v := params.InIDs; len(v) > 0 { + db = db.Where("id IN (?)", v) + } + if v := params.LikeName; len(v) > 0 { + db = db.Where("name LIKE ?", "%"+v+"%") + } + if v := params.Status; len(v) > 0 { + db = db.Where("status = ?", v) + } + if v := params.GtUpdatedAt; v != nil { + db = db.Where("updated_at > ?", v) + } + + var list schema.Roles + pageResult, err := util.WrapPageQuery(ctx, db, params.PaginationParam, opt.QueryOptions, &list) + if err != nil { + return nil, errors.WithStack(err) + } + + queryResult := &schema.RoleQueryResult{ + PageResult: pageResult, + Data: list, + } + return queryResult, nil +} + +// Get the specified role from the database. +func (a *Role) Get(ctx context.Context, id string, opts ...schema.RoleQueryOptions) (*schema.Role, error) { + var opt schema.RoleQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + item := new(schema.Role) + ok, err := util.FindOne(ctx, GetRoleDB(ctx, a.DB).Where("id=?", id), opt.QueryOptions, item) + if err != nil { + return nil, errors.WithStack(err) + } else if !ok { + return nil, nil + } + return item, nil +} + +// Exist checks if the specified role exists in the database. +func (a *Role) Exists(ctx context.Context, id string) (bool, error) { + ok, err := util.Exists(ctx, GetRoleDB(ctx, a.DB).Where("id=?", id)) + return ok, errors.WithStack(err) +} + +func (a *Role) ExistsCode(ctx context.Context, code string) (bool, error) { + ok, err := util.Exists(ctx, GetRoleDB(ctx, a.DB).Where("code=?", code)) + return ok, errors.WithStack(err) +} + +// Create a new role. +func (a *Role) Create(ctx context.Context, item *schema.Role) error { + result := GetRoleDB(ctx, a.DB).Create(item) + return errors.WithStack(result.Error) +} + +// Update the specified role in the database. +func (a *Role) Update(ctx context.Context, item *schema.Role) error { + result := GetRoleDB(ctx, a.DB).Where("id=?", item.ID).Select("*").Omit("created_at").Updates(item) + return errors.WithStack(result.Error) +} + +// Delete the specified role from the database. +func (a *Role) Delete(ctx context.Context, id string) error { + result := GetRoleDB(ctx, a.DB).Where("id=?", id).Delete(new(schema.Role)) + return errors.WithStack(result.Error) +} diff --git a/internal/mods/rbac/dal/role_menu.dal.go b/internal/mods/rbac/dal/role_menu.dal.go new file mode 100644 index 0000000..78da5f7 --- /dev/null +++ b/internal/mods/rbac/dal/role_menu.dal.go @@ -0,0 +1,98 @@ +package dal + +import ( + "context" + + "github.guxuan/haibei/internal/mods/rbac/schema" + "github.guxuan/haibei/pkg/errors" + "github.guxuan/haibei/pkg/util" + "gorm.io/gorm" +) + +// Get role menu storage instance +func GetRoleMenuDB(ctx context.Context, defDB *gorm.DB) *gorm.DB { + return util.GetDB(ctx, defDB).Model(new(schema.RoleMenu)) +} + +// Role permissions for RBAC +type RoleMenu struct { + DB *gorm.DB +} + +// Query role menus from the database based on the provided parameters and options. +func (a *RoleMenu) Query(ctx context.Context, params schema.RoleMenuQueryParam, opts ...schema.RoleMenuQueryOptions) (*schema.RoleMenuQueryResult, error) { + var opt schema.RoleMenuQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + db := GetRoleMenuDB(ctx, a.DB) + if v := params.RoleID; len(v) > 0 { + db = db.Where("role_id = ?", v) + } + + var list schema.RoleMenus + pageResult, err := util.WrapPageQuery(ctx, db, params.PaginationParam, opt.QueryOptions, &list) + if err != nil { + return nil, errors.WithStack(err) + } + + queryResult := &schema.RoleMenuQueryResult{ + PageResult: pageResult, + Data: list, + } + return queryResult, nil +} + +// Get the specified role menu from the database. +func (a *RoleMenu) Get(ctx context.Context, id string, opts ...schema.RoleMenuQueryOptions) (*schema.RoleMenu, error) { + var opt schema.RoleMenuQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + item := new(schema.RoleMenu) + ok, err := util.FindOne(ctx, GetRoleMenuDB(ctx, a.DB).Where("id=?", id), opt.QueryOptions, item) + if err != nil { + return nil, errors.WithStack(err) + } else if !ok { + return nil, nil + } + return item, nil +} + +// Exist checks if the specified role menu exists in the database. +func (a *RoleMenu) Exists(ctx context.Context, id string) (bool, error) { + ok, err := util.Exists(ctx, GetRoleMenuDB(ctx, a.DB).Where("id=?", id)) + return ok, errors.WithStack(err) +} + +// Create a new role menu. +func (a *RoleMenu) Create(ctx context.Context, item *schema.RoleMenu) error { + result := GetRoleMenuDB(ctx, a.DB).Create(item) + return errors.WithStack(result.Error) +} + +// Update the specified role menu in the database. +func (a *RoleMenu) Update(ctx context.Context, item *schema.RoleMenu) error { + result := GetRoleMenuDB(ctx, a.DB).Where("id=?", item.ID).Select("*").Omit("created_at").Updates(item) + return errors.WithStack(result.Error) +} + +// Delete the specified role menu from the database. +func (a *RoleMenu) Delete(ctx context.Context, id string) error { + result := GetRoleMenuDB(ctx, a.DB).Where("id=?", id).Delete(new(schema.RoleMenu)) + return errors.WithStack(result.Error) +} + +// Deletes role menus by role id. +func (a *RoleMenu) DeleteByRoleID(ctx context.Context, roleID string) error { + result := GetRoleMenuDB(ctx, a.DB).Where("role_id=?", roleID).Delete(new(schema.RoleMenu)) + return errors.WithStack(result.Error) +} + +// Deletes role menus by menu id. +func (a *RoleMenu) DeleteByMenuID(ctx context.Context, menuID string) error { + result := GetRoleMenuDB(ctx, a.DB).Where("menu_id=?", menuID).Delete(new(schema.RoleMenu)) + return errors.WithStack(result.Error) +} diff --git a/internal/mods/rbac/dal/user.dal.go b/internal/mods/rbac/dal/user.dal.go new file mode 100644 index 0000000..6752009 --- /dev/null +++ b/internal/mods/rbac/dal/user.dal.go @@ -0,0 +1,124 @@ +package dal + +import ( + "context" + + "github.guxuan/haibei/internal/mods/rbac/schema" + "github.guxuan/haibei/pkg/errors" + "github.guxuan/haibei/pkg/util" + "gorm.io/gorm" +) + +// Get user storage instance +func GetUserDB(ctx context.Context, defDB *gorm.DB) *gorm.DB { + return util.GetDB(ctx, defDB).Model(new(schema.User)) +} + +// User management for RBAC +type User struct { + DB *gorm.DB +} + +// Query users from the database based on the provided parameters and options. +func (a *User) Query(ctx context.Context, params schema.UserQueryParam, opts ...schema.UserQueryOptions) (*schema.UserQueryResult, error) { + var opt schema.UserQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + db := GetUserDB(ctx, a.DB) + if v := params.LikeUsername; len(v) > 0 { + db = db.Where("username LIKE ?", "%"+v+"%") + } + if v := params.LikeName; len(v) > 0 { + db = db.Where("name LIKE ?", "%"+v+"%") + } + if v := params.Status; len(v) > 0 { + db = db.Where("status = ?", v) + } + + var list schema.Users + pageResult, err := util.WrapPageQuery(ctx, db, params.PaginationParam, opt.QueryOptions, &list) + if err != nil { + return nil, errors.WithStack(err) + } + + queryResult := &schema.UserQueryResult{ + PageResult: pageResult, + Data: list, + } + return queryResult, nil +} + +// Get the specified user from the database. +func (a *User) Get(ctx context.Context, id string, opts ...schema.UserQueryOptions) (*schema.User, error) { + var opt schema.UserQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + item := new(schema.User) + ok, err := util.FindOne(ctx, GetUserDB(ctx, a.DB).Where("id=?", id), opt.QueryOptions, item) + if err != nil { + return nil, errors.WithStack(err) + } else if !ok { + return nil, nil + } + return item, nil +} + +func (a *User) GetByUsername(ctx context.Context, username string, opts ...schema.UserQueryOptions) (*schema.User, error) { + var opt schema.UserQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + item := new(schema.User) + ok, err := util.FindOne(ctx, GetUserDB(ctx, a.DB).Where("username=?", username), opt.QueryOptions, item) + if err != nil { + return nil, errors.WithStack(err) + } else if !ok { + return nil, nil + } + return item, nil +} + +// Exist checks if the specified user exists in the database. +func (a *User) Exists(ctx context.Context, id string) (bool, error) { + ok, err := util.Exists(ctx, GetUserDB(ctx, a.DB).Where("id=?", id)) + return ok, errors.WithStack(err) +} + +func (a *User) ExistsUsername(ctx context.Context, username string) (bool, error) { + ok, err := util.Exists(ctx, GetUserDB(ctx, a.DB).Where("username=?", username)) + return ok, errors.WithStack(err) +} + +// Create a new user. +func (a *User) Create(ctx context.Context, item *schema.User) error { + result := GetUserDB(ctx, a.DB).Create(item) + return errors.WithStack(result.Error) +} + +// Update the specified user in the database. +func (a *User) Update(ctx context.Context, item *schema.User, selectFields ...string) error { + db := GetUserDB(ctx, a.DB).Where("id=?", item.ID) + if len(selectFields) > 0 { + db = db.Select(selectFields) + } else { + db = db.Select("*").Omit("created_at") + } + result := db.Updates(item) + return errors.WithStack(result.Error) +} + +// Delete the specified user from the database. +func (a *User) Delete(ctx context.Context, id string) error { + result := GetUserDB(ctx, a.DB).Where("id=?", id).Delete(new(schema.User)) + return errors.WithStack(result.Error) +} + +func (a *User) UpdatePasswordByID(ctx context.Context, id string, password string) error { + result := GetUserDB(ctx, a.DB).Where("id=?", id).Select("password").Updates(schema.User{Password: password}) + return errors.WithStack(result.Error) +} diff --git a/internal/mods/rbac/dal/user_role.dal.go b/internal/mods/rbac/dal/user_role.dal.go new file mode 100644 index 0000000..703b1a0 --- /dev/null +++ b/internal/mods/rbac/dal/user_role.dal.go @@ -0,0 +1,108 @@ +package dal + +import ( + "context" + "fmt" + + "github.guxuan/haibei/internal/mods/rbac/schema" + "github.guxuan/haibei/pkg/errors" + "github.guxuan/haibei/pkg/util" + "gorm.io/gorm" +) + +// Get user role storage instance +func GetUserRoleDB(ctx context.Context, defDB *gorm.DB) *gorm.DB { + return util.GetDB(ctx, defDB).Model(new(schema.UserRole)) +} + +// User roles for RBAC +type UserRole struct { + DB *gorm.DB +} + +// Query user roles from the database based on the provided parameters and options. +func (a *UserRole) Query(ctx context.Context, params schema.UserRoleQueryParam, opts ...schema.UserRoleQueryOptions) (*schema.UserRoleQueryResult, error) { + var opt schema.UserRoleQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + db := a.DB.Table(fmt.Sprintf("%s AS a", new(schema.UserRole).TableName())) + if opt.JoinRole { + db = db.Joins(fmt.Sprintf("left join %s b on a.role_id=b.id", new(schema.Role).TableName())) + db = db.Select("a.*,b.name as role_name") + } + + if v := params.InUserIDs; len(v) > 0 { + db = db.Where("a.user_id IN (?)", v) + } + if v := params.UserID; len(v) > 0 { + db = db.Where("a.user_id = ?", v) + } + if v := params.RoleID; len(v) > 0 { + db = db.Where("a.role_id = ?", v) + } + + var list schema.UserRoles + pageResult, err := util.WrapPageQuery(ctx, db, params.PaginationParam, opt.QueryOptions, &list) + if err != nil { + return nil, errors.WithStack(err) + } + + queryResult := &schema.UserRoleQueryResult{ + PageResult: pageResult, + Data: list, + } + return queryResult, nil +} + +// Get the specified user role from the database. +func (a *UserRole) Get(ctx context.Context, id string, opts ...schema.UserRoleQueryOptions) (*schema.UserRole, error) { + var opt schema.UserRoleQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + item := new(schema.UserRole) + ok, err := util.FindOne(ctx, GetUserRoleDB(ctx, a.DB).Where("id=?", id), opt.QueryOptions, item) + if err != nil { + return nil, errors.WithStack(err) + } else if !ok { + return nil, nil + } + return item, nil +} + +// Exist checks if the specified user role exists in the database. +func (a *UserRole) Exists(ctx context.Context, id string) (bool, error) { + ok, err := util.Exists(ctx, GetUserRoleDB(ctx, a.DB).Where("id=?", id)) + return ok, errors.WithStack(err) +} + +// Create a new user role. +func (a *UserRole) Create(ctx context.Context, item *schema.UserRole) error { + result := GetUserRoleDB(ctx, a.DB).Create(item) + return errors.WithStack(result.Error) +} + +// Update the specified user role in the database. +func (a *UserRole) Update(ctx context.Context, item *schema.UserRole) error { + result := GetUserRoleDB(ctx, a.DB).Where("id=?", item.ID).Select("*").Omit("created_at").Updates(item) + return errors.WithStack(result.Error) +} + +// Delete the specified user role from the database. +func (a *UserRole) Delete(ctx context.Context, id string) error { + result := GetUserRoleDB(ctx, a.DB).Where("id=?", id).Delete(new(schema.UserRole)) + return errors.WithStack(result.Error) +} + +func (a *UserRole) DeleteByUserID(ctx context.Context, userID string) error { + result := GetUserRoleDB(ctx, a.DB).Where("user_id=?", userID).Delete(new(schema.UserRole)) + return errors.WithStack(result.Error) +} + +func (a *UserRole) DeleteByRoleID(ctx context.Context, roleID string) error { + result := GetUserRoleDB(ctx, a.DB).Where("role_id=?", roleID).Delete(new(schema.UserRole)) + return errors.WithStack(result.Error) +} diff --git a/internal/mods/rbac/main.go b/internal/mods/rbac/main.go new file mode 100644 index 0000000..189f4b0 --- /dev/null +++ b/internal/mods/rbac/main.go @@ -0,0 +1,118 @@ +package rbac + +import ( + "context" + "path/filepath" + + "github.com/gin-gonic/gin" + "github.guxuan/haibei/internal/config" + "github.guxuan/haibei/internal/mods/rbac/api" + "github.guxuan/haibei/internal/mods/rbac/schema" + "github.guxuan/haibei/pkg/logging" + "go.uber.org/zap" + "gorm.io/gorm" +) + +type RBAC struct { + DB *gorm.DB + MenuAPI *api.Menu + RoleAPI *api.Role + UserAPI *api.User + LoginAPI *api.Login + LoggerAPI *api.Logger + Casbinx *Casbinx +} + +func (a *RBAC) AutoMigrate(ctx context.Context) error { + return a.DB.AutoMigrate( + new(schema.Menu), + new(schema.MenuResource), + new(schema.Role), + new(schema.RoleMenu), + new(schema.User), + new(schema.UserRole), + ) +} + +func (a *RBAC) Init(ctx context.Context) error { + if config.C.Storage.DB.AutoMigrate { + if err := a.AutoMigrate(ctx); err != nil { + return err + } + } + + if err := a.Casbinx.Load(ctx); err != nil { + return err + } + + if name := config.C.General.MenuFile; name != "" { + fullPath := filepath.Join(config.C.General.WorkDir, name) + if err := a.MenuAPI.MenuBIZ.InitFromFile(ctx, fullPath); err != nil { + logging.Context(ctx).Error("failed to init menu data", zap.Error(err), zap.String("file", fullPath)) + } + } + + return nil +} + +func (a *RBAC) RegisterV1Routers(ctx context.Context, v1 *gin.RouterGroup) error { + captcha := v1.Group("captcha") + { + captcha.GET("id", a.LoginAPI.GetCaptcha) + captcha.GET("image", a.LoginAPI.ResponseCaptcha) + } + + v1.POST("login", a.LoginAPI.Login) + + current := v1.Group("current") + { + current.POST("refresh-token", a.LoginAPI.RefreshToken) + current.GET("user", a.LoginAPI.GetUserInfo) + current.GET("menus", a.LoginAPI.QueryMenus) + current.PUT("password", a.LoginAPI.UpdatePassword) + current.PUT("user", a.LoginAPI.UpdateUser) + current.POST("logout", a.LoginAPI.Logout) + } + + menu := v1.Group("menus") + { + menu.GET("", a.MenuAPI.Query) + menu.GET(":id", a.MenuAPI.Get) + menu.POST("", a.MenuAPI.Create) + menu.PUT(":id", a.MenuAPI.Update) + menu.DELETE(":id", a.MenuAPI.Delete) + } + + role := v1.Group("roles") + { + role.GET("", a.RoleAPI.Query) + role.GET(":id", a.RoleAPI.Get) + role.POST("", a.RoleAPI.Create) + role.PUT(":id", a.RoleAPI.Update) + role.DELETE(":id", a.RoleAPI.Delete) + } + + user := v1.Group("users") + { + user.GET("", a.UserAPI.Query) + user.GET(":id", a.UserAPI.Get) + user.POST("", a.UserAPI.Create) + user.PUT(":id", a.UserAPI.Update) + user.DELETE(":id", a.UserAPI.Delete) + user.PATCH(":id/reset-pwd", a.UserAPI.ResetPassword) + } + + logger := v1.Group("loggers") + { + logger.GET("", a.LoggerAPI.Query) + } + + return nil +} + +func (a *RBAC) Release(ctx context.Context) error { + if err := a.Casbinx.Release(ctx); err != nil { + return err + } + return nil +} diff --git a/internal/mods/rbac/schema/logger.go b/internal/mods/rbac/schema/logger.go new file mode 100644 index 0000000..10029d7 --- /dev/null +++ b/internal/mods/rbac/schema/logger.go @@ -0,0 +1,53 @@ +package schema + +import ( + "time" + + "github.guxuan/haibei/internal/config" + "github.guxuan/haibei/pkg/util" +) + +// Logger management +type Logger struct { + ID string `gorm:"size:20;primaryKey;" json:"id"` // Unique ID + Level string `gorm:"size:20;index;" json:"level"` // Log level + TraceID string `gorm:"size:64;index;" json:"trace_id"` // Trace ID + UserID string `gorm:"size:20;index;" json:"user_id"` // User ID + Tag string `gorm:"size:32;index;" json:"tag"` // Log tag + Message string `gorm:"size:1024;" json:"message"` // Log message + Stack string `gorm:"type:text;" json:"stack"` // Error stack + Data string `gorm:"type:text;" json:"data"` // Log data + CreatedAt time.Time `gorm:"index;" json:"created_at"` // Create time + LoginName string `json:"login_name" gorm:"<-:false;-:migration;"` // From User.Username + UserName string `json:"user_name" gorm:"<-:false;-:migration;"` // From User.Name +} + +func (a *Logger) TableName() string { + return config.C.FormatTableName("logger") +} + +// Defining the query parameters for the `Logger` struct. +type LoggerQueryParam struct { + util.PaginationParam + Level string `form:"level"` // Log level + TraceID string `form:"traceID"` // Trace ID + LikeUserName string `form:"userName"` // User Name + Tag string `form:"tag"` // Log tag + LikeMessage string `form:"message"` // Log message + StartTime string `form:"startTime"` // Start time + EndTime string `form:"endTime"` // End time +} + +// Defining the query options for the `Logger` struct. +type LoggerQueryOptions struct { + util.QueryOptions +} + +// Defining the query result for the `Logger` struct. +type LoggerQueryResult struct { + Data Loggers + PageResult *util.PaginationResult +} + +// Defining the slice of `Logger` struct. +type Loggers []*Logger diff --git a/internal/mods/rbac/schema/login.go b/internal/mods/rbac/schema/login.go new file mode 100644 index 0000000..e23a355 --- /dev/null +++ b/internal/mods/rbac/schema/login.go @@ -0,0 +1,38 @@ +package schema + +import "strings" + +type Captcha struct { + CaptchaID string `json:"captcha_id"` // Captcha ID +} + +type LoginForm struct { + Username string `json:"username" binding:"required"` // Login name + Password string `json:"password" binding:"required"` // Login password (md5 hash) + CaptchaID string `json:"captcha_id" binding:"required"` // Captcha verify id + CaptchaCode string `json:"captcha_code" binding:"required"` // Captcha verify code +} + +func (a *LoginForm) Trim() *LoginForm { + a.Username = strings.TrimSpace(a.Username) + a.CaptchaCode = strings.TrimSpace(a.CaptchaCode) + return a +} + +type UpdateLoginPassword struct { + OldPassword string `json:"old_password" binding:"required"` // Old password (md5 hash) + NewPassword string `json:"new_password" binding:"required"` // New password (md5 hash) +} + +type LoginToken struct { + AccessToken string `json:"access_token"` // Access token (JWT) + TokenType string `json:"token_type"` // Token type (Usage: Authorization=${token_type} ${access_token}) + ExpiresAt int64 `json:"expires_at"` // Expired time (Unit: second) +} + +type UpdateCurrentUser struct { + Name string `json:"name" binding:"required,max=64"` // Name of user + Phone string `json:"phone" binding:"max=32"` // Phone number of user + Email string `json:"email" binding:"max=128"` // Email of user + Remark string `json:"remark" binding:"max=1024"` // Remark of user +} diff --git a/internal/mods/rbac/schema/menu.go b/internal/mods/rbac/schema/menu.go new file mode 100644 index 0000000..0bcbe46 --- /dev/null +++ b/internal/mods/rbac/schema/menu.go @@ -0,0 +1,178 @@ +package schema + +import ( + "encoding/json" + "strings" + "time" + + "github.guxuan/haibei/internal/config" + "github.guxuan/haibei/pkg/errors" + "github.guxuan/haibei/pkg/util" +) + +const ( + MenuStatusDisabled = "disabled" + MenuStatusEnabled = "enabled" +) + +var ( + MenusOrderParams = []util.OrderByParam{ + {Field: "sequence", Direction: util.DESC}, + {Field: "created_at", Direction: util.DESC}, + } +) + +// Menu management for RBAC +type Menu struct { + ID string `json:"id" gorm:"size:20;primarykey;"` // Unique ID + Code string `json:"code" gorm:"size:32;index;"` // Code of menu (unique for each level) + Name string `json:"name" gorm:"size:128;index"` // Display name of menu + Description string `json:"description" gorm:"size:1024"` // Details about menu + Sequence int `json:"sequence" gorm:"index;"` // Sequence for sorting (Order by desc) + Type string `json:"type" gorm:"size:20;index"` // Type of menu (page, button) + Path string `json:"path" gorm:"size:255;"` // Access path of menu + Properties string `json:"properties" gorm:"type:text;"` // Properties of menu (JSON) + Status string `json:"status" gorm:"size:20;index"` // Status of menu (enabled, disabled) + ParentID string `json:"parent_id" gorm:"size:20;index;"` // Parent ID (From Menu.ID) + ParentPath string `json:"parent_path" gorm:"size:255;index;"` // Parent path (split by .) + Children *Menus `json:"children" gorm:"-"` // Child menus + CreatedAt time.Time `json:"created_at" gorm:"index;"` // Create time + UpdatedAt time.Time `json:"updated_at" gorm:"index;"` // Update time + Resources MenuResources `json:"resources" gorm:"-"` // Resources of menu +} + +func (a *Menu) TableName() string { + return config.C.FormatTableName("menu") +} + +// Defining the query parameters for the `Menu` struct. +type MenuQueryParam struct { + util.PaginationParam + CodePath string `form:"code"` // Code path (like xxx.xxx.xxx) + LikeName string `form:"name"` // Display name of menu + IncludeResources bool `form:"includeResources"` // Include resources + InIDs []string `form:"-"` // Include menu IDs + Status string `form:"-"` // Status of menu (disabled, enabled) + ParentID string `form:"-"` // Parent ID (From Menu.ID) + ParentPathPrefix string `form:"-"` // Parent path (split by .) + UserID string `form:"-"` // User ID + RoleID string `form:"-"` // Role ID +} + +// Defining the query options for the `Menu` struct. +type MenuQueryOptions struct { + util.QueryOptions +} + +// Defining the query result for the `Menu` struct. +type MenuQueryResult struct { + Data Menus + PageResult *util.PaginationResult +} + +// Defining the slice of `Menu` struct. +type Menus []*Menu + +func (a Menus) Len() int { + return len(a) +} + +func (a Menus) Less(i, j int) bool { + if a[i].Sequence == a[j].Sequence { + return a[i].CreatedAt.Unix() > a[j].CreatedAt.Unix() + } + return a[i].Sequence > a[j].Sequence +} + +func (a Menus) Swap(i, j int) { + a[i], a[j] = a[j], a[i] +} + +func (a Menus) ToMap() map[string]*Menu { + m := make(map[string]*Menu) + for _, item := range a { + m[item.ID] = item + } + return m +} + +func (a Menus) SplitParentIDs() []string { + parentIDs := make([]string, 0, len(a)) + idMapper := make(map[string]struct{}) + for _, item := range a { + if _, ok := idMapper[item.ID]; ok { + continue + } + idMapper[item.ID] = struct{}{} + if pp := item.ParentPath; pp != "" { + for _, pid := range strings.Split(pp, util.TreePathDelimiter) { + if pid == "" { + continue + } + if _, ok := idMapper[pid]; ok { + continue + } + parentIDs = append(parentIDs, pid) + idMapper[pid] = struct{}{} + } + } + } + return parentIDs +} + +func (a Menus) ToTree() Menus { + var list Menus + m := a.ToMap() + for _, item := range a { + if item.ParentID == "" { + list = append(list, item) + continue + } + if parent, ok := m[item.ParentID]; ok { + if parent.Children == nil { + children := Menus{item} + parent.Children = &children + continue + } + *parent.Children = append(*parent.Children, item) + } + } + return list +} + +// Defining the data structure for creating a `Menu` struct. +type MenuForm struct { + Code string `json:"code" binding:"required,max=32"` // Code of menu (unique for each level) + Name string `json:"name" binding:"required,max=128"` // Display name of menu + Description string `json:"description"` // Details about menu + Sequence int `json:"sequence"` // Sequence for sorting (Order by desc) + Type string `json:"type" binding:"required,oneof=page button"` // Type of menu (page, button) + Path string `json:"path"` // Access path of menu + Properties string `json:"properties"` // Properties of menu (JSON) + Status string `json:"status" binding:"required,oneof=disabled enabled"` // Status of menu (enabled, disabled) + ParentID string `json:"parent_id"` // Parent ID (From Menu.ID) + Resources MenuResources `json:"resources"` // Resources of menu +} + +// A validation function for the `MenuForm` struct. +func (a *MenuForm) Validate() error { + if v := a.Properties; v != "" { + if !json.Valid([]byte(v)) { + return errors.BadRequest("", "invalid properties") + } + } + return nil +} + +func (a *MenuForm) FillTo(menu *Menu) error { + menu.Code = a.Code + menu.Name = a.Name + menu.Description = a.Description + menu.Sequence = a.Sequence + menu.Type = a.Type + menu.Path = a.Path + menu.Properties = a.Properties + menu.Status = a.Status + menu.ParentID = a.ParentID + return nil +} diff --git a/internal/mods/rbac/schema/menu_resource.go b/internal/mods/rbac/schema/menu_resource.go new file mode 100644 index 0000000..9fd948b --- /dev/null +++ b/internal/mods/rbac/schema/menu_resource.go @@ -0,0 +1,56 @@ +package schema + +import ( + "time" + + "github.guxuan/haibei/internal/config" + "github.guxuan/haibei/pkg/util" +) + +// Menu resource management for RBAC +type MenuResource struct { + ID string `json:"id" gorm:"size:20;primarykey"` // Unique ID + MenuID string `json:"menu_id" gorm:"size:20;index"` // From Menu.ID + Method string `json:"method" gorm:"size:20;"` // HTTP method + Path string `json:"path" gorm:"size:255;"` // API request path (e.g. /api/v1/users/:id) + CreatedAt time.Time `json:"created_at" gorm:"index;"` // Create time + UpdatedAt time.Time `json:"updated_at" gorm:"index;"` // Update time +} + +func (a *MenuResource) TableName() string { + return config.C.FormatTableName("menu_resource") +} + +// Defining the query parameters for the `MenuResource` struct. +type MenuResourceQueryParam struct { + util.PaginationParam + MenuID string `form:"-"` // From Menu.ID + MenuIDs []string `form:"-"` // From Menu.ID +} + +// Defining the query options for the `MenuResource` struct. +type MenuResourceQueryOptions struct { + util.QueryOptions +} + +// Defining the query result for the `MenuResource` struct. +type MenuResourceQueryResult struct { + Data MenuResources + PageResult *util.PaginationResult +} + +// Defining the slice of `MenuResource` struct. +type MenuResources []*MenuResource + +// Defining the data structure for creating a `MenuResource` struct. +type MenuResourceForm struct { +} + +// A validation function for the `MenuResourceForm` struct. +func (a *MenuResourceForm) Validate() error { + return nil +} + +func (a *MenuResourceForm) FillTo(menuResource *MenuResource) error { + return nil +} diff --git a/internal/mods/rbac/schema/role.go b/internal/mods/rbac/schema/role.go new file mode 100644 index 0000000..8dcd297 --- /dev/null +++ b/internal/mods/rbac/schema/role.go @@ -0,0 +1,80 @@ +package schema + +import ( + "time" + + "github.guxuan/haibei/internal/config" + "github.guxuan/haibei/pkg/util" +) + +const ( + RoleStatusEnabled = "enabled" // Enabled + RoleStatusDisabled = "disabled" // Disabled + + RoleResultTypeSelect = "select" // Select +) + +// Role management for RBAC +type Role struct { + ID string `json:"id" gorm:"size:20;primarykey;"` // Unique ID + Code string `json:"code" gorm:"size:32;index;"` // Code of role (unique) + Name string `json:"name" gorm:"size:128;index"` // Display name of role + Description string `json:"description" gorm:"size:1024"` // Details about role + Sequence int `json:"sequence" gorm:"index"` // Sequence for sorting + Status string `json:"status" gorm:"size:20;index"` // Status of role (disabled, enabled) + CreatedAt time.Time `json:"created_at" gorm:"index;"` // Create time + UpdatedAt time.Time `json:"updated_at" gorm:"index;"` // Update time + Menus RoleMenus `json:"menus" gorm:"-"` // Role menu list +} + +func (a *Role) TableName() string { + return config.C.FormatTableName("role") +} + +// Defining the query parameters for the `Role` struct. +type RoleQueryParam struct { + util.PaginationParam + LikeName string `form:"name"` // Display name of role + Status string `form:"status" binding:"oneof=disabled enabled ''"` // Status of role (disabled, enabled) + ResultType string `form:"resultType"` // Result type (options: select) + InIDs []string `form:"-"` // ID list + GtUpdatedAt *time.Time `form:"-"` // Update time is greater than +} + +// Defining the query options for the `Role` struct. +type RoleQueryOptions struct { + util.QueryOptions +} + +// Defining the query result for the `Role` struct. +type RoleQueryResult struct { + Data Roles + PageResult *util.PaginationResult +} + +// Defining the slice of `Role` struct. +type Roles []*Role + +// Defining the data structure for creating a `Role` struct. +type RoleForm struct { + Code string `json:"code" binding:"required,max=32"` // Code of role (unique) + Name string `json:"name" binding:"required,max=128"` // Display name of role + Description string `json:"description"` // Details about role + Sequence int `json:"sequence"` // Sequence for sorting + Status string `json:"status" binding:"required,oneof=disabled enabled"` // Status of role (enabled, disabled) + Menus RoleMenus `json:"menus"` // Role menu list +} + +// A validation function for the `RoleForm` struct. +func (a *RoleForm) Validate() error { + return nil +} + +func (a *RoleForm) FillTo(role *Role) error { + role.Code = a.Code + role.Name = a.Name + role.Description = a.Description + role.Sequence = a.Sequence + role.Status = a.Status + return nil +} diff --git a/internal/mods/rbac/schema/role_menu.go b/internal/mods/rbac/schema/role_menu.go new file mode 100644 index 0000000..0c0bc3f --- /dev/null +++ b/internal/mods/rbac/schema/role_menu.go @@ -0,0 +1,54 @@ +package schema + +import ( + "time" + + "github.guxuan/haibei/internal/config" + "github.guxuan/haibei/pkg/util" +) + +// Role permissions for RBAC +type RoleMenu struct { + ID string `json:"id" gorm:"size:20;primarykey"` // Unique ID + RoleID string `json:"role_id" gorm:"size:20;index"` // From Role.ID + MenuID string `json:"menu_id" gorm:"size:20;index"` // From Menu.ID + CreatedAt time.Time `json:"created_at" gorm:"index;"` // Create time + UpdatedAt time.Time `json:"updated_at" gorm:"index;"` // Update time +} + +func (a *RoleMenu) TableName() string { + return config.C.FormatTableName("role_menu") +} + +// Defining the query parameters for the `RoleMenu` struct. +type RoleMenuQueryParam struct { + util.PaginationParam + RoleID string `form:"-"` // From Role.ID +} + +// Defining the query options for the `RoleMenu` struct. +type RoleMenuQueryOptions struct { + util.QueryOptions +} + +// Defining the query result for the `RoleMenu` struct. +type RoleMenuQueryResult struct { + Data RoleMenus + PageResult *util.PaginationResult +} + +// Defining the slice of `RoleMenu` struct. +type RoleMenus []*RoleMenu + +// Defining the data structure for creating a `RoleMenu` struct. +type RoleMenuForm struct { +} + +// A validation function for the `RoleMenuForm` struct. +func (a *RoleMenuForm) Validate() error { + return nil +} + +func (a *RoleMenuForm) FillTo(roleMenu *RoleMenu) error { + return nil +} diff --git a/internal/mods/rbac/schema/user.go b/internal/mods/rbac/schema/user.go new file mode 100644 index 0000000..2766574 --- /dev/null +++ b/internal/mods/rbac/schema/user.go @@ -0,0 +1,106 @@ +package schema + +import ( + "time" + + "github.com/go-playground/validator/v10" + "github.guxuan/haibei/internal/config" + "github.guxuan/haibei/pkg/crypto/hash" + "github.guxuan/haibei/pkg/errors" + "github.guxuan/haibei/pkg/util" +) + +const ( + UserStatusActivated = "activated" + UserStatusFreezed = "freezed" +) + +// User management for RBAC +type User struct { + ID string `json:"id" gorm:"size:20;primarykey;"` // Unique ID + Username string `json:"username" gorm:"size:64;index"` // Username for login + Name string `json:"name" gorm:"size:64;index"` // Name of user + Password string `json:"-" gorm:"size:64;"` // Password for login (encrypted) + Phone string `json:"phone" gorm:"size:32;"` // Phone number of user + Email string `json:"email" gorm:"size:128;"` + CompanyID uint `json:"companyId" gorm:"index;"` // Remark of user + Remark string `json:"remark" gorm:"size:1024;"` // Remark of user + Status string `json:"status" gorm:"size:20;index"` // Status of user (activated, freezed) + CreatedAt time.Time `json:"created_at" gorm:"index;"` // Create time + UpdatedAt time.Time `json:"updated_at" gorm:"index;"` // Update time + Roles UserRoles `json:"roles" gorm:"-"` // Roles of user +} + +func (a *User) TableName() string { + return config.C.FormatTableName("user") +} + +// Defining the query parameters for the `User` struct. +type UserQueryParam struct { + util.PaginationParam + LikeUsername string `form:"username"` // Username for login + LikeName string `form:"name"` // Name of user + Status string `form:"status" binding:"oneof=activated freezed ''"` // Status of user (activated, freezed) +} + +// Defining the query options for the `User` struct. +type UserQueryOptions struct { + util.QueryOptions +} + +// Defining the query result for the `User` struct. +type UserQueryResult struct { + Data Users + PageResult *util.PaginationResult +} + +// Defining the slice of `User` struct. +type Users []*User + +func (a Users) ToIDs() []string { + var ids []string + for _, item := range a { + ids = append(ids, item.ID) + } + return ids +} + +// Defining the data structure for creating a `User` struct. +type UserForm struct { + Username string `json:"username" binding:"required,max=64"` // Username for login + Name string `json:"name" binding:"required,max=64"` // Name of user + Password string `json:"password" binding:"max=64"` // Password for login (md5 hash) + Phone string `json:"phone" binding:"max=32"` // Phone number of user + Email string `json:"email" binding:"max=128"` // Email of user + Remark string `json:"remark" binding:"max=1024"` // Remark of user + Status string `json:"status" binding:"required,oneof=activated freezed"` // Status of user (activated, freezed) + Roles UserRoles `json:"roles" binding:"required"` // Roles of user +} + +// A validation function for the `UserForm` struct. +func (a *UserForm) Validate() error { + if a.Email != "" && validator.New().Var(a.Email, "email") != nil { + return errors.BadRequest("", "Invalid email address") + } + return nil +} + +// Convert `UserForm` to `User` object. +func (a *UserForm) FillTo(user *User) error { + user.Username = a.Username + user.Name = a.Name + user.Phone = a.Phone + user.Email = a.Email + user.Remark = a.Remark + user.Status = a.Status + + if pass := a.Password; pass != "" { + hashPass, err := hash.GeneratePassword(pass) + if err != nil { + return errors.BadRequest("", "Failed to generate hash password: %s", err.Error()) + } + user.Password = hashPass + } + + return nil +} diff --git a/internal/mods/rbac/schema/user_role.go b/internal/mods/rbac/schema/user_role.go new file mode 100644 index 0000000..c717493 --- /dev/null +++ b/internal/mods/rbac/schema/user_role.go @@ -0,0 +1,74 @@ +package schema + +import ( + "time" + + "github.guxuan/haibei/internal/config" + "github.guxuan/haibei/pkg/util" +) + +// User roles for RBAC +type UserRole struct { + ID string `json:"id" gorm:"size:20;primarykey"` // Unique ID + UserID string `json:"user_id" gorm:"size:20;index"` // From User.ID + RoleID string `json:"role_id" gorm:"size:20;index"` // From Role.ID + CreatedAt time.Time `json:"created_at" gorm:"index;"` // Create time + UpdatedAt time.Time `json:"updated_at" gorm:"index;"` // Update time + RoleName string `json:"role_name" gorm:"<-:false;-:migration;"` // From Role.Name +} + +func (a *UserRole) TableName() string { + return config.C.FormatTableName("user_role") +} + +// Defining the query parameters for the `UserRole` struct. +type UserRoleQueryParam struct { + util.PaginationParam + InUserIDs []string `form:"-"` // From User.ID + UserID string `form:"-"` // From User.ID + RoleID string `form:"-"` // From Role.ID +} + +// Defining the query options for the `UserRole` struct. +type UserRoleQueryOptions struct { + util.QueryOptions + JoinRole bool // Join role table +} + +// Defining the query result for the `UserRole` struct. +type UserRoleQueryResult struct { + Data UserRoles + PageResult *util.PaginationResult +} + +// Defining the slice of `UserRole` struct. +type UserRoles []*UserRole + +func (a UserRoles) ToUserIDMap() map[string]UserRoles { + m := make(map[string]UserRoles) + for _, userRole := range a { + m[userRole.UserID] = append(m[userRole.UserID], userRole) + } + return m +} + +func (a UserRoles) ToRoleIDs() []string { + var ids []string + for _, item := range a { + ids = append(ids, item.RoleID) + } + return ids +} + +// Defining the data structure for creating a `UserRole` struct. +type UserRoleForm struct { +} + +// A validation function for the `UserRoleForm` struct. +func (a *UserRoleForm) Validate() error { + return nil +} + +func (a *UserRoleForm) FillTo(userRole *UserRole) error { + return nil +} diff --git a/internal/mods/rbac/wire.go b/internal/mods/rbac/wire.go new file mode 100644 index 0000000..a92fb92 --- /dev/null +++ b/internal/mods/rbac/wire.go @@ -0,0 +1,31 @@ +package rbac + +import ( + "github.com/google/wire" + "github.guxuan/haibei/internal/mods/rbac/api" + "github.guxuan/haibei/internal/mods/rbac/biz" + "github.guxuan/haibei/internal/mods/rbac/dal" +) + +// Collection of wire providers +var Set = wire.NewSet( + wire.Struct(new(RBAC), "*"), + wire.Struct(new(Casbinx), "*"), + wire.Struct(new(dal.Menu), "*"), + wire.Struct(new(biz.Menu), "*"), + wire.Struct(new(api.Menu), "*"), + wire.Struct(new(dal.MenuResource), "*"), + wire.Struct(new(dal.Role), "*"), + wire.Struct(new(biz.Role), "*"), + wire.Struct(new(api.Role), "*"), + wire.Struct(new(dal.RoleMenu), "*"), + wire.Struct(new(dal.User), "*"), + wire.Struct(new(biz.User), "*"), + wire.Struct(new(api.User), "*"), + wire.Struct(new(dal.UserRole), "*"), + wire.Struct(new(biz.Login), "*"), + wire.Struct(new(api.Login), "*"), + wire.Struct(new(api.Logger), "*"), + wire.Struct(new(biz.Logger), "*"), + wire.Struct(new(dal.Logger), "*"), +) diff --git a/internal/swagger/docs.go b/internal/swagger/docs.go new file mode 100644 index 0000000..83d5261 --- /dev/null +++ b/internal/swagger/docs.go @@ -0,0 +1,8027 @@ +// Package swagger Code generated by swaggo/swag. DO NOT EDIT +package swagger + +import "github.com/swaggo/swag" + +const docTemplate = `{ + "schemes": {{ marshal .Schemes }}, + "swagger": "2.0", + "info": { + "description": "{{escape .Description}}", + "title": "{{.Title}}", + "contact": {}, + "version": "{{.Version}}" + }, + "host": "{{.Host}}", + "basePath": "{{.BasePath}}", + "paths": { + "/api/v1/activities": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "活动模块" + ], + "summary": "Query activity list", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "pagination index", + "name": "current", + "in": "query", + "required": true + }, + { + "type": "integer", + "default": 10, + "description": "pagination size", + "name": "pageSize", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.Activity" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "活动模块" + ], + "summary": "Create activity record", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.ActivityForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.Activity" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/activities/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "活动模块" + ], + "summary": "Get activity record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.Activity" + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "活动模块" + ], + "summary": "Update activity record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.ActivityForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "活动模块" + ], + "summary": "Delete activity record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/activity-details": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "ActivityDetailAPI" + ], + "summary": "Query activity detail list", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "pagination index", + "name": "current", + "in": "query", + "required": true + }, + { + "type": "integer", + "default": 10, + "description": "pagination size", + "name": "pageSize", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.ActivityDetail" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "ActivityDetailAPI" + ], + "summary": "Create activity detail record", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.ActivityDetailForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.ActivityDetail" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/activity-details/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "ActivityDetailAPI" + ], + "summary": "Get activity detail record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.ActivityDetail" + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "ActivityDetailAPI" + ], + "summary": "Update activity detail record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.ActivityDetailForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "ActivityDetailAPI" + ], + "summary": "Delete activity detail record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/apps/banner": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "APP端" + ], + "summary": "首页banner", + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.Banner" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/apps/bind": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "APP端" + ], + "summary": "小程序绑定手机号", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/apps/login": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "APP端" + ], + "summary": "小程序登陆", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/apps/sms/{phone}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "APP端" + ], + "summary": "发送验证码", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/areas": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "区域模块" + ], + "summary": "Query area list", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "pagination index", + "name": "current", + "in": "query", + "required": true + }, + { + "type": "integer", + "default": 10, + "description": "pagination size", + "name": "pageSize", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.Area" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "区域模块" + ], + "summary": "Create area record", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.AreaForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.Area" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/areas/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "区域模块" + ], + "summary": "Get area record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.Area" + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "区域模块" + ], + "summary": "Update area record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.AreaForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "区域模块" + ], + "summary": "Delete area record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/balances": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "积分模块" + ], + "summary": "Query balance list", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "pagination index", + "name": "current", + "in": "query", + "required": true + }, + { + "type": "integer", + "default": 10, + "description": "pagination size", + "name": "pageSize", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.Balance" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "积分模块" + ], + "summary": "Create balance record", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.BalanceForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.Balance" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/balances/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "积分模块" + ], + "summary": "Get balance record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.Balance" + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "积分模块" + ], + "summary": "Update balance record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.BalanceForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "积分模块" + ], + "summary": "Delete balance record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/banners": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "首页轮播图模块" + ], + "summary": "Query banner list", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "pagination index", + "name": "current", + "in": "query", + "required": true + }, + { + "type": "integer", + "default": 10, + "description": "pagination size", + "name": "pageSize", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.Banner" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "首页轮播图模块" + ], + "summary": "Create banner record", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.BannerForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.Banner" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/banners/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "首页轮播图模块" + ], + "summary": "Get banner record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.Banner" + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "首页轮播图模块" + ], + "summary": "Update banner record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.BannerForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "首页轮播图模块" + ], + "summary": "Delete banner record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/captcha/id": { + "get": { + "tags": [ + "LoginAPI" + ], + "summary": "Get captcha ID", + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.Captcha" + } + } + } + ] + } + } + } + } + }, + "/api/v1/captcha/image": { + "get": { + "produces": [ + "image/png" + ], + "tags": [ + "LoginAPI" + ], + "summary": "Response captcha image", + "parameters": [ + { + "type": "string", + "description": "Captcha ID", + "name": "id", + "in": "query", + "required": true + }, + { + "type": "number", + "description": "Reload captcha image (reload=1)", + "name": "reload", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Captcha image" + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/commons": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "CommonAPI" + ], + "summary": "Query common list", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "pagination index", + "name": "current", + "in": "query", + "required": true + }, + { + "type": "integer", + "default": 10, + "description": "pagination size", + "name": "pageSize", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.Common" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "CommonAPI" + ], + "summary": "Create common record", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.CommonForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.Common" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/commons/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "CommonAPI" + ], + "summary": "Get common record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.Common" + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "CommonAPI" + ], + "summary": "Update common record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.CommonForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "CommonAPI" + ], + "summary": "Delete common record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/companies": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "项目模块" + ], + "summary": "Query company list", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "pagination index", + "name": "current", + "in": "query", + "required": true + }, + { + "type": "integer", + "default": 10, + "description": "pagination size", + "name": "pageSize", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.Company" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "项目模块" + ], + "summary": "Create company record", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.CompanyForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.Company" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/companies/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "项目模块" + ], + "summary": "Get company record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.Company" + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "项目模块" + ], + "summary": "Update company record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.CompanyForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "项目模块" + ], + "summary": "Delete company record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/current/logout": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "LoginAPI" + ], + "summary": "Logout system", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/current/menus": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "LoginAPI" + ], + "summary": "Query current user menus based on the current user role", + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.Menu" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/current/password": { + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "LoginAPI" + ], + "summary": "Change current user password", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.UpdateLoginPassword" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/current/refresh-token": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "LoginAPI" + ], + "summary": "Refresh current access token", + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.LoginToken" + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/current/user": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "LoginAPI" + ], + "summary": "Get current user info", + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.User" + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "LoginAPI" + ], + "summary": "Update current user info", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.UpdateCurrentUser" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/customers": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "客户模块" + ], + "summary": "Query customer list", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "pagination index", + "name": "current", + "in": "query", + "required": true + }, + { + "type": "integer", + "default": 10, + "description": "pagination size", + "name": "pageSize", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.Customer" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "客户模块" + ], + "summary": "Create customer record", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.CustomerForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.Customer" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/customers/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "客户模块" + ], + "summary": "Get customer record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.Customer" + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "客户模块" + ], + "summary": "Update customer record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.CustomerForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "客户模块" + ], + "summary": "Delete customer record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/grades": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "权益模块" + ], + "summary": "Query grade list", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "pagination index", + "name": "current", + "in": "query", + "required": true + }, + { + "type": "integer", + "default": 10, + "description": "pagination size", + "name": "pageSize", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.Grade" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "权益模块" + ], + "summary": "Create grade record", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.GradeForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.Grade" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/grades/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "权益模块" + ], + "summary": "Get grade record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.Grade" + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "权益模块" + ], + "summary": "Update grade record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.GradeForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "权益模块" + ], + "summary": "Delete grade record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/loggers": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "LoggerAPI" + ], + "summary": "Query logger list", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "pagination index", + "name": "current", + "in": "query", + "required": true + }, + { + "type": "integer", + "default": 10, + "description": "pagination size", + "name": "pageSize", + "in": "query", + "required": true + }, + { + "type": "string", + "description": "log level", + "name": "level", + "in": "query" + }, + { + "type": "string", + "description": "trace ID", + "name": "traceID", + "in": "query" + }, + { + "type": "string", + "description": "user name", + "name": "userName", + "in": "query" + }, + { + "type": "string", + "description": "log tag", + "name": "tag", + "in": "query" + }, + { + "type": "string", + "description": "log message", + "name": "message", + "in": "query" + }, + { + "type": "string", + "description": "start time", + "name": "startTime", + "in": "query" + }, + { + "type": "string", + "description": "end time", + "name": "endTime", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.Logger" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/login": { + "post": { + "tags": [ + "LoginAPI" + ], + "summary": "Login system with username and password", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.LoginForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.LoginToken" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/menus": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "MenuAPI" + ], + "summary": "Query menu tree data", + "parameters": [ + { + "type": "string", + "description": "Code path of menu (like xxx.xxx.xxx)", + "name": "code", + "in": "query" + }, + { + "type": "string", + "description": "Name of menu", + "name": "name", + "in": "query" + }, + { + "type": "boolean", + "description": "Whether to include menu resources", + "name": "includeResources", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.Menu" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "MenuAPI" + ], + "summary": "Create menu record", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.MenuForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.Menu" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/menus/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "MenuAPI" + ], + "summary": "Get menu record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.Menu" + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "MenuAPI" + ], + "summary": "Update menu record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.MenuForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "MenuAPI" + ], + "summary": "Delete menu record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/notices": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "公告模块" + ], + "summary": "Query notice list", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "pagination index", + "name": "current", + "in": "query", + "required": true + }, + { + "type": "integer", + "default": 10, + "description": "pagination size", + "name": "pageSize", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.Notice" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "公告模块" + ], + "summary": "Create notice record", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.NoticeForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.Notice" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/notices/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "公告模块" + ], + "summary": "Get notice record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.Notice" + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "公告模块" + ], + "summary": "Update notice record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.NoticeForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "公告模块" + ], + "summary": "Delete notice record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/points": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "PointAPI" + ], + "summary": "Query point list", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "pagination index", + "name": "current", + "in": "query", + "required": true + }, + { + "type": "integer", + "default": 10, + "description": "pagination size", + "name": "pageSize", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.Point" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "PointAPI" + ], + "summary": "Create point record", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.PointForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.Point" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/points/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "PointAPI" + ], + "summary": "Get point record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.Point" + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "PointAPI" + ], + "summary": "Update point record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.PointForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "PointAPI" + ], + "summary": "Delete point record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/product-categories": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "权益产品分类模块" + ], + "summary": "Query product category list", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "pagination index", + "name": "current", + "in": "query", + "required": true + }, + { + "type": "integer", + "default": 10, + "description": "pagination size", + "name": "pageSize", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.ProductCategory" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "权益产品分类模块" + ], + "summary": "Create product category record", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.ProductCategoryForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.ProductCategory" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/product-categories/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "权益产品分类模块" + ], + "summary": "Get product category record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.ProductCategory" + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "权益产品分类模块" + ], + "summary": "Update product category record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.ProductCategoryForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "权益产品分类模块" + ], + "summary": "Delete product category record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/product-orders": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "ProductOrderAPI" + ], + "summary": "Query product order list", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "pagination index", + "name": "current", + "in": "query", + "required": true + }, + { + "type": "integer", + "default": 10, + "description": "pagination size", + "name": "pageSize", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.ProductOrder" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "ProductOrderAPI" + ], + "summary": "Create product order record", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.ProductOrderForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.ProductOrder" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/product-orders/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "ProductOrderAPI" + ], + "summary": "Get product order record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.ProductOrder" + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "ProductOrderAPI" + ], + "summary": "Update product order record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.ProductOrderForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "ProductOrderAPI" + ], + "summary": "Delete product order record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/products": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "权益产品模块" + ], + "summary": "Query product list", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "pagination index", + "name": "current", + "in": "query", + "required": true + }, + { + "type": "integer", + "default": 10, + "description": "pagination size", + "name": "pageSize", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.Product" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "权益产品模块" + ], + "summary": "Create product record", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.ProductForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.Product" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/products/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "权益产品模块" + ], + "summary": "Get product record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.Product" + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "权益产品模块" + ], + "summary": "Update product record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.ProductForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "权益产品模块" + ], + "summary": "Delete product record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/qeustion-details": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "QeustionDetailAPI" + ], + "summary": "Query qeustion detail list", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "pagination index", + "name": "current", + "in": "query", + "required": true + }, + { + "type": "integer", + "default": 10, + "description": "pagination size", + "name": "pageSize", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.QeustionDetail" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "QeustionDetailAPI" + ], + "summary": "Create qeustion detail record", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.QeustionDetailForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.QeustionDetail" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/qeustion-details/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "QeustionDetailAPI" + ], + "summary": "Get qeustion detail record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.QeustionDetail" + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "QeustionDetailAPI" + ], + "summary": "Update qeustion detail record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.QeustionDetailForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "QeustionDetailAPI" + ], + "summary": "Delete qeustion detail record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/questionnaires": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "问卷模块" + ], + "summary": "Query questionnaire list", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "pagination index", + "name": "current", + "in": "query", + "required": true + }, + { + "type": "integer", + "default": 10, + "description": "pagination size", + "name": "pageSize", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.Questionnaire" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "问卷模块" + ], + "summary": "Create questionnaire record", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.QuestionnaireForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.Questionnaire" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/questionnaires/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "问卷模块" + ], + "summary": "Get questionnaire record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.Questionnaire" + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "问卷模块" + ], + "summary": "Update questionnaire record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.QuestionnaireForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "问卷模块" + ], + "summary": "Delete questionnaire record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/reception_center": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "楼盘模块" + ], + "summary": "Query ReceptionCenter list", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "pagination index", + "name": "current", + "in": "query", + "required": true + }, + { + "type": "integer", + "default": 10, + "description": "pagination size", + "name": "pageSize", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.ReceptionCenter" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "楼盘模块" + ], + "summary": "check QrCode", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/api.CheckQrCodeStruct" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/reception_center/qr_code/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "楼盘模块" + ], + "summary": "Get ReceptionCenter QrCode by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "integer" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/reception_center/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "楼盘模块" + ], + "summary": "Get ReceptionCenter record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.ReceptionCenter" + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "楼盘模块" + ], + "summary": "Update ReceptionCenter record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.ReceptionCenterForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "楼盘模块" + ], + "summary": "Delete ReceptionCenter record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/roles": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "RoleAPI" + ], + "summary": "Query role list", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "pagination index", + "name": "current", + "in": "query", + "required": true + }, + { + "type": "integer", + "default": 10, + "description": "pagination size", + "name": "pageSize", + "in": "query", + "required": true + }, + { + "type": "string", + "description": "Display name of role", + "name": "name", + "in": "query" + }, + { + "type": "string", + "description": "Status of role (disabled, enabled)", + "name": "status", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.Role" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "RoleAPI" + ], + "summary": "Create role record", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.RoleForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.Role" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/roles/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "RoleAPI" + ], + "summary": "Get role record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.Role" + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "RoleAPI" + ], + "summary": "Update role record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.RoleForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "RoleAPI" + ], + "summary": "Delete role record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/users": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "UserAPI" + ], + "summary": "Query user list", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "pagination index", + "name": "current", + "in": "query", + "required": true + }, + { + "type": "integer", + "default": 10, + "description": "pagination size", + "name": "pageSize", + "in": "query", + "required": true + }, + { + "type": "string", + "description": "Username for login", + "name": "username", + "in": "query" + }, + { + "type": "string", + "description": "Name of user", + "name": "name", + "in": "query" + }, + { + "type": "string", + "description": "Status of user (activated, freezed)", + "name": "status", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.User" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "UserAPI" + ], + "summary": "Create user record", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.UserForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.User" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/users/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "UserAPI" + ], + "summary": "Get user record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.User" + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "UserAPI" + ], + "summary": "Update user record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.UserForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "UserAPI" + ], + "summary": "Delete user record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/users/{id}/reset-pwd": { + "patch": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "UserAPI" + ], + "summary": "Reset user password by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + } + }, + "definitions": { + "api.CheckQrCodeStruct": { + "type": "object", + "properties": { + "customer_id": { + "type": "integer" + }, + "reception_center_id": { + "type": "integer" + } + } + }, + "errors.Error": { + "type": "object", + "properties": { + "code": { + "type": "integer" + }, + "detail": { + "type": "string" + }, + "id": { + "type": "string" + }, + "status": { + "type": "string" + } + } + }, + "schema.Activity": { + "type": "object", + "properties": { + "address": { + "type": "string" + }, + "areaId": { + "type": "integer" + }, + "content": { + "type": "string" + }, + "cover": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "createdId": { + "type": "integer" + }, + "deletedAt": { + "type": "integer" + }, + "deletedId": { + "type": "integer" + }, + "endAt": { + "type": "string" + }, + "endSignupAt": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "images": { + "type": "array", + "items": { + "type": "string" + } + }, + "maxSignupNum": { + "type": "integer" + }, + "point": { + "type": "integer" + }, + "signupNum": { + "type": "integer" + }, + "startAt": { + "type": "string" + }, + "status": { + "type": "string" + }, + "title": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + }, + "schema.ActivityDetail": { + "type": "object", + "properties": { + "activityId": { + "type": "integer" + }, + "createdAt": { + "type": "string" + }, + "createdId": { + "type": "integer" + }, + "customerId": { + "type": "integer" + }, + "deletedAt": { + "type": "integer" + }, + "deletedId": { + "type": "integer" + }, + "id": { + "type": "integer" + }, + "status": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + }, + "schema.ActivityDetailForm": { + "type": "object", + "properties": { + "activityId": { + "type": "integer" + }, + "customerId": { + "type": "integer" + }, + "status": { + "type": "string" + } + } + }, + "schema.ActivityForm": { + "type": "object", + "properties": { + "address": { + "type": "string" + }, + "areaId": { + "type": "integer" + }, + "content": { + "type": "string" + }, + "cover": { + "type": "string" + }, + "endAt": { + "type": "string" + }, + "endSignupAt": { + "type": "string" + }, + "images": { + "type": "array", + "items": { + "type": "string" + } + }, + "maxSignupNum": { + "type": "integer" + }, + "point": { + "type": "integer" + }, + "signupNum": { + "type": "integer" + }, + "startAt": { + "type": "string" + }, + "status": { + "type": "string" + }, + "title": { + "type": "string" + } + } + }, + "schema.Area": { + "type": "object", + "properties": { + "createdAt": { + "type": "string" + }, + "createdId": { + "type": "integer" + }, + "deletedAt": { + "type": "integer" + }, + "deletedId": { + "type": "integer" + }, + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "status": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + }, + "schema.AreaForm": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "status": { + "type": "string" + } + } + }, + "schema.Balance": { + "type": "object", + "properties": { + "after": { + "type": "integer" + }, + "before": { + "type": "integer" + }, + "change": { + "type": "integer" + }, + "createdAt": { + "type": "string" + }, + "createdId": { + "type": "integer" + }, + "creatorId": { + "type": "string" + }, + "customerId": { + "type": "integer" + }, + "deletedAt": { + "type": "integer" + }, + "deletedId": { + "type": "integer" + }, + "id": { + "type": "integer" + }, + "operatorId": { + "type": "integer" + }, + "reason": { + "type": "string" + }, + "type": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + }, + "schema.BalanceForm": { + "type": "object", + "properties": { + "after": { + "type": "integer" + }, + "before": { + "type": "integer" + }, + "change": { + "type": "integer" + }, + "creatorId": { + "type": "string" + }, + "customerId": { + "type": "integer" + }, + "operatorId": { + "type": "integer" + }, + "reason": { + "type": "string" + }, + "type": { + "type": "string" + } + } + }, + "schema.Banner": { + "type": "object", + "properties": { + "areaId": { + "type": "integer" + }, + "createdAt": { + "type": "string" + }, + "createdId": { + "type": "integer" + }, + "deletedAt": { + "type": "integer" + }, + "deletedId": { + "type": "integer" + }, + "id": { + "type": "integer" + }, + "img": { + "type": "string" + }, + "link": { + "type": "string" + }, + "name": { + "type": "string" + }, + "sequence": { + "type": "integer" + }, + "status": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + }, + "schema.BannerForm": { + "type": "object", + "properties": { + "areaId": { + "type": "integer" + }, + "img": { + "type": "string" + }, + "link": { + "type": "string" + }, + "name": { + "type": "string" + }, + "sequence": { + "type": "integer" + }, + "status": { + "type": "string" + } + } + }, + "schema.Captcha": { + "type": "object", + "properties": { + "captcha_id": { + "description": "Captcha ID", + "type": "string" + } + } + }, + "schema.Common": { + "type": "object", + "properties": { + "created_at": { + "description": "Create time", + "type": "string" + }, + "id": { + "description": "Unique ID", + "type": "string" + }, + "updated_at": { + "description": "Update time", + "type": "string" + } + } + }, + "schema.CommonForm": { + "type": "object" + }, + "schema.Company": { + "type": "object", + "properties": { + "areaId": { + "type": "integer" + }, + "createdAt": { + "type": "string" + }, + "createdId": { + "type": "integer" + }, + "deletedAt": { + "type": "integer" + }, + "deletedId": { + "type": "integer" + }, + "id": { + "type": "integer" + }, + "img": { + "type": "string" + }, + "name": { + "type": "string" + }, + "status": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + }, + "schema.CompanyForm": { + "type": "object", + "properties": { + "areaId": { + "type": "integer" + }, + "img": { + "type": "string" + }, + "name": { + "type": "string" + }, + "status": { + "type": "string" + } + } + }, + "schema.Customer": { + "type": "object", + "properties": { + "areaId": { + "type": "integer" + }, + "avatar": { + "type": "string" + }, + "balance": { + "type": "integer" + }, + "birthday": { + "type": "string" + }, + "companyId": { + "type": "integer" + }, + "createdAt": { + "type": "string" + }, + "createdId": { + "type": "integer" + }, + "deletedAt": { + "type": "integer" + }, + "deletedId": { + "type": "integer" + }, + "grade": { + "type": "integer" + }, + "id": { + "type": "integer" + }, + "inviterId": { + "type": "integer" + }, + "mentorId": { + "type": "string" + }, + "name": { + "type": "string" + }, + "phone": { + "type": "string" + }, + "status": { + "type": "string" + }, + "updatedAt": { + "type": "string" + }, + "wxSign": { + "type": "string" + } + } + }, + "schema.CustomerForm": { + "type": "object", + "properties": { + "areaId": { + "type": "integer" + }, + "avatar": { + "type": "string" + }, + "balance": { + "type": "integer" + }, + "birthday": { + "type": "string" + }, + "companyId": { + "type": "integer" + }, + "grade": { + "type": "integer" + }, + "inviterId": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "phone": { + "type": "string" + }, + "status": { + "type": "string" + }, + "wxSign": { + "type": "string" + } + } + }, + "schema.Grade": { + "type": "object", + "properties": { + "createdAt": { + "type": "string" + }, + "createdId": { + "type": "integer" + }, + "deletedAt": { + "type": "integer" + }, + "deletedId": { + "type": "integer" + }, + "icon": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "introduce": { + "type": "string" + }, + "lv": { + "type": "string" + }, + "name": { + "type": "string" + }, + "score": { + "type": "integer" + }, + "status": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + }, + "schema.GradeForm": { + "type": "object", + "properties": { + "icon": { + "type": "string" + }, + "introduce": { + "type": "string" + }, + "lv": { + "type": "string" + }, + "name": { + "type": "string" + }, + "score": { + "type": "integer" + }, + "status": { + "type": "string" + } + } + }, + "schema.Logger": { + "type": "object", + "properties": { + "created_at": { + "description": "Create time", + "type": "string" + }, + "data": { + "description": "Log data", + "type": "string" + }, + "id": { + "description": "Unique ID", + "type": "string" + }, + "level": { + "description": "Log level", + "type": "string" + }, + "login_name": { + "description": "From User.Username", + "type": "string" + }, + "message": { + "description": "Log message", + "type": "string" + }, + "stack": { + "description": "Error stack", + "type": "string" + }, + "tag": { + "description": "Log tag", + "type": "string" + }, + "trace_id": { + "description": "Trace ID", + "type": "string" + }, + "user_id": { + "description": "User ID", + "type": "string" + }, + "user_name": { + "description": "From User.Name", + "type": "string" + } + } + }, + "schema.LoginForm": { + "type": "object", + "required": [ + "captcha_code", + "captcha_id", + "password", + "username" + ], + "properties": { + "captcha_code": { + "description": "Captcha verify code", + "type": "string" + }, + "captcha_id": { + "description": "Captcha verify id", + "type": "string" + }, + "password": { + "description": "Login password (md5 hash)", + "type": "string" + }, + "username": { + "description": "Login name", + "type": "string" + } + } + }, + "schema.LoginToken": { + "type": "object", + "properties": { + "access_token": { + "description": "Access token (JWT)", + "type": "string" + }, + "expires_at": { + "description": "Expired time (Unit: second)", + "type": "integer" + }, + "token_type": { + "description": "Token type (Usage: Authorization=${token_type} ${access_token})", + "type": "string" + } + } + }, + "schema.Menu": { + "type": "object", + "properties": { + "children": { + "description": "Child menus", + "type": "array", + "items": { + "$ref": "#/definitions/schema.Menu" + } + }, + "code": { + "description": "Code of menu (unique for each level)", + "type": "string" + }, + "created_at": { + "description": "Create time", + "type": "string" + }, + "description": { + "description": "Details about menu", + "type": "string" + }, + "id": { + "description": "Unique ID", + "type": "string" + }, + "name": { + "description": "Display name of menu", + "type": "string" + }, + "parent_id": { + "description": "Parent ID (From Menu.ID)", + "type": "string" + }, + "parent_path": { + "description": "Parent path (split by .)", + "type": "string" + }, + "path": { + "description": "Access path of menu", + "type": "string" + }, + "properties": { + "description": "Properties of menu (JSON)", + "type": "string" + }, + "resources": { + "description": "Resources of menu", + "type": "array", + "items": { + "$ref": "#/definitions/schema.MenuResource" + } + }, + "sequence": { + "description": "Sequence for sorting (Order by desc)", + "type": "integer" + }, + "status": { + "description": "Status of menu (enabled, disabled)", + "type": "string" + }, + "type": { + "description": "Type of menu (page, button)", + "type": "string" + }, + "updated_at": { + "description": "Update time", + "type": "string" + } + } + }, + "schema.MenuForm": { + "type": "object", + "required": [ + "code", + "name", + "status", + "type" + ], + "properties": { + "code": { + "description": "Code of menu (unique for each level)", + "type": "string", + "maxLength": 32 + }, + "description": { + "description": "Details about menu", + "type": "string" + }, + "name": { + "description": "Display name of menu", + "type": "string", + "maxLength": 128 + }, + "parent_id": { + "description": "Parent ID (From Menu.ID)", + "type": "string" + }, + "path": { + "description": "Access path of menu", + "type": "string" + }, + "properties": { + "description": "Properties of menu (JSON)", + "type": "string" + }, + "resources": { + "description": "Resources of menu", + "type": "array", + "items": { + "$ref": "#/definitions/schema.MenuResource" + } + }, + "sequence": { + "description": "Sequence for sorting (Order by desc)", + "type": "integer" + }, + "status": { + "description": "Status of menu (enabled, disabled)", + "type": "string", + "enum": [ + "disabled", + "enabled" + ] + }, + "type": { + "description": "Type of menu (page, button)", + "type": "string", + "enum": [ + "page", + "button" + ] + } + } + }, + "schema.MenuResource": { + "type": "object", + "properties": { + "created_at": { + "description": "Create time", + "type": "string" + }, + "id": { + "description": "Unique ID", + "type": "string" + }, + "menu_id": { + "description": "From Menu.ID", + "type": "string" + }, + "method": { + "description": "HTTP method", + "type": "string" + }, + "path": { + "description": "API request path (e.g. /api/v1/users/:id)", + "type": "string" + }, + "updated_at": { + "description": "Update time", + "type": "string" + } + } + }, + "schema.Notice": { + "type": "object", + "properties": { + "areaId": { + "type": "integer" + }, + "content": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "createdId": { + "type": "integer" + }, + "deletedAt": { + "type": "integer" + }, + "deletedId": { + "type": "integer" + }, + "id": { + "type": "integer" + }, + "sequence": { + "type": "integer" + }, + "status": { + "type": "string" + }, + "title": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + }, + "schema.NoticeForm": { + "type": "object", + "properties": { + "areaId": { + "type": "integer" + }, + "content": { + "type": "string" + }, + "sequence": { + "type": "integer" + }, + "status": { + "type": "string" + }, + "title": { + "type": "string" + } + } + }, + "schema.Point": { + "type": "object", + "properties": { + "created_at": { + "description": "Create time", + "type": "string" + }, + "id": { + "description": "Unique ID", + "type": "string" + }, + "updated_at": { + "description": "Update time", + "type": "string" + } + } + }, + "schema.PointForm": { + "type": "object" + }, + "schema.Product": { + "type": "object", + "properties": { + "areaId": { + "type": "integer" + }, + "businessImgs": { + "type": "array", + "items": { + "type": "string" + } + }, + "category": { + "description": "关联分类", + "allOf": [ + { + "$ref": "#/definitions/schema.ProductCategory" + } + ] + }, + "categoryId": { + "type": "integer" + }, + "cover": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "createdId": { + "type": "integer" + }, + "deletedAt": { + "type": "integer" + }, + "deletedId": { + "type": "integer" + }, + "details": { + "type": "string" + }, + "expireAt": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "instructions": { + "type": "string" + }, + "isHot": { + "type": "boolean" + }, + "maxNum": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "policy": { + "type": "string" + }, + "price": { + "type": "integer" + }, + "status": { + "type": "string" + }, + "stock": { + "type": "integer" + }, + "type": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + }, + "schema.ProductCategory": { + "type": "object", + "properties": { + "createdAt": { + "type": "string" + }, + "createdId": { + "type": "integer" + }, + "deletedAt": { + "type": "integer" + }, + "deletedId": { + "type": "integer" + }, + "icon": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "introduce": { + "type": "string" + }, + "name": { + "type": "string" + }, + "parentId": { + "type": "integer" + }, + "status": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + }, + "schema.ProductCategoryForm": { + "type": "object", + "properties": { + "icon": { + "type": "string" + }, + "introduce": { + "type": "string" + }, + "name": { + "type": "string" + }, + "parentId": { + "type": "integer" + }, + "status": { + "type": "string" + } + } + }, + "schema.ProductForm": { + "type": "object", + "properties": { + "areaId": { + "type": "integer" + }, + "businessImgs": { + "type": "array", + "items": { + "type": "string" + } + }, + "categoryId": { + "type": "integer" + }, + "cover": { + "type": "string" + }, + "details": { + "type": "string" + }, + "expireAt": { + "type": "string" + }, + "instructions": { + "type": "string" + }, + "isHot": { + "type": "boolean" + }, + "maxNum": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "policy": { + "type": "string" + }, + "price": { + "type": "integer" + }, + "status": { + "type": "string" + }, + "stock": { + "type": "integer" + }, + "type": { + "type": "string" + } + } + }, + "schema.ProductOrder": { + "type": "object", + "properties": { + "createdAt": { + "type": "string" + }, + "createdId": { + "type": "integer" + }, + "customerId": { + "type": "integer" + }, + "deletedAt": { + "type": "integer" + }, + "deletedId": { + "type": "integer" + }, + "id": { + "type": "integer" + }, + "mentorId": { + "type": "string" + }, + "productId": { + "type": "integer" + }, + "status": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + }, + "schema.ProductOrderForm": { + "type": "object", + "properties": { + "customerId": { + "type": "integer" + }, + "mentorId": { + "type": "string" + }, + "productId": { + "type": "integer" + }, + "status": { + "type": "string" + } + } + }, + "schema.QeustionDetail": { + "type": "object", + "properties": { + "content": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.QuestionDetailTable" + } + }, + "createdAt": { + "type": "string" + }, + "createdId": { + "type": "integer" + }, + "customerId": { + "type": "integer" + }, + "deletedAt": { + "type": "integer" + }, + "deletedId": { + "type": "integer" + }, + "id": { + "type": "integer" + }, + "questionId": { + "type": "integer" + }, + "status": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + }, + "schema.QeustionDetailForm": { + "type": "object", + "properties": { + "content": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.QuestionDetailTable" + } + }, + "customerId": { + "type": "integer" + }, + "questionId": { + "type": "integer" + }, + "status": { + "type": "string" + } + } + }, + "schema.QuestionDetailTable": { + "type": "object", + "properties": { + "answer": { + "type": "string" + }, + "label": { + "type": "string" + }, + "options": { + "type": "array", + "items": { + "type": "string" + } + }, + "required": { + "type": "boolean" + }, + "type": { + "type": "string" + } + } + }, + "schema.Questionnaire": { + "type": "object", + "properties": { + "areaId": { + "type": "integer" + }, + "content": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.QuestionnaireTable" + } + }, + "cover": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "createdId": { + "type": "integer" + }, + "deletedAt": { + "type": "integer" + }, + "deletedId": { + "type": "integer" + }, + "endAt": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "point": { + "type": "integer" + }, + "startAt": { + "type": "string" + }, + "status": { + "type": "string" + }, + "title": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + }, + "schema.QuestionnaireForm": { + "type": "object", + "properties": { + "areaId": { + "type": "integer" + }, + "content": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.QuestionnaireTable" + } + }, + "cover": { + "type": "string" + }, + "endAt": { + "type": "string" + }, + "point": { + "type": "integer" + }, + "startAt": { + "type": "string" + }, + "status": { + "type": "string" + }, + "title": { + "type": "string" + } + } + }, + "schema.QuestionnaireTable": { + "type": "object", + "properties": { + "label": { + "type": "string" + }, + "options": { + "type": "array", + "items": { + "type": "string" + } + }, + "required": { + "type": "boolean" + }, + "type": { + "type": "string" + } + } + }, + "schema.ReceptionCenter": { + "type": "object", + "properties": { + "areaId": { + "type": "integer" + }, + "createdAt": { + "type": "string" + }, + "createdId": { + "type": "integer" + }, + "deletedAt": { + "type": "integer" + }, + "deletedId": { + "type": "integer" + }, + "id": { + "type": "integer" + }, + "introduce": { + "type": "string" + }, + "name": { + "type": "string" + }, + "status": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + }, + "schema.ReceptionCenterForm": { + "type": "object", + "properties": { + "areaId": { + "type": "integer" + }, + "introduce": { + "type": "string" + }, + "name": { + "type": "string" + }, + "status": { + "type": "string" + } + } + }, + "schema.Role": { + "type": "object", + "properties": { + "code": { + "description": "Code of role (unique)", + "type": "string" + }, + "created_at": { + "description": "Create time", + "type": "string" + }, + "description": { + "description": "Details about role", + "type": "string" + }, + "id": { + "description": "Unique ID", + "type": "string" + }, + "menus": { + "description": "Role menu list", + "type": "array", + "items": { + "$ref": "#/definitions/schema.RoleMenu" + } + }, + "name": { + "description": "Display name of role", + "type": "string" + }, + "sequence": { + "description": "Sequence for sorting", + "type": "integer" + }, + "status": { + "description": "Status of role (disabled, enabled)", + "type": "string" + }, + "updated_at": { + "description": "Update time", + "type": "string" + } + } + }, + "schema.RoleForm": { + "type": "object", + "required": [ + "code", + "name", + "status" + ], + "properties": { + "code": { + "description": "Code of role (unique)", + "type": "string", + "maxLength": 32 + }, + "description": { + "description": "Details about role", + "type": "string" + }, + "menus": { + "description": "Role menu list", + "type": "array", + "items": { + "$ref": "#/definitions/schema.RoleMenu" + } + }, + "name": { + "description": "Display name of role", + "type": "string", + "maxLength": 128 + }, + "sequence": { + "description": "Sequence for sorting", + "type": "integer" + }, + "status": { + "description": "Status of role (enabled, disabled)", + "type": "string", + "enum": [ + "disabled", + "enabled" + ] + } + } + }, + "schema.RoleMenu": { + "type": "object", + "properties": { + "created_at": { + "description": "Create time", + "type": "string" + }, + "id": { + "description": "Unique ID", + "type": "string" + }, + "menu_id": { + "description": "From Menu.ID", + "type": "string" + }, + "role_id": { + "description": "From Role.ID", + "type": "string" + }, + "updated_at": { + "description": "Update time", + "type": "string" + } + } + }, + "schema.UpdateCurrentUser": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "email": { + "description": "Email of user", + "type": "string", + "maxLength": 128 + }, + "name": { + "description": "Name of user", + "type": "string", + "maxLength": 64 + }, + "phone": { + "description": "Phone number of user", + "type": "string", + "maxLength": 32 + }, + "remark": { + "description": "Remark of user", + "type": "string", + "maxLength": 1024 + } + } + }, + "schema.UpdateLoginPassword": { + "type": "object", + "required": [ + "new_password", + "old_password" + ], + "properties": { + "new_password": { + "description": "New password (md5 hash)", + "type": "string" + }, + "old_password": { + "description": "Old password (md5 hash)", + "type": "string" + } + } + }, + "schema.User": { + "type": "object", + "properties": { + "companyId": { + "description": "Remark of user", + "type": "integer" + }, + "created_at": { + "description": "Create time", + "type": "string" + }, + "email": { + "type": "string" + }, + "id": { + "description": "Unique ID", + "type": "string" + }, + "name": { + "description": "Name of user", + "type": "string" + }, + "phone": { + "description": "Phone number of user", + "type": "string" + }, + "remark": { + "description": "Remark of user", + "type": "string" + }, + "roles": { + "description": "Roles of user", + "type": "array", + "items": { + "$ref": "#/definitions/schema.UserRole" + } + }, + "status": { + "description": "Status of user (activated, freezed)", + "type": "string" + }, + "updated_at": { + "description": "Update time", + "type": "string" + }, + "username": { + "description": "Username for login", + "type": "string" + } + } + }, + "schema.UserForm": { + "type": "object", + "required": [ + "name", + "roles", + "status", + "username" + ], + "properties": { + "email": { + "description": "Email of user", + "type": "string", + "maxLength": 128 + }, + "name": { + "description": "Name of user", + "type": "string", + "maxLength": 64 + }, + "password": { + "description": "Password for login (md5 hash)", + "type": "string", + "maxLength": 64 + }, + "phone": { + "description": "Phone number of user", + "type": "string", + "maxLength": 32 + }, + "remark": { + "description": "Remark of user", + "type": "string", + "maxLength": 1024 + }, + "roles": { + "description": "Roles of user", + "type": "array", + "items": { + "$ref": "#/definitions/schema.UserRole" + } + }, + "status": { + "description": "Status of user (activated, freezed)", + "type": "string", + "enum": [ + "activated", + "freezed" + ] + }, + "username": { + "description": "Username for login", + "type": "string", + "maxLength": 64 + } + } + }, + "schema.UserRole": { + "type": "object", + "properties": { + "created_at": { + "description": "Create time", + "type": "string" + }, + "id": { + "description": "Unique ID", + "type": "string" + }, + "role_id": { + "description": "From Role.ID", + "type": "string" + }, + "role_name": { + "description": "From Role.Name", + "type": "string" + }, + "updated_at": { + "description": "Update time", + "type": "string" + }, + "user_id": { + "description": "From User.ID", + "type": "string" + } + } + }, + "util.ResponseResult": { + "type": "object", + "properties": { + "data": {}, + "error": { + "$ref": "#/definitions/errors.Error" + }, + "success": { + "type": "boolean" + }, + "total": { + "type": "integer" + } + } + } + }, + "securityDefinitions": { + "ApiKeyAuth": { + "type": "apiKey", + "name": "Authorization", + "in": "header" + } + } +}` + +// SwaggerInfo holds exported Swagger Info so clients can modify it +var SwaggerInfo = &swag.Spec{ + Version: "v1.0.0", + Host: "", + BasePath: "", + Schemes: []string{}, + Title: "haibei", + Description: "中海小程序", + InfoInstanceName: "swagger", + SwaggerTemplate: docTemplate, + LeftDelim: "{{", + RightDelim: "}}", +} + +func init() { + swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo) +} diff --git a/internal/swagger/swagger.json b/internal/swagger/swagger.json new file mode 100644 index 0000000..df1ca45 --- /dev/null +++ b/internal/swagger/swagger.json @@ -0,0 +1,8001 @@ +{ + "swagger": "2.0", + "info": { + "description": "中海小程序", + "title": "haibei", + "contact": {}, + "version": "v1.0.0" + }, + "paths": { + "/api/v1/activities": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "活动模块" + ], + "summary": "Query activity list", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "pagination index", + "name": "current", + "in": "query", + "required": true + }, + { + "type": "integer", + "default": 10, + "description": "pagination size", + "name": "pageSize", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.Activity" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "活动模块" + ], + "summary": "Create activity record", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.ActivityForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.Activity" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/activities/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "活动模块" + ], + "summary": "Get activity record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.Activity" + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "活动模块" + ], + "summary": "Update activity record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.ActivityForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "活动模块" + ], + "summary": "Delete activity record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/activity-details": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "ActivityDetailAPI" + ], + "summary": "Query activity detail list", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "pagination index", + "name": "current", + "in": "query", + "required": true + }, + { + "type": "integer", + "default": 10, + "description": "pagination size", + "name": "pageSize", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.ActivityDetail" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "ActivityDetailAPI" + ], + "summary": "Create activity detail record", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.ActivityDetailForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.ActivityDetail" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/activity-details/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "ActivityDetailAPI" + ], + "summary": "Get activity detail record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.ActivityDetail" + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "ActivityDetailAPI" + ], + "summary": "Update activity detail record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.ActivityDetailForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "ActivityDetailAPI" + ], + "summary": "Delete activity detail record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/apps/banner": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "APP端" + ], + "summary": "首页banner", + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.Banner" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/apps/bind": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "APP端" + ], + "summary": "小程序绑定手机号", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/apps/login": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "APP端" + ], + "summary": "小程序登陆", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/apps/sms/{phone}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "APP端" + ], + "summary": "发送验证码", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/areas": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "区域模块" + ], + "summary": "Query area list", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "pagination index", + "name": "current", + "in": "query", + "required": true + }, + { + "type": "integer", + "default": 10, + "description": "pagination size", + "name": "pageSize", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.Area" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "区域模块" + ], + "summary": "Create area record", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.AreaForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.Area" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/areas/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "区域模块" + ], + "summary": "Get area record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.Area" + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "区域模块" + ], + "summary": "Update area record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.AreaForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "区域模块" + ], + "summary": "Delete area record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/balances": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "积分模块" + ], + "summary": "Query balance list", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "pagination index", + "name": "current", + "in": "query", + "required": true + }, + { + "type": "integer", + "default": 10, + "description": "pagination size", + "name": "pageSize", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.Balance" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "积分模块" + ], + "summary": "Create balance record", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.BalanceForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.Balance" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/balances/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "积分模块" + ], + "summary": "Get balance record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.Balance" + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "积分模块" + ], + "summary": "Update balance record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.BalanceForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "积分模块" + ], + "summary": "Delete balance record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/banners": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "首页轮播图模块" + ], + "summary": "Query banner list", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "pagination index", + "name": "current", + "in": "query", + "required": true + }, + { + "type": "integer", + "default": 10, + "description": "pagination size", + "name": "pageSize", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.Banner" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "首页轮播图模块" + ], + "summary": "Create banner record", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.BannerForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.Banner" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/banners/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "首页轮播图模块" + ], + "summary": "Get banner record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.Banner" + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "首页轮播图模块" + ], + "summary": "Update banner record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.BannerForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "首页轮播图模块" + ], + "summary": "Delete banner record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/captcha/id": { + "get": { + "tags": [ + "LoginAPI" + ], + "summary": "Get captcha ID", + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.Captcha" + } + } + } + ] + } + } + } + } + }, + "/api/v1/captcha/image": { + "get": { + "produces": [ + "image/png" + ], + "tags": [ + "LoginAPI" + ], + "summary": "Response captcha image", + "parameters": [ + { + "type": "string", + "description": "Captcha ID", + "name": "id", + "in": "query", + "required": true + }, + { + "type": "number", + "description": "Reload captcha image (reload=1)", + "name": "reload", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Captcha image" + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/commons": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "CommonAPI" + ], + "summary": "Query common list", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "pagination index", + "name": "current", + "in": "query", + "required": true + }, + { + "type": "integer", + "default": 10, + "description": "pagination size", + "name": "pageSize", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.Common" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "CommonAPI" + ], + "summary": "Create common record", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.CommonForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.Common" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/commons/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "CommonAPI" + ], + "summary": "Get common record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.Common" + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "CommonAPI" + ], + "summary": "Update common record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.CommonForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "CommonAPI" + ], + "summary": "Delete common record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/companies": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "项目模块" + ], + "summary": "Query company list", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "pagination index", + "name": "current", + "in": "query", + "required": true + }, + { + "type": "integer", + "default": 10, + "description": "pagination size", + "name": "pageSize", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.Company" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "项目模块" + ], + "summary": "Create company record", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.CompanyForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.Company" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/companies/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "项目模块" + ], + "summary": "Get company record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.Company" + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "项目模块" + ], + "summary": "Update company record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.CompanyForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "项目模块" + ], + "summary": "Delete company record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/current/logout": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "LoginAPI" + ], + "summary": "Logout system", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/current/menus": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "LoginAPI" + ], + "summary": "Query current user menus based on the current user role", + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.Menu" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/current/password": { + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "LoginAPI" + ], + "summary": "Change current user password", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.UpdateLoginPassword" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/current/refresh-token": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "LoginAPI" + ], + "summary": "Refresh current access token", + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.LoginToken" + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/current/user": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "LoginAPI" + ], + "summary": "Get current user info", + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.User" + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "LoginAPI" + ], + "summary": "Update current user info", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.UpdateCurrentUser" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/customers": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "客户模块" + ], + "summary": "Query customer list", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "pagination index", + "name": "current", + "in": "query", + "required": true + }, + { + "type": "integer", + "default": 10, + "description": "pagination size", + "name": "pageSize", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.Customer" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "客户模块" + ], + "summary": "Create customer record", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.CustomerForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.Customer" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/customers/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "客户模块" + ], + "summary": "Get customer record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.Customer" + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "客户模块" + ], + "summary": "Update customer record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.CustomerForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "客户模块" + ], + "summary": "Delete customer record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/grades": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "权益模块" + ], + "summary": "Query grade list", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "pagination index", + "name": "current", + "in": "query", + "required": true + }, + { + "type": "integer", + "default": 10, + "description": "pagination size", + "name": "pageSize", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.Grade" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "权益模块" + ], + "summary": "Create grade record", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.GradeForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.Grade" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/grades/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "权益模块" + ], + "summary": "Get grade record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.Grade" + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "权益模块" + ], + "summary": "Update grade record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.GradeForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "权益模块" + ], + "summary": "Delete grade record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/loggers": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "LoggerAPI" + ], + "summary": "Query logger list", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "pagination index", + "name": "current", + "in": "query", + "required": true + }, + { + "type": "integer", + "default": 10, + "description": "pagination size", + "name": "pageSize", + "in": "query", + "required": true + }, + { + "type": "string", + "description": "log level", + "name": "level", + "in": "query" + }, + { + "type": "string", + "description": "trace ID", + "name": "traceID", + "in": "query" + }, + { + "type": "string", + "description": "user name", + "name": "userName", + "in": "query" + }, + { + "type": "string", + "description": "log tag", + "name": "tag", + "in": "query" + }, + { + "type": "string", + "description": "log message", + "name": "message", + "in": "query" + }, + { + "type": "string", + "description": "start time", + "name": "startTime", + "in": "query" + }, + { + "type": "string", + "description": "end time", + "name": "endTime", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.Logger" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/login": { + "post": { + "tags": [ + "LoginAPI" + ], + "summary": "Login system with username and password", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.LoginForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.LoginToken" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/menus": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "MenuAPI" + ], + "summary": "Query menu tree data", + "parameters": [ + { + "type": "string", + "description": "Code path of menu (like xxx.xxx.xxx)", + "name": "code", + "in": "query" + }, + { + "type": "string", + "description": "Name of menu", + "name": "name", + "in": "query" + }, + { + "type": "boolean", + "description": "Whether to include menu resources", + "name": "includeResources", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.Menu" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "MenuAPI" + ], + "summary": "Create menu record", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.MenuForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.Menu" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/menus/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "MenuAPI" + ], + "summary": "Get menu record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.Menu" + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "MenuAPI" + ], + "summary": "Update menu record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.MenuForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "MenuAPI" + ], + "summary": "Delete menu record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/notices": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "公告模块" + ], + "summary": "Query notice list", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "pagination index", + "name": "current", + "in": "query", + "required": true + }, + { + "type": "integer", + "default": 10, + "description": "pagination size", + "name": "pageSize", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.Notice" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "公告模块" + ], + "summary": "Create notice record", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.NoticeForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.Notice" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/notices/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "公告模块" + ], + "summary": "Get notice record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.Notice" + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "公告模块" + ], + "summary": "Update notice record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.NoticeForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "公告模块" + ], + "summary": "Delete notice record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/points": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "PointAPI" + ], + "summary": "Query point list", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "pagination index", + "name": "current", + "in": "query", + "required": true + }, + { + "type": "integer", + "default": 10, + "description": "pagination size", + "name": "pageSize", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.Point" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "PointAPI" + ], + "summary": "Create point record", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.PointForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.Point" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/points/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "PointAPI" + ], + "summary": "Get point record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.Point" + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "PointAPI" + ], + "summary": "Update point record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.PointForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "PointAPI" + ], + "summary": "Delete point record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/product-categories": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "权益产品分类模块" + ], + "summary": "Query product category list", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "pagination index", + "name": "current", + "in": "query", + "required": true + }, + { + "type": "integer", + "default": 10, + "description": "pagination size", + "name": "pageSize", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.ProductCategory" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "权益产品分类模块" + ], + "summary": "Create product category record", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.ProductCategoryForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.ProductCategory" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/product-categories/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "权益产品分类模块" + ], + "summary": "Get product category record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.ProductCategory" + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "权益产品分类模块" + ], + "summary": "Update product category record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.ProductCategoryForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "权益产品分类模块" + ], + "summary": "Delete product category record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/product-orders": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "ProductOrderAPI" + ], + "summary": "Query product order list", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "pagination index", + "name": "current", + "in": "query", + "required": true + }, + { + "type": "integer", + "default": 10, + "description": "pagination size", + "name": "pageSize", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.ProductOrder" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "ProductOrderAPI" + ], + "summary": "Create product order record", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.ProductOrderForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.ProductOrder" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/product-orders/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "ProductOrderAPI" + ], + "summary": "Get product order record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.ProductOrder" + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "ProductOrderAPI" + ], + "summary": "Update product order record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.ProductOrderForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "ProductOrderAPI" + ], + "summary": "Delete product order record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/products": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "权益产品模块" + ], + "summary": "Query product list", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "pagination index", + "name": "current", + "in": "query", + "required": true + }, + { + "type": "integer", + "default": 10, + "description": "pagination size", + "name": "pageSize", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.Product" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "权益产品模块" + ], + "summary": "Create product record", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.ProductForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.Product" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/products/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "权益产品模块" + ], + "summary": "Get product record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.Product" + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "权益产品模块" + ], + "summary": "Update product record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.ProductForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "权益产品模块" + ], + "summary": "Delete product record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/qeustion-details": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "QeustionDetailAPI" + ], + "summary": "Query qeustion detail list", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "pagination index", + "name": "current", + "in": "query", + "required": true + }, + { + "type": "integer", + "default": 10, + "description": "pagination size", + "name": "pageSize", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.QeustionDetail" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "QeustionDetailAPI" + ], + "summary": "Create qeustion detail record", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.QeustionDetailForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.QeustionDetail" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/qeustion-details/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "QeustionDetailAPI" + ], + "summary": "Get qeustion detail record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.QeustionDetail" + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "QeustionDetailAPI" + ], + "summary": "Update qeustion detail record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.QeustionDetailForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "QeustionDetailAPI" + ], + "summary": "Delete qeustion detail record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/questionnaires": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "问卷模块" + ], + "summary": "Query questionnaire list", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "pagination index", + "name": "current", + "in": "query", + "required": true + }, + { + "type": "integer", + "default": 10, + "description": "pagination size", + "name": "pageSize", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.Questionnaire" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "问卷模块" + ], + "summary": "Create questionnaire record", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.QuestionnaireForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.Questionnaire" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/questionnaires/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "问卷模块" + ], + "summary": "Get questionnaire record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.Questionnaire" + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "问卷模块" + ], + "summary": "Update questionnaire record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.QuestionnaireForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "问卷模块" + ], + "summary": "Delete questionnaire record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/reception_center": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "楼盘模块" + ], + "summary": "Query ReceptionCenter list", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "pagination index", + "name": "current", + "in": "query", + "required": true + }, + { + "type": "integer", + "default": 10, + "description": "pagination size", + "name": "pageSize", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.ReceptionCenter" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "楼盘模块" + ], + "summary": "check QrCode", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/api.CheckQrCodeStruct" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/reception_center/qr_code/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "楼盘模块" + ], + "summary": "Get ReceptionCenter QrCode by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "integer" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/reception_center/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "楼盘模块" + ], + "summary": "Get ReceptionCenter record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.ReceptionCenter" + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "楼盘模块" + ], + "summary": "Update ReceptionCenter record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.ReceptionCenterForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "楼盘模块" + ], + "summary": "Delete ReceptionCenter record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/roles": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "RoleAPI" + ], + "summary": "Query role list", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "pagination index", + "name": "current", + "in": "query", + "required": true + }, + { + "type": "integer", + "default": 10, + "description": "pagination size", + "name": "pageSize", + "in": "query", + "required": true + }, + { + "type": "string", + "description": "Display name of role", + "name": "name", + "in": "query" + }, + { + "type": "string", + "description": "Status of role (disabled, enabled)", + "name": "status", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.Role" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "RoleAPI" + ], + "summary": "Create role record", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.RoleForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.Role" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/roles/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "RoleAPI" + ], + "summary": "Get role record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.Role" + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "RoleAPI" + ], + "summary": "Update role record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.RoleForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "RoleAPI" + ], + "summary": "Delete role record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/users": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "UserAPI" + ], + "summary": "Query user list", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "pagination index", + "name": "current", + "in": "query", + "required": true + }, + { + "type": "integer", + "default": 10, + "description": "pagination size", + "name": "pageSize", + "in": "query", + "required": true + }, + { + "type": "string", + "description": "Username for login", + "name": "username", + "in": "query" + }, + { + "type": "string", + "description": "Name of user", + "name": "name", + "in": "query" + }, + { + "type": "string", + "description": "Status of user (activated, freezed)", + "name": "status", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.User" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "UserAPI" + ], + "summary": "Create user record", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.UserForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.User" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/users/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "UserAPI" + ], + "summary": "Get user record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.User" + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "UserAPI" + ], + "summary": "Update user record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.UserForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "UserAPI" + ], + "summary": "Delete user record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/users/{id}/reset-pwd": { + "patch": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "UserAPI" + ], + "summary": "Reset user password by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + } + }, + "definitions": { + "api.CheckQrCodeStruct": { + "type": "object", + "properties": { + "customer_id": { + "type": "integer" + }, + "reception_center_id": { + "type": "integer" + } + } + }, + "errors.Error": { + "type": "object", + "properties": { + "code": { + "type": "integer" + }, + "detail": { + "type": "string" + }, + "id": { + "type": "string" + }, + "status": { + "type": "string" + } + } + }, + "schema.Activity": { + "type": "object", + "properties": { + "address": { + "type": "string" + }, + "areaId": { + "type": "integer" + }, + "content": { + "type": "string" + }, + "cover": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "createdId": { + "type": "integer" + }, + "deletedAt": { + "type": "integer" + }, + "deletedId": { + "type": "integer" + }, + "endAt": { + "type": "string" + }, + "endSignupAt": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "images": { + "type": "array", + "items": { + "type": "string" + } + }, + "maxSignupNum": { + "type": "integer" + }, + "point": { + "type": "integer" + }, + "signupNum": { + "type": "integer" + }, + "startAt": { + "type": "string" + }, + "status": { + "type": "string" + }, + "title": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + }, + "schema.ActivityDetail": { + "type": "object", + "properties": { + "activityId": { + "type": "integer" + }, + "createdAt": { + "type": "string" + }, + "createdId": { + "type": "integer" + }, + "customerId": { + "type": "integer" + }, + "deletedAt": { + "type": "integer" + }, + "deletedId": { + "type": "integer" + }, + "id": { + "type": "integer" + }, + "status": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + }, + "schema.ActivityDetailForm": { + "type": "object", + "properties": { + "activityId": { + "type": "integer" + }, + "customerId": { + "type": "integer" + }, + "status": { + "type": "string" + } + } + }, + "schema.ActivityForm": { + "type": "object", + "properties": { + "address": { + "type": "string" + }, + "areaId": { + "type": "integer" + }, + "content": { + "type": "string" + }, + "cover": { + "type": "string" + }, + "endAt": { + "type": "string" + }, + "endSignupAt": { + "type": "string" + }, + "images": { + "type": "array", + "items": { + "type": "string" + } + }, + "maxSignupNum": { + "type": "integer" + }, + "point": { + "type": "integer" + }, + "signupNum": { + "type": "integer" + }, + "startAt": { + "type": "string" + }, + "status": { + "type": "string" + }, + "title": { + "type": "string" + } + } + }, + "schema.Area": { + "type": "object", + "properties": { + "createdAt": { + "type": "string" + }, + "createdId": { + "type": "integer" + }, + "deletedAt": { + "type": "integer" + }, + "deletedId": { + "type": "integer" + }, + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "status": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + }, + "schema.AreaForm": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "status": { + "type": "string" + } + } + }, + "schema.Balance": { + "type": "object", + "properties": { + "after": { + "type": "integer" + }, + "before": { + "type": "integer" + }, + "change": { + "type": "integer" + }, + "createdAt": { + "type": "string" + }, + "createdId": { + "type": "integer" + }, + "creatorId": { + "type": "string" + }, + "customerId": { + "type": "integer" + }, + "deletedAt": { + "type": "integer" + }, + "deletedId": { + "type": "integer" + }, + "id": { + "type": "integer" + }, + "operatorId": { + "type": "integer" + }, + "reason": { + "type": "string" + }, + "type": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + }, + "schema.BalanceForm": { + "type": "object", + "properties": { + "after": { + "type": "integer" + }, + "before": { + "type": "integer" + }, + "change": { + "type": "integer" + }, + "creatorId": { + "type": "string" + }, + "customerId": { + "type": "integer" + }, + "operatorId": { + "type": "integer" + }, + "reason": { + "type": "string" + }, + "type": { + "type": "string" + } + } + }, + "schema.Banner": { + "type": "object", + "properties": { + "areaId": { + "type": "integer" + }, + "createdAt": { + "type": "string" + }, + "createdId": { + "type": "integer" + }, + "deletedAt": { + "type": "integer" + }, + "deletedId": { + "type": "integer" + }, + "id": { + "type": "integer" + }, + "img": { + "type": "string" + }, + "link": { + "type": "string" + }, + "name": { + "type": "string" + }, + "sequence": { + "type": "integer" + }, + "status": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + }, + "schema.BannerForm": { + "type": "object", + "properties": { + "areaId": { + "type": "integer" + }, + "img": { + "type": "string" + }, + "link": { + "type": "string" + }, + "name": { + "type": "string" + }, + "sequence": { + "type": "integer" + }, + "status": { + "type": "string" + } + } + }, + "schema.Captcha": { + "type": "object", + "properties": { + "captcha_id": { + "description": "Captcha ID", + "type": "string" + } + } + }, + "schema.Common": { + "type": "object", + "properties": { + "created_at": { + "description": "Create time", + "type": "string" + }, + "id": { + "description": "Unique ID", + "type": "string" + }, + "updated_at": { + "description": "Update time", + "type": "string" + } + } + }, + "schema.CommonForm": { + "type": "object" + }, + "schema.Company": { + "type": "object", + "properties": { + "areaId": { + "type": "integer" + }, + "createdAt": { + "type": "string" + }, + "createdId": { + "type": "integer" + }, + "deletedAt": { + "type": "integer" + }, + "deletedId": { + "type": "integer" + }, + "id": { + "type": "integer" + }, + "img": { + "type": "string" + }, + "name": { + "type": "string" + }, + "status": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + }, + "schema.CompanyForm": { + "type": "object", + "properties": { + "areaId": { + "type": "integer" + }, + "img": { + "type": "string" + }, + "name": { + "type": "string" + }, + "status": { + "type": "string" + } + } + }, + "schema.Customer": { + "type": "object", + "properties": { + "areaId": { + "type": "integer" + }, + "avatar": { + "type": "string" + }, + "balance": { + "type": "integer" + }, + "birthday": { + "type": "string" + }, + "companyId": { + "type": "integer" + }, + "createdAt": { + "type": "string" + }, + "createdId": { + "type": "integer" + }, + "deletedAt": { + "type": "integer" + }, + "deletedId": { + "type": "integer" + }, + "grade": { + "type": "integer" + }, + "id": { + "type": "integer" + }, + "inviterId": { + "type": "integer" + }, + "mentorId": { + "type": "string" + }, + "name": { + "type": "string" + }, + "phone": { + "type": "string" + }, + "status": { + "type": "string" + }, + "updatedAt": { + "type": "string" + }, + "wxSign": { + "type": "string" + } + } + }, + "schema.CustomerForm": { + "type": "object", + "properties": { + "areaId": { + "type": "integer" + }, + "avatar": { + "type": "string" + }, + "balance": { + "type": "integer" + }, + "birthday": { + "type": "string" + }, + "companyId": { + "type": "integer" + }, + "grade": { + "type": "integer" + }, + "inviterId": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "phone": { + "type": "string" + }, + "status": { + "type": "string" + }, + "wxSign": { + "type": "string" + } + } + }, + "schema.Grade": { + "type": "object", + "properties": { + "createdAt": { + "type": "string" + }, + "createdId": { + "type": "integer" + }, + "deletedAt": { + "type": "integer" + }, + "deletedId": { + "type": "integer" + }, + "icon": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "introduce": { + "type": "string" + }, + "lv": { + "type": "string" + }, + "name": { + "type": "string" + }, + "score": { + "type": "integer" + }, + "status": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + }, + "schema.GradeForm": { + "type": "object", + "properties": { + "icon": { + "type": "string" + }, + "introduce": { + "type": "string" + }, + "lv": { + "type": "string" + }, + "name": { + "type": "string" + }, + "score": { + "type": "integer" + }, + "status": { + "type": "string" + } + } + }, + "schema.Logger": { + "type": "object", + "properties": { + "created_at": { + "description": "Create time", + "type": "string" + }, + "data": { + "description": "Log data", + "type": "string" + }, + "id": { + "description": "Unique ID", + "type": "string" + }, + "level": { + "description": "Log level", + "type": "string" + }, + "login_name": { + "description": "From User.Username", + "type": "string" + }, + "message": { + "description": "Log message", + "type": "string" + }, + "stack": { + "description": "Error stack", + "type": "string" + }, + "tag": { + "description": "Log tag", + "type": "string" + }, + "trace_id": { + "description": "Trace ID", + "type": "string" + }, + "user_id": { + "description": "User ID", + "type": "string" + }, + "user_name": { + "description": "From User.Name", + "type": "string" + } + } + }, + "schema.LoginForm": { + "type": "object", + "required": [ + "captcha_code", + "captcha_id", + "password", + "username" + ], + "properties": { + "captcha_code": { + "description": "Captcha verify code", + "type": "string" + }, + "captcha_id": { + "description": "Captcha verify id", + "type": "string" + }, + "password": { + "description": "Login password (md5 hash)", + "type": "string" + }, + "username": { + "description": "Login name", + "type": "string" + } + } + }, + "schema.LoginToken": { + "type": "object", + "properties": { + "access_token": { + "description": "Access token (JWT)", + "type": "string" + }, + "expires_at": { + "description": "Expired time (Unit: second)", + "type": "integer" + }, + "token_type": { + "description": "Token type (Usage: Authorization=${token_type} ${access_token})", + "type": "string" + } + } + }, + "schema.Menu": { + "type": "object", + "properties": { + "children": { + "description": "Child menus", + "type": "array", + "items": { + "$ref": "#/definitions/schema.Menu" + } + }, + "code": { + "description": "Code of menu (unique for each level)", + "type": "string" + }, + "created_at": { + "description": "Create time", + "type": "string" + }, + "description": { + "description": "Details about menu", + "type": "string" + }, + "id": { + "description": "Unique ID", + "type": "string" + }, + "name": { + "description": "Display name of menu", + "type": "string" + }, + "parent_id": { + "description": "Parent ID (From Menu.ID)", + "type": "string" + }, + "parent_path": { + "description": "Parent path (split by .)", + "type": "string" + }, + "path": { + "description": "Access path of menu", + "type": "string" + }, + "properties": { + "description": "Properties of menu (JSON)", + "type": "string" + }, + "resources": { + "description": "Resources of menu", + "type": "array", + "items": { + "$ref": "#/definitions/schema.MenuResource" + } + }, + "sequence": { + "description": "Sequence for sorting (Order by desc)", + "type": "integer" + }, + "status": { + "description": "Status of menu (enabled, disabled)", + "type": "string" + }, + "type": { + "description": "Type of menu (page, button)", + "type": "string" + }, + "updated_at": { + "description": "Update time", + "type": "string" + } + } + }, + "schema.MenuForm": { + "type": "object", + "required": [ + "code", + "name", + "status", + "type" + ], + "properties": { + "code": { + "description": "Code of menu (unique for each level)", + "type": "string", + "maxLength": 32 + }, + "description": { + "description": "Details about menu", + "type": "string" + }, + "name": { + "description": "Display name of menu", + "type": "string", + "maxLength": 128 + }, + "parent_id": { + "description": "Parent ID (From Menu.ID)", + "type": "string" + }, + "path": { + "description": "Access path of menu", + "type": "string" + }, + "properties": { + "description": "Properties of menu (JSON)", + "type": "string" + }, + "resources": { + "description": "Resources of menu", + "type": "array", + "items": { + "$ref": "#/definitions/schema.MenuResource" + } + }, + "sequence": { + "description": "Sequence for sorting (Order by desc)", + "type": "integer" + }, + "status": { + "description": "Status of menu (enabled, disabled)", + "type": "string", + "enum": [ + "disabled", + "enabled" + ] + }, + "type": { + "description": "Type of menu (page, button)", + "type": "string", + "enum": [ + "page", + "button" + ] + } + } + }, + "schema.MenuResource": { + "type": "object", + "properties": { + "created_at": { + "description": "Create time", + "type": "string" + }, + "id": { + "description": "Unique ID", + "type": "string" + }, + "menu_id": { + "description": "From Menu.ID", + "type": "string" + }, + "method": { + "description": "HTTP method", + "type": "string" + }, + "path": { + "description": "API request path (e.g. /api/v1/users/:id)", + "type": "string" + }, + "updated_at": { + "description": "Update time", + "type": "string" + } + } + }, + "schema.Notice": { + "type": "object", + "properties": { + "areaId": { + "type": "integer" + }, + "content": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "createdId": { + "type": "integer" + }, + "deletedAt": { + "type": "integer" + }, + "deletedId": { + "type": "integer" + }, + "id": { + "type": "integer" + }, + "sequence": { + "type": "integer" + }, + "status": { + "type": "string" + }, + "title": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + }, + "schema.NoticeForm": { + "type": "object", + "properties": { + "areaId": { + "type": "integer" + }, + "content": { + "type": "string" + }, + "sequence": { + "type": "integer" + }, + "status": { + "type": "string" + }, + "title": { + "type": "string" + } + } + }, + "schema.Point": { + "type": "object", + "properties": { + "created_at": { + "description": "Create time", + "type": "string" + }, + "id": { + "description": "Unique ID", + "type": "string" + }, + "updated_at": { + "description": "Update time", + "type": "string" + } + } + }, + "schema.PointForm": { + "type": "object" + }, + "schema.Product": { + "type": "object", + "properties": { + "areaId": { + "type": "integer" + }, + "businessImgs": { + "type": "array", + "items": { + "type": "string" + } + }, + "category": { + "description": "关联分类", + "allOf": [ + { + "$ref": "#/definitions/schema.ProductCategory" + } + ] + }, + "categoryId": { + "type": "integer" + }, + "cover": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "createdId": { + "type": "integer" + }, + "deletedAt": { + "type": "integer" + }, + "deletedId": { + "type": "integer" + }, + "details": { + "type": "string" + }, + "expireAt": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "instructions": { + "type": "string" + }, + "isHot": { + "type": "boolean" + }, + "maxNum": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "policy": { + "type": "string" + }, + "price": { + "type": "integer" + }, + "status": { + "type": "string" + }, + "stock": { + "type": "integer" + }, + "type": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + }, + "schema.ProductCategory": { + "type": "object", + "properties": { + "createdAt": { + "type": "string" + }, + "createdId": { + "type": "integer" + }, + "deletedAt": { + "type": "integer" + }, + "deletedId": { + "type": "integer" + }, + "icon": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "introduce": { + "type": "string" + }, + "name": { + "type": "string" + }, + "parentId": { + "type": "integer" + }, + "status": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + }, + "schema.ProductCategoryForm": { + "type": "object", + "properties": { + "icon": { + "type": "string" + }, + "introduce": { + "type": "string" + }, + "name": { + "type": "string" + }, + "parentId": { + "type": "integer" + }, + "status": { + "type": "string" + } + } + }, + "schema.ProductForm": { + "type": "object", + "properties": { + "areaId": { + "type": "integer" + }, + "businessImgs": { + "type": "array", + "items": { + "type": "string" + } + }, + "categoryId": { + "type": "integer" + }, + "cover": { + "type": "string" + }, + "details": { + "type": "string" + }, + "expireAt": { + "type": "string" + }, + "instructions": { + "type": "string" + }, + "isHot": { + "type": "boolean" + }, + "maxNum": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "policy": { + "type": "string" + }, + "price": { + "type": "integer" + }, + "status": { + "type": "string" + }, + "stock": { + "type": "integer" + }, + "type": { + "type": "string" + } + } + }, + "schema.ProductOrder": { + "type": "object", + "properties": { + "createdAt": { + "type": "string" + }, + "createdId": { + "type": "integer" + }, + "customerId": { + "type": "integer" + }, + "deletedAt": { + "type": "integer" + }, + "deletedId": { + "type": "integer" + }, + "id": { + "type": "integer" + }, + "mentorId": { + "type": "string" + }, + "productId": { + "type": "integer" + }, + "status": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + }, + "schema.ProductOrderForm": { + "type": "object", + "properties": { + "customerId": { + "type": "integer" + }, + "mentorId": { + "type": "string" + }, + "productId": { + "type": "integer" + }, + "status": { + "type": "string" + } + } + }, + "schema.QeustionDetail": { + "type": "object", + "properties": { + "content": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.QuestionDetailTable" + } + }, + "createdAt": { + "type": "string" + }, + "createdId": { + "type": "integer" + }, + "customerId": { + "type": "integer" + }, + "deletedAt": { + "type": "integer" + }, + "deletedId": { + "type": "integer" + }, + "id": { + "type": "integer" + }, + "questionId": { + "type": "integer" + }, + "status": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + }, + "schema.QeustionDetailForm": { + "type": "object", + "properties": { + "content": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.QuestionDetailTable" + } + }, + "customerId": { + "type": "integer" + }, + "questionId": { + "type": "integer" + }, + "status": { + "type": "string" + } + } + }, + "schema.QuestionDetailTable": { + "type": "object", + "properties": { + "answer": { + "type": "string" + }, + "label": { + "type": "string" + }, + "options": { + "type": "array", + "items": { + "type": "string" + } + }, + "required": { + "type": "boolean" + }, + "type": { + "type": "string" + } + } + }, + "schema.Questionnaire": { + "type": "object", + "properties": { + "areaId": { + "type": "integer" + }, + "content": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.QuestionnaireTable" + } + }, + "cover": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "createdId": { + "type": "integer" + }, + "deletedAt": { + "type": "integer" + }, + "deletedId": { + "type": "integer" + }, + "endAt": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "point": { + "type": "integer" + }, + "startAt": { + "type": "string" + }, + "status": { + "type": "string" + }, + "title": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + }, + "schema.QuestionnaireForm": { + "type": "object", + "properties": { + "areaId": { + "type": "integer" + }, + "content": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.QuestionnaireTable" + } + }, + "cover": { + "type": "string" + }, + "endAt": { + "type": "string" + }, + "point": { + "type": "integer" + }, + "startAt": { + "type": "string" + }, + "status": { + "type": "string" + }, + "title": { + "type": "string" + } + } + }, + "schema.QuestionnaireTable": { + "type": "object", + "properties": { + "label": { + "type": "string" + }, + "options": { + "type": "array", + "items": { + "type": "string" + } + }, + "required": { + "type": "boolean" + }, + "type": { + "type": "string" + } + } + }, + "schema.ReceptionCenter": { + "type": "object", + "properties": { + "areaId": { + "type": "integer" + }, + "createdAt": { + "type": "string" + }, + "createdId": { + "type": "integer" + }, + "deletedAt": { + "type": "integer" + }, + "deletedId": { + "type": "integer" + }, + "id": { + "type": "integer" + }, + "introduce": { + "type": "string" + }, + "name": { + "type": "string" + }, + "status": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + }, + "schema.ReceptionCenterForm": { + "type": "object", + "properties": { + "areaId": { + "type": "integer" + }, + "introduce": { + "type": "string" + }, + "name": { + "type": "string" + }, + "status": { + "type": "string" + } + } + }, + "schema.Role": { + "type": "object", + "properties": { + "code": { + "description": "Code of role (unique)", + "type": "string" + }, + "created_at": { + "description": "Create time", + "type": "string" + }, + "description": { + "description": "Details about role", + "type": "string" + }, + "id": { + "description": "Unique ID", + "type": "string" + }, + "menus": { + "description": "Role menu list", + "type": "array", + "items": { + "$ref": "#/definitions/schema.RoleMenu" + } + }, + "name": { + "description": "Display name of role", + "type": "string" + }, + "sequence": { + "description": "Sequence for sorting", + "type": "integer" + }, + "status": { + "description": "Status of role (disabled, enabled)", + "type": "string" + }, + "updated_at": { + "description": "Update time", + "type": "string" + } + } + }, + "schema.RoleForm": { + "type": "object", + "required": [ + "code", + "name", + "status" + ], + "properties": { + "code": { + "description": "Code of role (unique)", + "type": "string", + "maxLength": 32 + }, + "description": { + "description": "Details about role", + "type": "string" + }, + "menus": { + "description": "Role menu list", + "type": "array", + "items": { + "$ref": "#/definitions/schema.RoleMenu" + } + }, + "name": { + "description": "Display name of role", + "type": "string", + "maxLength": 128 + }, + "sequence": { + "description": "Sequence for sorting", + "type": "integer" + }, + "status": { + "description": "Status of role (enabled, disabled)", + "type": "string", + "enum": [ + "disabled", + "enabled" + ] + } + } + }, + "schema.RoleMenu": { + "type": "object", + "properties": { + "created_at": { + "description": "Create time", + "type": "string" + }, + "id": { + "description": "Unique ID", + "type": "string" + }, + "menu_id": { + "description": "From Menu.ID", + "type": "string" + }, + "role_id": { + "description": "From Role.ID", + "type": "string" + }, + "updated_at": { + "description": "Update time", + "type": "string" + } + } + }, + "schema.UpdateCurrentUser": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "email": { + "description": "Email of user", + "type": "string", + "maxLength": 128 + }, + "name": { + "description": "Name of user", + "type": "string", + "maxLength": 64 + }, + "phone": { + "description": "Phone number of user", + "type": "string", + "maxLength": 32 + }, + "remark": { + "description": "Remark of user", + "type": "string", + "maxLength": 1024 + } + } + }, + "schema.UpdateLoginPassword": { + "type": "object", + "required": [ + "new_password", + "old_password" + ], + "properties": { + "new_password": { + "description": "New password (md5 hash)", + "type": "string" + }, + "old_password": { + "description": "Old password (md5 hash)", + "type": "string" + } + } + }, + "schema.User": { + "type": "object", + "properties": { + "companyId": { + "description": "Remark of user", + "type": "integer" + }, + "created_at": { + "description": "Create time", + "type": "string" + }, + "email": { + "type": "string" + }, + "id": { + "description": "Unique ID", + "type": "string" + }, + "name": { + "description": "Name of user", + "type": "string" + }, + "phone": { + "description": "Phone number of user", + "type": "string" + }, + "remark": { + "description": "Remark of user", + "type": "string" + }, + "roles": { + "description": "Roles of user", + "type": "array", + "items": { + "$ref": "#/definitions/schema.UserRole" + } + }, + "status": { + "description": "Status of user (activated, freezed)", + "type": "string" + }, + "updated_at": { + "description": "Update time", + "type": "string" + }, + "username": { + "description": "Username for login", + "type": "string" + } + } + }, + "schema.UserForm": { + "type": "object", + "required": [ + "name", + "roles", + "status", + "username" + ], + "properties": { + "email": { + "description": "Email of user", + "type": "string", + "maxLength": 128 + }, + "name": { + "description": "Name of user", + "type": "string", + "maxLength": 64 + }, + "password": { + "description": "Password for login (md5 hash)", + "type": "string", + "maxLength": 64 + }, + "phone": { + "description": "Phone number of user", + "type": "string", + "maxLength": 32 + }, + "remark": { + "description": "Remark of user", + "type": "string", + "maxLength": 1024 + }, + "roles": { + "description": "Roles of user", + "type": "array", + "items": { + "$ref": "#/definitions/schema.UserRole" + } + }, + "status": { + "description": "Status of user (activated, freezed)", + "type": "string", + "enum": [ + "activated", + "freezed" + ] + }, + "username": { + "description": "Username for login", + "type": "string", + "maxLength": 64 + } + } + }, + "schema.UserRole": { + "type": "object", + "properties": { + "created_at": { + "description": "Create time", + "type": "string" + }, + "id": { + "description": "Unique ID", + "type": "string" + }, + "role_id": { + "description": "From Role.ID", + "type": "string" + }, + "role_name": { + "description": "From Role.Name", + "type": "string" + }, + "updated_at": { + "description": "Update time", + "type": "string" + }, + "user_id": { + "description": "From User.ID", + "type": "string" + } + } + }, + "util.ResponseResult": { + "type": "object", + "properties": { + "data": {}, + "error": { + "$ref": "#/definitions/errors.Error" + }, + "success": { + "type": "boolean" + }, + "total": { + "type": "integer" + } + } + } + }, + "securityDefinitions": { + "ApiKeyAuth": { + "type": "apiKey", + "name": "Authorization", + "in": "header" + } + } +} \ No newline at end of file diff --git a/internal/swagger/swagger.yaml b/internal/swagger/swagger.yaml new file mode 100644 index 0000000..8474008 --- /dev/null +++ b/internal/swagger/swagger.yaml @@ -0,0 +1,4964 @@ +definitions: + api.CheckQrCodeStruct: + properties: + customer_id: + type: integer + reception_center_id: + type: integer + type: object + errors.Error: + properties: + code: + type: integer + detail: + type: string + id: + type: string + status: + type: string + type: object + schema.Activity: + properties: + address: + type: string + areaId: + type: integer + content: + type: string + cover: + type: string + createdAt: + type: string + createdId: + type: integer + deletedAt: + type: integer + deletedId: + type: integer + endAt: + type: string + endSignupAt: + type: string + id: + type: integer + images: + items: + type: string + type: array + maxSignupNum: + type: integer + point: + type: integer + signupNum: + type: integer + startAt: + type: string + status: + type: string + title: + type: string + updatedAt: + type: string + type: object + schema.ActivityDetail: + properties: + activityId: + type: integer + createdAt: + type: string + createdId: + type: integer + customerId: + type: integer + deletedAt: + type: integer + deletedId: + type: integer + id: + type: integer + status: + type: string + updatedAt: + type: string + type: object + schema.ActivityDetailForm: + properties: + activityId: + type: integer + customerId: + type: integer + status: + type: string + type: object + schema.ActivityForm: + properties: + address: + type: string + areaId: + type: integer + content: + type: string + cover: + type: string + endAt: + type: string + endSignupAt: + type: string + images: + items: + type: string + type: array + maxSignupNum: + type: integer + point: + type: integer + signupNum: + type: integer + startAt: + type: string + status: + type: string + title: + type: string + type: object + schema.Area: + properties: + createdAt: + type: string + createdId: + type: integer + deletedAt: + type: integer + deletedId: + type: integer + id: + type: integer + name: + type: string + status: + type: string + updatedAt: + type: string + type: object + schema.AreaForm: + properties: + name: + type: string + status: + type: string + type: object + schema.Balance: + properties: + after: + type: integer + before: + type: integer + change: + type: integer + createdAt: + type: string + createdId: + type: integer + creatorId: + type: string + customerId: + type: integer + deletedAt: + type: integer + deletedId: + type: integer + id: + type: integer + operatorId: + type: integer + reason: + type: string + type: + type: string + updatedAt: + type: string + type: object + schema.BalanceForm: + properties: + after: + type: integer + before: + type: integer + change: + type: integer + creatorId: + type: string + customerId: + type: integer + operatorId: + type: integer + reason: + type: string + type: + type: string + type: object + schema.Banner: + properties: + areaId: + type: integer + createdAt: + type: string + createdId: + type: integer + deletedAt: + type: integer + deletedId: + type: integer + id: + type: integer + img: + type: string + link: + type: string + name: + type: string + sequence: + type: integer + status: + type: string + updatedAt: + type: string + type: object + schema.BannerForm: + properties: + areaId: + type: integer + img: + type: string + link: + type: string + name: + type: string + sequence: + type: integer + status: + type: string + type: object + schema.Captcha: + properties: + captcha_id: + description: Captcha ID + type: string + type: object + schema.Common: + properties: + created_at: + description: Create time + type: string + id: + description: Unique ID + type: string + updated_at: + description: Update time + type: string + type: object + schema.CommonForm: + type: object + schema.Company: + properties: + areaId: + type: integer + createdAt: + type: string + createdId: + type: integer + deletedAt: + type: integer + deletedId: + type: integer + id: + type: integer + img: + type: string + name: + type: string + status: + type: string + updatedAt: + type: string + type: object + schema.CompanyForm: + properties: + areaId: + type: integer + img: + type: string + name: + type: string + status: + type: string + type: object + schema.Customer: + properties: + areaId: + type: integer + avatar: + type: string + balance: + type: integer + birthday: + type: string + companyId: + type: integer + createdAt: + type: string + createdId: + type: integer + deletedAt: + type: integer + deletedId: + type: integer + grade: + type: integer + id: + type: integer + inviterId: + type: integer + mentorId: + type: string + name: + type: string + phone: + type: string + status: + type: string + updatedAt: + type: string + wxSign: + type: string + type: object + schema.CustomerForm: + properties: + areaId: + type: integer + avatar: + type: string + balance: + type: integer + birthday: + type: string + companyId: + type: integer + grade: + type: integer + inviterId: + type: integer + name: + type: string + phone: + type: string + status: + type: string + wxSign: + type: string + type: object + schema.Grade: + properties: + createdAt: + type: string + createdId: + type: integer + deletedAt: + type: integer + deletedId: + type: integer + icon: + type: string + id: + type: integer + introduce: + type: string + lv: + type: string + name: + type: string + score: + type: integer + status: + type: string + updatedAt: + type: string + type: object + schema.GradeForm: + properties: + icon: + type: string + introduce: + type: string + lv: + type: string + name: + type: string + score: + type: integer + status: + type: string + type: object + schema.Logger: + properties: + created_at: + description: Create time + type: string + data: + description: Log data + type: string + id: + description: Unique ID + type: string + level: + description: Log level + type: string + login_name: + description: From User.Username + type: string + message: + description: Log message + type: string + stack: + description: Error stack + type: string + tag: + description: Log tag + type: string + trace_id: + description: Trace ID + type: string + user_id: + description: User ID + type: string + user_name: + description: From User.Name + type: string + type: object + schema.LoginForm: + properties: + captcha_code: + description: Captcha verify code + type: string + captcha_id: + description: Captcha verify id + type: string + password: + description: Login password (md5 hash) + type: string + username: + description: Login name + type: string + required: + - captcha_code + - captcha_id + - password + - username + type: object + schema.LoginToken: + properties: + access_token: + description: Access token (JWT) + type: string + expires_at: + description: 'Expired time (Unit: second)' + type: integer + token_type: + description: 'Token type (Usage: Authorization=${token_type} ${access_token})' + type: string + type: object + schema.Menu: + properties: + children: + description: Child menus + items: + $ref: '#/definitions/schema.Menu' + type: array + code: + description: Code of menu (unique for each level) + type: string + created_at: + description: Create time + type: string + description: + description: Details about menu + type: string + id: + description: Unique ID + type: string + name: + description: Display name of menu + type: string + parent_id: + description: Parent ID (From Menu.ID) + type: string + parent_path: + description: Parent path (split by .) + type: string + path: + description: Access path of menu + type: string + properties: + description: Properties of menu (JSON) + type: string + resources: + description: Resources of menu + items: + $ref: '#/definitions/schema.MenuResource' + type: array + sequence: + description: Sequence for sorting (Order by desc) + type: integer + status: + description: Status of menu (enabled, disabled) + type: string + type: + description: Type of menu (page, button) + type: string + updated_at: + description: Update time + type: string + type: object + schema.MenuForm: + properties: + code: + description: Code of menu (unique for each level) + maxLength: 32 + type: string + description: + description: Details about menu + type: string + name: + description: Display name of menu + maxLength: 128 + type: string + parent_id: + description: Parent ID (From Menu.ID) + type: string + path: + description: Access path of menu + type: string + properties: + description: Properties of menu (JSON) + type: string + resources: + description: Resources of menu + items: + $ref: '#/definitions/schema.MenuResource' + type: array + sequence: + description: Sequence for sorting (Order by desc) + type: integer + status: + description: Status of menu (enabled, disabled) + enum: + - disabled + - enabled + type: string + type: + description: Type of menu (page, button) + enum: + - page + - button + type: string + required: + - code + - name + - status + - type + type: object + schema.MenuResource: + properties: + created_at: + description: Create time + type: string + id: + description: Unique ID + type: string + menu_id: + description: From Menu.ID + type: string + method: + description: HTTP method + type: string + path: + description: API request path (e.g. /api/v1/users/:id) + type: string + updated_at: + description: Update time + type: string + type: object + schema.Notice: + properties: + areaId: + type: integer + content: + type: string + createdAt: + type: string + createdId: + type: integer + deletedAt: + type: integer + deletedId: + type: integer + id: + type: integer + sequence: + type: integer + status: + type: string + title: + type: string + updatedAt: + type: string + type: object + schema.NoticeForm: + properties: + areaId: + type: integer + content: + type: string + sequence: + type: integer + status: + type: string + title: + type: string + type: object + schema.Point: + properties: + created_at: + description: Create time + type: string + id: + description: Unique ID + type: string + updated_at: + description: Update time + type: string + type: object + schema.PointForm: + type: object + schema.Product: + properties: + areaId: + type: integer + businessImgs: + items: + type: string + type: array + category: + allOf: + - $ref: '#/definitions/schema.ProductCategory' + description: 关联分类 + categoryId: + type: integer + cover: + type: string + createdAt: + type: string + createdId: + type: integer + deletedAt: + type: integer + deletedId: + type: integer + details: + type: string + expireAt: + type: string + id: + type: integer + instructions: + type: string + isHot: + type: boolean + maxNum: + type: integer + name: + type: string + policy: + type: string + price: + type: integer + status: + type: string + stock: + type: integer + type: + type: string + updatedAt: + type: string + type: object + schema.ProductCategory: + properties: + createdAt: + type: string + createdId: + type: integer + deletedAt: + type: integer + deletedId: + type: integer + icon: + type: string + id: + type: integer + introduce: + type: string + name: + type: string + parentId: + type: integer + status: + type: string + updatedAt: + type: string + type: object + schema.ProductCategoryForm: + properties: + icon: + type: string + introduce: + type: string + name: + type: string + parentId: + type: integer + status: + type: string + type: object + schema.ProductForm: + properties: + areaId: + type: integer + businessImgs: + items: + type: string + type: array + categoryId: + type: integer + cover: + type: string + details: + type: string + expireAt: + type: string + instructions: + type: string + isHot: + type: boolean + maxNum: + type: integer + name: + type: string + policy: + type: string + price: + type: integer + status: + type: string + stock: + type: integer + type: + type: string + type: object + schema.ProductOrder: + properties: + createdAt: + type: string + createdId: + type: integer + customerId: + type: integer + deletedAt: + type: integer + deletedId: + type: integer + id: + type: integer + mentorId: + type: string + productId: + type: integer + status: + type: string + updatedAt: + type: string + type: object + schema.ProductOrderForm: + properties: + customerId: + type: integer + mentorId: + type: string + productId: + type: integer + status: + type: string + type: object + schema.QeustionDetail: + properties: + content: + items: + $ref: '#/definitions/schema.QuestionDetailTable' + type: array + createdAt: + type: string + createdId: + type: integer + customerId: + type: integer + deletedAt: + type: integer + deletedId: + type: integer + id: + type: integer + questionId: + type: integer + status: + type: string + updatedAt: + type: string + type: object + schema.QeustionDetailForm: + properties: + content: + items: + $ref: '#/definitions/schema.QuestionDetailTable' + type: array + customerId: + type: integer + questionId: + type: integer + status: + type: string + type: object + schema.QuestionDetailTable: + properties: + answer: + type: string + label: + type: string + options: + items: + type: string + type: array + required: + type: boolean + type: + type: string + type: object + schema.Questionnaire: + properties: + areaId: + type: integer + content: + items: + $ref: '#/definitions/schema.QuestionnaireTable' + type: array + cover: + type: string + createdAt: + type: string + createdId: + type: integer + deletedAt: + type: integer + deletedId: + type: integer + endAt: + type: string + id: + type: integer + point: + type: integer + startAt: + type: string + status: + type: string + title: + type: string + updatedAt: + type: string + type: object + schema.QuestionnaireForm: + properties: + areaId: + type: integer + content: + items: + $ref: '#/definitions/schema.QuestionnaireTable' + type: array + cover: + type: string + endAt: + type: string + point: + type: integer + startAt: + type: string + status: + type: string + title: + type: string + type: object + schema.QuestionnaireTable: + properties: + label: + type: string + options: + items: + type: string + type: array + required: + type: boolean + type: + type: string + type: object + schema.ReceptionCenter: + properties: + areaId: + type: integer + createdAt: + type: string + createdId: + type: integer + deletedAt: + type: integer + deletedId: + type: integer + id: + type: integer + introduce: + type: string + name: + type: string + status: + type: string + updatedAt: + type: string + type: object + schema.ReceptionCenterForm: + properties: + areaId: + type: integer + introduce: + type: string + name: + type: string + status: + type: string + type: object + schema.Role: + properties: + code: + description: Code of role (unique) + type: string + created_at: + description: Create time + type: string + description: + description: Details about role + type: string + id: + description: Unique ID + type: string + menus: + description: Role menu list + items: + $ref: '#/definitions/schema.RoleMenu' + type: array + name: + description: Display name of role + type: string + sequence: + description: Sequence for sorting + type: integer + status: + description: Status of role (disabled, enabled) + type: string + updated_at: + description: Update time + type: string + type: object + schema.RoleForm: + properties: + code: + description: Code of role (unique) + maxLength: 32 + type: string + description: + description: Details about role + type: string + menus: + description: Role menu list + items: + $ref: '#/definitions/schema.RoleMenu' + type: array + name: + description: Display name of role + maxLength: 128 + type: string + sequence: + description: Sequence for sorting + type: integer + status: + description: Status of role (enabled, disabled) + enum: + - disabled + - enabled + type: string + required: + - code + - name + - status + type: object + schema.RoleMenu: + properties: + created_at: + description: Create time + type: string + id: + description: Unique ID + type: string + menu_id: + description: From Menu.ID + type: string + role_id: + description: From Role.ID + type: string + updated_at: + description: Update time + type: string + type: object + schema.UpdateCurrentUser: + properties: + email: + description: Email of user + maxLength: 128 + type: string + name: + description: Name of user + maxLength: 64 + type: string + phone: + description: Phone number of user + maxLength: 32 + type: string + remark: + description: Remark of user + maxLength: 1024 + type: string + required: + - name + type: object + schema.UpdateLoginPassword: + properties: + new_password: + description: New password (md5 hash) + type: string + old_password: + description: Old password (md5 hash) + type: string + required: + - new_password + - old_password + type: object + schema.User: + properties: + companyId: + description: Remark of user + type: integer + created_at: + description: Create time + type: string + email: + type: string + id: + description: Unique ID + type: string + name: + description: Name of user + type: string + phone: + description: Phone number of user + type: string + remark: + description: Remark of user + type: string + roles: + description: Roles of user + items: + $ref: '#/definitions/schema.UserRole' + type: array + status: + description: Status of user (activated, freezed) + type: string + updated_at: + description: Update time + type: string + username: + description: Username for login + type: string + type: object + schema.UserForm: + properties: + email: + description: Email of user + maxLength: 128 + type: string + name: + description: Name of user + maxLength: 64 + type: string + password: + description: Password for login (md5 hash) + maxLength: 64 + type: string + phone: + description: Phone number of user + maxLength: 32 + type: string + remark: + description: Remark of user + maxLength: 1024 + type: string + roles: + description: Roles of user + items: + $ref: '#/definitions/schema.UserRole' + type: array + status: + description: Status of user (activated, freezed) + enum: + - activated + - freezed + type: string + username: + description: Username for login + maxLength: 64 + type: string + required: + - name + - roles + - status + - username + type: object + schema.UserRole: + properties: + created_at: + description: Create time + type: string + id: + description: Unique ID + type: string + role_id: + description: From Role.ID + type: string + role_name: + description: From Role.Name + type: string + updated_at: + description: Update time + type: string + user_id: + description: From User.ID + type: string + type: object + util.ResponseResult: + properties: + data: {} + error: + $ref: '#/definitions/errors.Error' + success: + type: boolean + total: + type: integer + type: object +info: + contact: {} + description: 中海小程序 + title: haibei + version: v1.0.0 +paths: + /api/v1/activities: + get: + parameters: + - default: 1 + description: pagination index + in: query + name: current + required: true + type: integer + - default: 10 + description: pagination size + in: query + name: pageSize + required: true + type: integer + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + items: + $ref: '#/definitions/schema.Activity' + type: array + type: object + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Query activity list + tags: + - 活动模块 + post: + parameters: + - description: Request body + in: body + name: body + required: true + schema: + $ref: '#/definitions/schema.ActivityForm' + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + $ref: '#/definitions/schema.Activity' + type: object + "400": + description: Bad Request + schema: + $ref: '#/definitions/util.ResponseResult' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Create activity record + tags: + - 活动模块 + /api/v1/activities/{id}: + delete: + parameters: + - description: unique id + in: path + name: id + required: true + type: string + responses: + "200": + description: OK + schema: + $ref: '#/definitions/util.ResponseResult' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Delete activity record by ID + tags: + - 活动模块 + get: + parameters: + - description: unique id + in: path + name: id + required: true + type: string + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + $ref: '#/definitions/schema.Activity' + type: object + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Get activity record by ID + tags: + - 活动模块 + put: + parameters: + - description: unique id + in: path + name: id + required: true + type: string + - description: Request body + in: body + name: body + required: true + schema: + $ref: '#/definitions/schema.ActivityForm' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/util.ResponseResult' + "400": + description: Bad Request + schema: + $ref: '#/definitions/util.ResponseResult' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Update activity record by ID + tags: + - 活动模块 + /api/v1/activity-details: + get: + parameters: + - default: 1 + description: pagination index + in: query + name: current + required: true + type: integer + - default: 10 + description: pagination size + in: query + name: pageSize + required: true + type: integer + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + items: + $ref: '#/definitions/schema.ActivityDetail' + type: array + type: object + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Query activity detail list + tags: + - ActivityDetailAPI + post: + parameters: + - description: Request body + in: body + name: body + required: true + schema: + $ref: '#/definitions/schema.ActivityDetailForm' + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + $ref: '#/definitions/schema.ActivityDetail' + type: object + "400": + description: Bad Request + schema: + $ref: '#/definitions/util.ResponseResult' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Create activity detail record + tags: + - ActivityDetailAPI + /api/v1/activity-details/{id}: + delete: + parameters: + - description: unique id + in: path + name: id + required: true + type: string + responses: + "200": + description: OK + schema: + $ref: '#/definitions/util.ResponseResult' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Delete activity detail record by ID + tags: + - ActivityDetailAPI + get: + parameters: + - description: unique id + in: path + name: id + required: true + type: string + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + $ref: '#/definitions/schema.ActivityDetail' + type: object + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Get activity detail record by ID + tags: + - ActivityDetailAPI + put: + parameters: + - description: unique id + in: path + name: id + required: true + type: string + - description: Request body + in: body + name: body + required: true + schema: + $ref: '#/definitions/schema.ActivityDetailForm' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/util.ResponseResult' + "400": + description: Bad Request + schema: + $ref: '#/definitions/util.ResponseResult' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Update activity detail record by ID + tags: + - ActivityDetailAPI + /api/v1/apps/banner: + get: + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + items: + $ref: '#/definitions/schema.Banner' + type: array + type: object + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: 首页banner + tags: + - APP端 + /api/v1/apps/bind: + post: + responses: + "200": + description: OK + schema: + $ref: '#/definitions/util.ResponseResult' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: 小程序绑定手机号 + tags: + - APP端 + /api/v1/apps/login: + post: + responses: + "200": + description: OK + schema: + $ref: '#/definitions/util.ResponseResult' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: 小程序登陆 + tags: + - APP端 + /api/v1/apps/sms/{phone}: + get: + responses: + "200": + description: OK + schema: + $ref: '#/definitions/util.ResponseResult' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: 发送验证码 + tags: + - APP端 + /api/v1/areas: + get: + parameters: + - default: 1 + description: pagination index + in: query + name: current + required: true + type: integer + - default: 10 + description: pagination size + in: query + name: pageSize + required: true + type: integer + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + items: + $ref: '#/definitions/schema.Area' + type: array + type: object + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Query area list + tags: + - 区域模块 + post: + parameters: + - description: Request body + in: body + name: body + required: true + schema: + $ref: '#/definitions/schema.AreaForm' + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + $ref: '#/definitions/schema.Area' + type: object + "400": + description: Bad Request + schema: + $ref: '#/definitions/util.ResponseResult' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Create area record + tags: + - 区域模块 + /api/v1/areas/{id}: + delete: + parameters: + - description: unique id + in: path + name: id + required: true + type: string + responses: + "200": + description: OK + schema: + $ref: '#/definitions/util.ResponseResult' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Delete area record by ID + tags: + - 区域模块 + get: + parameters: + - description: unique id + in: path + name: id + required: true + type: string + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + $ref: '#/definitions/schema.Area' + type: object + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Get area record by ID + tags: + - 区域模块 + put: + parameters: + - description: unique id + in: path + name: id + required: true + type: string + - description: Request body + in: body + name: body + required: true + schema: + $ref: '#/definitions/schema.AreaForm' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/util.ResponseResult' + "400": + description: Bad Request + schema: + $ref: '#/definitions/util.ResponseResult' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Update area record by ID + tags: + - 区域模块 + /api/v1/balances: + get: + parameters: + - default: 1 + description: pagination index + in: query + name: current + required: true + type: integer + - default: 10 + description: pagination size + in: query + name: pageSize + required: true + type: integer + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + items: + $ref: '#/definitions/schema.Balance' + type: array + type: object + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Query balance list + tags: + - 积分模块 + post: + parameters: + - description: Request body + in: body + name: body + required: true + schema: + $ref: '#/definitions/schema.BalanceForm' + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + $ref: '#/definitions/schema.Balance' + type: object + "400": + description: Bad Request + schema: + $ref: '#/definitions/util.ResponseResult' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Create balance record + tags: + - 积分模块 + /api/v1/balances/{id}: + delete: + parameters: + - description: unique id + in: path + name: id + required: true + type: string + responses: + "200": + description: OK + schema: + $ref: '#/definitions/util.ResponseResult' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Delete balance record by ID + tags: + - 积分模块 + get: + parameters: + - description: unique id + in: path + name: id + required: true + type: string + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + $ref: '#/definitions/schema.Balance' + type: object + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Get balance record by ID + tags: + - 积分模块 + put: + parameters: + - description: unique id + in: path + name: id + required: true + type: string + - description: Request body + in: body + name: body + required: true + schema: + $ref: '#/definitions/schema.BalanceForm' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/util.ResponseResult' + "400": + description: Bad Request + schema: + $ref: '#/definitions/util.ResponseResult' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Update balance record by ID + tags: + - 积分模块 + /api/v1/banners: + get: + parameters: + - default: 1 + description: pagination index + in: query + name: current + required: true + type: integer + - default: 10 + description: pagination size + in: query + name: pageSize + required: true + type: integer + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + items: + $ref: '#/definitions/schema.Banner' + type: array + type: object + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Query banner list + tags: + - 首页轮播图模块 + post: + parameters: + - description: Request body + in: body + name: body + required: true + schema: + $ref: '#/definitions/schema.BannerForm' + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + $ref: '#/definitions/schema.Banner' + type: object + "400": + description: Bad Request + schema: + $ref: '#/definitions/util.ResponseResult' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Create banner record + tags: + - 首页轮播图模块 + /api/v1/banners/{id}: + delete: + parameters: + - description: unique id + in: path + name: id + required: true + type: string + responses: + "200": + description: OK + schema: + $ref: '#/definitions/util.ResponseResult' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Delete banner record by ID + tags: + - 首页轮播图模块 + get: + parameters: + - description: unique id + in: path + name: id + required: true + type: string + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + $ref: '#/definitions/schema.Banner' + type: object + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Get banner record by ID + tags: + - 首页轮播图模块 + put: + parameters: + - description: unique id + in: path + name: id + required: true + type: string + - description: Request body + in: body + name: body + required: true + schema: + $ref: '#/definitions/schema.BannerForm' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/util.ResponseResult' + "400": + description: Bad Request + schema: + $ref: '#/definitions/util.ResponseResult' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Update banner record by ID + tags: + - 首页轮播图模块 + /api/v1/captcha/id: + get: + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + $ref: '#/definitions/schema.Captcha' + type: object + summary: Get captcha ID + tags: + - LoginAPI + /api/v1/captcha/image: + get: + parameters: + - description: Captcha ID + in: query + name: id + required: true + type: string + - description: Reload captcha image (reload=1) + in: query + name: reload + type: number + produces: + - image/png + responses: + "200": + description: Captcha image + "404": + description: Not Found + schema: + $ref: '#/definitions/util.ResponseResult' + summary: Response captcha image + tags: + - LoginAPI + /api/v1/commons: + get: + parameters: + - default: 1 + description: pagination index + in: query + name: current + required: true + type: integer + - default: 10 + description: pagination size + in: query + name: pageSize + required: true + type: integer + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + items: + $ref: '#/definitions/schema.Common' + type: array + type: object + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Query common list + tags: + - CommonAPI + post: + parameters: + - description: Request body + in: body + name: body + required: true + schema: + $ref: '#/definitions/schema.CommonForm' + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + $ref: '#/definitions/schema.Common' + type: object + "400": + description: Bad Request + schema: + $ref: '#/definitions/util.ResponseResult' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Create common record + tags: + - CommonAPI + /api/v1/commons/{id}: + delete: + parameters: + - description: unique id + in: path + name: id + required: true + type: string + responses: + "200": + description: OK + schema: + $ref: '#/definitions/util.ResponseResult' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Delete common record by ID + tags: + - CommonAPI + get: + parameters: + - description: unique id + in: path + name: id + required: true + type: string + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + $ref: '#/definitions/schema.Common' + type: object + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Get common record by ID + tags: + - CommonAPI + put: + parameters: + - description: unique id + in: path + name: id + required: true + type: string + - description: Request body + in: body + name: body + required: true + schema: + $ref: '#/definitions/schema.CommonForm' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/util.ResponseResult' + "400": + description: Bad Request + schema: + $ref: '#/definitions/util.ResponseResult' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Update common record by ID + tags: + - CommonAPI + /api/v1/companies: + get: + parameters: + - default: 1 + description: pagination index + in: query + name: current + required: true + type: integer + - default: 10 + description: pagination size + in: query + name: pageSize + required: true + type: integer + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + items: + $ref: '#/definitions/schema.Company' + type: array + type: object + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Query company list + tags: + - 项目模块 + post: + parameters: + - description: Request body + in: body + name: body + required: true + schema: + $ref: '#/definitions/schema.CompanyForm' + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + $ref: '#/definitions/schema.Company' + type: object + "400": + description: Bad Request + schema: + $ref: '#/definitions/util.ResponseResult' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Create company record + tags: + - 项目模块 + /api/v1/companies/{id}: + delete: + parameters: + - description: unique id + in: path + name: id + required: true + type: string + responses: + "200": + description: OK + schema: + $ref: '#/definitions/util.ResponseResult' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Delete company record by ID + tags: + - 项目模块 + get: + parameters: + - description: unique id + in: path + name: id + required: true + type: string + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + $ref: '#/definitions/schema.Company' + type: object + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Get company record by ID + tags: + - 项目模块 + put: + parameters: + - description: unique id + in: path + name: id + required: true + type: string + - description: Request body + in: body + name: body + required: true + schema: + $ref: '#/definitions/schema.CompanyForm' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/util.ResponseResult' + "400": + description: Bad Request + schema: + $ref: '#/definitions/util.ResponseResult' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Update company record by ID + tags: + - 项目模块 + /api/v1/current/logout: + post: + responses: + "200": + description: OK + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Logout system + tags: + - LoginAPI + /api/v1/current/menus: + get: + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + items: + $ref: '#/definitions/schema.Menu' + type: array + type: object + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Query current user menus based on the current user role + tags: + - LoginAPI + /api/v1/current/password: + put: + parameters: + - description: Request body + in: body + name: body + required: true + schema: + $ref: '#/definitions/schema.UpdateLoginPassword' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/util.ResponseResult' + "400": + description: Bad Request + schema: + $ref: '#/definitions/util.ResponseResult' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Change current user password + tags: + - LoginAPI + /api/v1/current/refresh-token: + post: + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + $ref: '#/definitions/schema.LoginToken' + type: object + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Refresh current access token + tags: + - LoginAPI + /api/v1/current/user: + get: + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + $ref: '#/definitions/schema.User' + type: object + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Get current user info + tags: + - LoginAPI + put: + parameters: + - description: Request body + in: body + name: body + required: true + schema: + $ref: '#/definitions/schema.UpdateCurrentUser' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/util.ResponseResult' + "400": + description: Bad Request + schema: + $ref: '#/definitions/util.ResponseResult' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Update current user info + tags: + - LoginAPI + /api/v1/customers: + get: + parameters: + - default: 1 + description: pagination index + in: query + name: current + required: true + type: integer + - default: 10 + description: pagination size + in: query + name: pageSize + required: true + type: integer + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + items: + $ref: '#/definitions/schema.Customer' + type: array + type: object + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Query customer list + tags: + - 客户模块 + post: + parameters: + - description: Request body + in: body + name: body + required: true + schema: + $ref: '#/definitions/schema.CustomerForm' + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + $ref: '#/definitions/schema.Customer' + type: object + "400": + description: Bad Request + schema: + $ref: '#/definitions/util.ResponseResult' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Create customer record + tags: + - 客户模块 + /api/v1/customers/{id}: + delete: + parameters: + - description: unique id + in: path + name: id + required: true + type: string + responses: + "200": + description: OK + schema: + $ref: '#/definitions/util.ResponseResult' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Delete customer record by ID + tags: + - 客户模块 + get: + parameters: + - description: unique id + in: path + name: id + required: true + type: string + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + $ref: '#/definitions/schema.Customer' + type: object + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Get customer record by ID + tags: + - 客户模块 + put: + parameters: + - description: unique id + in: path + name: id + required: true + type: string + - description: Request body + in: body + name: body + required: true + schema: + $ref: '#/definitions/schema.CustomerForm' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/util.ResponseResult' + "400": + description: Bad Request + schema: + $ref: '#/definitions/util.ResponseResult' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Update customer record by ID + tags: + - 客户模块 + /api/v1/grades: + get: + parameters: + - default: 1 + description: pagination index + in: query + name: current + required: true + type: integer + - default: 10 + description: pagination size + in: query + name: pageSize + required: true + type: integer + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + items: + $ref: '#/definitions/schema.Grade' + type: array + type: object + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Query grade list + tags: + - 权益模块 + post: + parameters: + - description: Request body + in: body + name: body + required: true + schema: + $ref: '#/definitions/schema.GradeForm' + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + $ref: '#/definitions/schema.Grade' + type: object + "400": + description: Bad Request + schema: + $ref: '#/definitions/util.ResponseResult' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Create grade record + tags: + - 权益模块 + /api/v1/grades/{id}: + delete: + parameters: + - description: unique id + in: path + name: id + required: true + type: string + responses: + "200": + description: OK + schema: + $ref: '#/definitions/util.ResponseResult' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Delete grade record by ID + tags: + - 权益模块 + get: + parameters: + - description: unique id + in: path + name: id + required: true + type: string + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + $ref: '#/definitions/schema.Grade' + type: object + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Get grade record by ID + tags: + - 权益模块 + put: + parameters: + - description: unique id + in: path + name: id + required: true + type: string + - description: Request body + in: body + name: body + required: true + schema: + $ref: '#/definitions/schema.GradeForm' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/util.ResponseResult' + "400": + description: Bad Request + schema: + $ref: '#/definitions/util.ResponseResult' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Update grade record by ID + tags: + - 权益模块 + /api/v1/loggers: + get: + parameters: + - default: 1 + description: pagination index + in: query + name: current + required: true + type: integer + - default: 10 + description: pagination size + in: query + name: pageSize + required: true + type: integer + - description: log level + in: query + name: level + type: string + - description: trace ID + in: query + name: traceID + type: string + - description: user name + in: query + name: userName + type: string + - description: log tag + in: query + name: tag + type: string + - description: log message + in: query + name: message + type: string + - description: start time + in: query + name: startTime + type: string + - description: end time + in: query + name: endTime + type: string + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + items: + $ref: '#/definitions/schema.Logger' + type: array + type: object + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Query logger list + tags: + - LoggerAPI + /api/v1/login: + post: + parameters: + - description: Request body + in: body + name: body + required: true + schema: + $ref: '#/definitions/schema.LoginForm' + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + $ref: '#/definitions/schema.LoginToken' + type: object + "400": + description: Bad Request + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + summary: Login system with username and password + tags: + - LoginAPI + /api/v1/menus: + get: + parameters: + - description: Code path of menu (like xxx.xxx.xxx) + in: query + name: code + type: string + - description: Name of menu + in: query + name: name + type: string + - description: Whether to include menu resources + in: query + name: includeResources + type: boolean + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + items: + $ref: '#/definitions/schema.Menu' + type: array + type: object + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Query menu tree data + tags: + - MenuAPI + post: + parameters: + - description: Request body + in: body + name: body + required: true + schema: + $ref: '#/definitions/schema.MenuForm' + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + $ref: '#/definitions/schema.Menu' + type: object + "400": + description: Bad Request + schema: + $ref: '#/definitions/util.ResponseResult' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Create menu record + tags: + - MenuAPI + /api/v1/menus/{id}: + delete: + parameters: + - description: unique id + in: path + name: id + required: true + type: string + responses: + "200": + description: OK + schema: + $ref: '#/definitions/util.ResponseResult' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Delete menu record by ID + tags: + - MenuAPI + get: + parameters: + - description: unique id + in: path + name: id + required: true + type: string + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + $ref: '#/definitions/schema.Menu' + type: object + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Get menu record by ID + tags: + - MenuAPI + put: + parameters: + - description: unique id + in: path + name: id + required: true + type: string + - description: Request body + in: body + name: body + required: true + schema: + $ref: '#/definitions/schema.MenuForm' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/util.ResponseResult' + "400": + description: Bad Request + schema: + $ref: '#/definitions/util.ResponseResult' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Update menu record by ID + tags: + - MenuAPI + /api/v1/notices: + get: + parameters: + - default: 1 + description: pagination index + in: query + name: current + required: true + type: integer + - default: 10 + description: pagination size + in: query + name: pageSize + required: true + type: integer + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + items: + $ref: '#/definitions/schema.Notice' + type: array + type: object + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Query notice list + tags: + - 公告模块 + post: + parameters: + - description: Request body + in: body + name: body + required: true + schema: + $ref: '#/definitions/schema.NoticeForm' + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + $ref: '#/definitions/schema.Notice' + type: object + "400": + description: Bad Request + schema: + $ref: '#/definitions/util.ResponseResult' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Create notice record + tags: + - 公告模块 + /api/v1/notices/{id}: + delete: + parameters: + - description: unique id + in: path + name: id + required: true + type: string + responses: + "200": + description: OK + schema: + $ref: '#/definitions/util.ResponseResult' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Delete notice record by ID + tags: + - 公告模块 + get: + parameters: + - description: unique id + in: path + name: id + required: true + type: string + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + $ref: '#/definitions/schema.Notice' + type: object + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Get notice record by ID + tags: + - 公告模块 + put: + parameters: + - description: unique id + in: path + name: id + required: true + type: string + - description: Request body + in: body + name: body + required: true + schema: + $ref: '#/definitions/schema.NoticeForm' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/util.ResponseResult' + "400": + description: Bad Request + schema: + $ref: '#/definitions/util.ResponseResult' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Update notice record by ID + tags: + - 公告模块 + /api/v1/points: + get: + parameters: + - default: 1 + description: pagination index + in: query + name: current + required: true + type: integer + - default: 10 + description: pagination size + in: query + name: pageSize + required: true + type: integer + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + items: + $ref: '#/definitions/schema.Point' + type: array + type: object + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Query point list + tags: + - PointAPI + post: + parameters: + - description: Request body + in: body + name: body + required: true + schema: + $ref: '#/definitions/schema.PointForm' + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + $ref: '#/definitions/schema.Point' + type: object + "400": + description: Bad Request + schema: + $ref: '#/definitions/util.ResponseResult' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Create point record + tags: + - PointAPI + /api/v1/points/{id}: + delete: + parameters: + - description: unique id + in: path + name: id + required: true + type: string + responses: + "200": + description: OK + schema: + $ref: '#/definitions/util.ResponseResult' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Delete point record by ID + tags: + - PointAPI + get: + parameters: + - description: unique id + in: path + name: id + required: true + type: string + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + $ref: '#/definitions/schema.Point' + type: object + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Get point record by ID + tags: + - PointAPI + put: + parameters: + - description: unique id + in: path + name: id + required: true + type: string + - description: Request body + in: body + name: body + required: true + schema: + $ref: '#/definitions/schema.PointForm' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/util.ResponseResult' + "400": + description: Bad Request + schema: + $ref: '#/definitions/util.ResponseResult' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Update point record by ID + tags: + - PointAPI + /api/v1/product-categories: + get: + parameters: + - default: 1 + description: pagination index + in: query + name: current + required: true + type: integer + - default: 10 + description: pagination size + in: query + name: pageSize + required: true + type: integer + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + items: + $ref: '#/definitions/schema.ProductCategory' + type: array + type: object + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Query product category list + tags: + - 权益产品分类模块 + post: + parameters: + - description: Request body + in: body + name: body + required: true + schema: + $ref: '#/definitions/schema.ProductCategoryForm' + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + $ref: '#/definitions/schema.ProductCategory' + type: object + "400": + description: Bad Request + schema: + $ref: '#/definitions/util.ResponseResult' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Create product category record + tags: + - 权益产品分类模块 + /api/v1/product-categories/{id}: + delete: + parameters: + - description: unique id + in: path + name: id + required: true + type: string + responses: + "200": + description: OK + schema: + $ref: '#/definitions/util.ResponseResult' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Delete product category record by ID + tags: + - 权益产品分类模块 + get: + parameters: + - description: unique id + in: path + name: id + required: true + type: string + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + $ref: '#/definitions/schema.ProductCategory' + type: object + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Get product category record by ID + tags: + - 权益产品分类模块 + put: + parameters: + - description: unique id + in: path + name: id + required: true + type: string + - description: Request body + in: body + name: body + required: true + schema: + $ref: '#/definitions/schema.ProductCategoryForm' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/util.ResponseResult' + "400": + description: Bad Request + schema: + $ref: '#/definitions/util.ResponseResult' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Update product category record by ID + tags: + - 权益产品分类模块 + /api/v1/product-orders: + get: + parameters: + - default: 1 + description: pagination index + in: query + name: current + required: true + type: integer + - default: 10 + description: pagination size + in: query + name: pageSize + required: true + type: integer + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + items: + $ref: '#/definitions/schema.ProductOrder' + type: array + type: object + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Query product order list + tags: + - ProductOrderAPI + post: + parameters: + - description: Request body + in: body + name: body + required: true + schema: + $ref: '#/definitions/schema.ProductOrderForm' + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + $ref: '#/definitions/schema.ProductOrder' + type: object + "400": + description: Bad Request + schema: + $ref: '#/definitions/util.ResponseResult' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Create product order record + tags: + - ProductOrderAPI + /api/v1/product-orders/{id}: + delete: + parameters: + - description: unique id + in: path + name: id + required: true + type: string + responses: + "200": + description: OK + schema: + $ref: '#/definitions/util.ResponseResult' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Delete product order record by ID + tags: + - ProductOrderAPI + get: + parameters: + - description: unique id + in: path + name: id + required: true + type: string + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + $ref: '#/definitions/schema.ProductOrder' + type: object + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Get product order record by ID + tags: + - ProductOrderAPI + put: + parameters: + - description: unique id + in: path + name: id + required: true + type: string + - description: Request body + in: body + name: body + required: true + schema: + $ref: '#/definitions/schema.ProductOrderForm' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/util.ResponseResult' + "400": + description: Bad Request + schema: + $ref: '#/definitions/util.ResponseResult' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Update product order record by ID + tags: + - ProductOrderAPI + /api/v1/products: + get: + parameters: + - default: 1 + description: pagination index + in: query + name: current + required: true + type: integer + - default: 10 + description: pagination size + in: query + name: pageSize + required: true + type: integer + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + items: + $ref: '#/definitions/schema.Product' + type: array + type: object + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Query product list + tags: + - 权益产品模块 + post: + parameters: + - description: Request body + in: body + name: body + required: true + schema: + $ref: '#/definitions/schema.ProductForm' + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + $ref: '#/definitions/schema.Product' + type: object + "400": + description: Bad Request + schema: + $ref: '#/definitions/util.ResponseResult' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Create product record + tags: + - 权益产品模块 + /api/v1/products/{id}: + delete: + parameters: + - description: unique id + in: path + name: id + required: true + type: string + responses: + "200": + description: OK + schema: + $ref: '#/definitions/util.ResponseResult' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Delete product record by ID + tags: + - 权益产品模块 + get: + parameters: + - description: unique id + in: path + name: id + required: true + type: string + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + $ref: '#/definitions/schema.Product' + type: object + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Get product record by ID + tags: + - 权益产品模块 + put: + parameters: + - description: unique id + in: path + name: id + required: true + type: string + - description: Request body + in: body + name: body + required: true + schema: + $ref: '#/definitions/schema.ProductForm' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/util.ResponseResult' + "400": + description: Bad Request + schema: + $ref: '#/definitions/util.ResponseResult' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Update product record by ID + tags: + - 权益产品模块 + /api/v1/qeustion-details: + get: + parameters: + - default: 1 + description: pagination index + in: query + name: current + required: true + type: integer + - default: 10 + description: pagination size + in: query + name: pageSize + required: true + type: integer + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + items: + $ref: '#/definitions/schema.QeustionDetail' + type: array + type: object + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Query qeustion detail list + tags: + - QeustionDetailAPI + post: + parameters: + - description: Request body + in: body + name: body + required: true + schema: + $ref: '#/definitions/schema.QeustionDetailForm' + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + $ref: '#/definitions/schema.QeustionDetail' + type: object + "400": + description: Bad Request + schema: + $ref: '#/definitions/util.ResponseResult' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Create qeustion detail record + tags: + - QeustionDetailAPI + /api/v1/qeustion-details/{id}: + delete: + parameters: + - description: unique id + in: path + name: id + required: true + type: string + responses: + "200": + description: OK + schema: + $ref: '#/definitions/util.ResponseResult' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Delete qeustion detail record by ID + tags: + - QeustionDetailAPI + get: + parameters: + - description: unique id + in: path + name: id + required: true + type: string + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + $ref: '#/definitions/schema.QeustionDetail' + type: object + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Get qeustion detail record by ID + tags: + - QeustionDetailAPI + put: + parameters: + - description: unique id + in: path + name: id + required: true + type: string + - description: Request body + in: body + name: body + required: true + schema: + $ref: '#/definitions/schema.QeustionDetailForm' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/util.ResponseResult' + "400": + description: Bad Request + schema: + $ref: '#/definitions/util.ResponseResult' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Update qeustion detail record by ID + tags: + - QeustionDetailAPI + /api/v1/questionnaires: + get: + parameters: + - default: 1 + description: pagination index + in: query + name: current + required: true + type: integer + - default: 10 + description: pagination size + in: query + name: pageSize + required: true + type: integer + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + items: + $ref: '#/definitions/schema.Questionnaire' + type: array + type: object + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Query questionnaire list + tags: + - 问卷模块 + post: + parameters: + - description: Request body + in: body + name: body + required: true + schema: + $ref: '#/definitions/schema.QuestionnaireForm' + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + $ref: '#/definitions/schema.Questionnaire' + type: object + "400": + description: Bad Request + schema: + $ref: '#/definitions/util.ResponseResult' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Create questionnaire record + tags: + - 问卷模块 + /api/v1/questionnaires/{id}: + delete: + parameters: + - description: unique id + in: path + name: id + required: true + type: string + responses: + "200": + description: OK + schema: + $ref: '#/definitions/util.ResponseResult' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Delete questionnaire record by ID + tags: + - 问卷模块 + get: + parameters: + - description: unique id + in: path + name: id + required: true + type: string + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + $ref: '#/definitions/schema.Questionnaire' + type: object + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Get questionnaire record by ID + tags: + - 问卷模块 + put: + parameters: + - description: unique id + in: path + name: id + required: true + type: string + - description: Request body + in: body + name: body + required: true + schema: + $ref: '#/definitions/schema.QuestionnaireForm' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/util.ResponseResult' + "400": + description: Bad Request + schema: + $ref: '#/definitions/util.ResponseResult' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Update questionnaire record by ID + tags: + - 问卷模块 + /api/v1/reception_center: + get: + parameters: + - default: 1 + description: pagination index + in: query + name: current + required: true + type: integer + - default: 10 + description: pagination size + in: query + name: pageSize + required: true + type: integer + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + items: + $ref: '#/definitions/schema.ReceptionCenter' + type: array + type: object + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Query ReceptionCenter list + tags: + - 楼盘模块 + post: + parameters: + - description: Request body + in: body + name: body + required: true + schema: + $ref: '#/definitions/api.CheckQrCodeStruct' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/util.ResponseResult' + "400": + description: Bad Request + schema: + $ref: '#/definitions/util.ResponseResult' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: check QrCode + tags: + - 楼盘模块 + /api/v1/reception_center/{id}: + delete: + parameters: + - description: unique id + in: path + name: id + required: true + type: string + responses: + "200": + description: OK + schema: + $ref: '#/definitions/util.ResponseResult' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Delete ReceptionCenter record by ID + tags: + - 楼盘模块 + get: + parameters: + - description: unique id + in: path + name: id + required: true + type: string + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + $ref: '#/definitions/schema.ReceptionCenter' + type: object + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Get ReceptionCenter record by ID + tags: + - 楼盘模块 + put: + parameters: + - description: unique id + in: path + name: id + required: true + type: string + - description: Request body + in: body + name: body + required: true + schema: + $ref: '#/definitions/schema.ReceptionCenterForm' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/util.ResponseResult' + "400": + description: Bad Request + schema: + $ref: '#/definitions/util.ResponseResult' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Update ReceptionCenter record by ID + tags: + - 楼盘模块 + /api/v1/reception_center/qr_code/{id}: + get: + parameters: + - description: unique id + in: path + name: id + required: true + type: string + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + items: + type: integer + type: array + type: object + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Get ReceptionCenter QrCode by ID + tags: + - 楼盘模块 + /api/v1/roles: + get: + parameters: + - default: 1 + description: pagination index + in: query + name: current + required: true + type: integer + - default: 10 + description: pagination size + in: query + name: pageSize + required: true + type: integer + - description: Display name of role + in: query + name: name + type: string + - description: Status of role (disabled, enabled) + in: query + name: status + type: string + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + items: + $ref: '#/definitions/schema.Role' + type: array + type: object + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Query role list + tags: + - RoleAPI + post: + parameters: + - description: Request body + in: body + name: body + required: true + schema: + $ref: '#/definitions/schema.RoleForm' + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + $ref: '#/definitions/schema.Role' + type: object + "400": + description: Bad Request + schema: + $ref: '#/definitions/util.ResponseResult' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Create role record + tags: + - RoleAPI + /api/v1/roles/{id}: + delete: + parameters: + - description: unique id + in: path + name: id + required: true + type: string + responses: + "200": + description: OK + schema: + $ref: '#/definitions/util.ResponseResult' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Delete role record by ID + tags: + - RoleAPI + get: + parameters: + - description: unique id + in: path + name: id + required: true + type: string + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + $ref: '#/definitions/schema.Role' + type: object + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Get role record by ID + tags: + - RoleAPI + put: + parameters: + - description: unique id + in: path + name: id + required: true + type: string + - description: Request body + in: body + name: body + required: true + schema: + $ref: '#/definitions/schema.RoleForm' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/util.ResponseResult' + "400": + description: Bad Request + schema: + $ref: '#/definitions/util.ResponseResult' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Update role record by ID + tags: + - RoleAPI + /api/v1/users: + get: + parameters: + - default: 1 + description: pagination index + in: query + name: current + required: true + type: integer + - default: 10 + description: pagination size + in: query + name: pageSize + required: true + type: integer + - description: Username for login + in: query + name: username + type: string + - description: Name of user + in: query + name: name + type: string + - description: Status of user (activated, freezed) + in: query + name: status + type: string + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + items: + $ref: '#/definitions/schema.User' + type: array + type: object + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Query user list + tags: + - UserAPI + post: + parameters: + - description: Request body + in: body + name: body + required: true + schema: + $ref: '#/definitions/schema.UserForm' + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + $ref: '#/definitions/schema.User' + type: object + "400": + description: Bad Request + schema: + $ref: '#/definitions/util.ResponseResult' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Create user record + tags: + - UserAPI + /api/v1/users/{id}: + delete: + parameters: + - description: unique id + in: path + name: id + required: true + type: string + responses: + "200": + description: OK + schema: + $ref: '#/definitions/util.ResponseResult' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Delete user record by ID + tags: + - UserAPI + get: + parameters: + - description: unique id + in: path + name: id + required: true + type: string + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + $ref: '#/definitions/schema.User' + type: object + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Get user record by ID + tags: + - UserAPI + put: + parameters: + - description: unique id + in: path + name: id + required: true + type: string + - description: Request body + in: body + name: body + required: true + schema: + $ref: '#/definitions/schema.UserForm' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/util.ResponseResult' + "400": + description: Bad Request + schema: + $ref: '#/definitions/util.ResponseResult' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Update user record by ID + tags: + - UserAPI + /api/v1/users/{id}/reset-pwd: + patch: + parameters: + - description: unique id + in: path + name: id + required: true + type: string + responses: + "200": + description: OK + schema: + $ref: '#/definitions/util.ResponseResult' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Reset user password by ID + tags: + - UserAPI +securityDefinitions: + ApiKeyAuth: + in: header + name: Authorization + type: apiKey +swagger: "2.0" diff --git a/internal/utility/prom/prom.go b/internal/utility/prom/prom.go new file mode 100644 index 0000000..3ac8f6b --- /dev/null +++ b/internal/utility/prom/prom.go @@ -0,0 +1,36 @@ +package prom + +import ( + "github.com/gin-gonic/gin" + "github.guxuan/haibei/internal/config" + "github.guxuan/haibei/pkg/promx" + "github.guxuan/haibei/pkg/util" +) + +var ( + Ins *promx.PrometheusWrapper + GinMiddleware gin.HandlerFunc +) + +func Init() { + logMethod := make(map[string]struct{}) + logAPI := make(map[string]struct{}) + for _, m := range config.C.Util.Prometheus.LogMethods { + logMethod[m] = struct{}{} + } + for _, a := range config.C.Util.Prometheus.LogApis { + logAPI[a] = struct{}{} + } + Ins = promx.NewPrometheusWrapper(&promx.Config{ + Enable: config.C.Util.Prometheus.Enable, + App: config.C.General.AppName, + ListenPort: config.C.Util.Prometheus.Port, + BasicUserName: config.C.Util.Prometheus.BasicUsername, + BasicPassword: config.C.Util.Prometheus.BasicPassword, + LogApi: logAPI, + LogMethod: logMethod, + Objectives: map[float64]float64{0.9: 0.01, 0.95: 0.005, 0.99: 0.001}, + DefaultCollect: config.C.Util.Prometheus.DefaultCollect, + }) + GinMiddleware = promx.NewAdapterGin(Ins).Middleware(config.C.Util.Prometheus.Enable, util.ReqBodyKey) +} diff --git a/internal/wirex/injector.go b/internal/wirex/injector.go new file mode 100644 index 0000000..9fa0b5f --- /dev/null +++ b/internal/wirex/injector.go @@ -0,0 +1,129 @@ +package wirex + +import ( + "context" + "github.guxuan/haibei/pkg/jwtx" + "time" + + "github.com/golang-jwt/jwt" + "github.guxuan/haibei/internal/config" + "github.guxuan/haibei/internal/mods" + "github.guxuan/haibei/pkg/cachex" + "github.guxuan/haibei/pkg/gormx" + "gorm.io/gorm" +) + +type Injector struct { + DB *gorm.DB + Cache cachex.Cacher + Auth jwtx.Auther + M *mods.Mods +} + +// It creates a new database connection, and returns a function that closes the connection +func InitDB(ctx context.Context) (*gorm.DB, func(), error) { + cfg := config.C.Storage.DB + + resolver := make([]gormx.ResolverConfig, len(cfg.Resolver)) + for i, v := range cfg.Resolver { + resolver[i] = gormx.ResolverConfig{ + DBType: v.DBType, + Sources: v.Sources, + Replicas: v.Replicas, + Tables: v.Tables, + } + } + + db, err := gormx.New(gormx.Config{ + Debug: cfg.Debug, + PrepareStmt: cfg.PrepareStmt, + DBType: cfg.Type, + DSN: cfg.DSN, + MaxLifetime: cfg.MaxLifetime, + MaxIdleTime: cfg.MaxIdleTime, + MaxOpenConns: cfg.MaxOpenConns, + MaxIdleConns: cfg.MaxIdleConns, + TablePrefix: cfg.TablePrefix, + Resolver: resolver, + }) + if err != nil { + return nil, nil, err + } + + return db, func() { + sqlDB, err := db.DB() + if err == nil { + _ = sqlDB.Close() + } + }, nil +} + +// It returns a cachex.Cacher instance, a function to close the cache, and an error +func InitCacher(ctx context.Context) (cachex.Cacher, func(), error) { + cfg := config.C.Storage.Cache + + var cache cachex.Cacher + switch cfg.Type { + case "redis": + cache = cachex.NewRedisCache(cachex.RedisConfig{ + Addr: cfg.Redis.Addr, + DB: cfg.Redis.DB, + //Username: cfg.Redis.Username, + Password: cfg.Redis.Password, + }, cachex.WithDelimiter(cfg.Delimiter)) + case "badger": + cache = cachex.NewBadgerCache(cachex.BadgerConfig{ + Path: cfg.Badger.Path, + }, cachex.WithDelimiter(cfg.Delimiter)) + default: + cache = cachex.NewMemoryCache(cachex.MemoryConfig{ + CleanupInterval: time.Second * time.Duration(cfg.Memory.CleanupInterval), + }, cachex.WithDelimiter(cfg.Delimiter)) + } + + return cache, func() { + _ = cache.Close(ctx) + }, nil +} + +func InitAuth(ctx context.Context) (jwtx.Auther, func(), error) { + cfg := config.C.Middleware.Auth + var opts []jwtx.Option + opts = append(opts, jwtx.SetExpired(cfg.Expired)) + opts = append(opts, jwtx.SetSigningKey(cfg.SigningKey, cfg.OldSigningKey)) + + var method jwt.SigningMethod + switch cfg.SigningMethod { + case "HS256": + method = jwt.SigningMethodHS256 + case "HS384": + method = jwt.SigningMethodHS384 + default: + method = jwt.SigningMethodHS512 + } + opts = append(opts, jwtx.SetSigningMethod(method)) + + var cache cachex.Cacher + switch cfg.Store.Type { + case "redis": + cache = cachex.NewRedisCache(cachex.RedisConfig{ + Addr: cfg.Store.Redis.Addr, + DB: cfg.Store.Redis.DB, + //Username: cfg.Store.Redis.Username, + Password: cfg.Store.Redis.Password, + }, cachex.WithDelimiter(cfg.Store.Delimiter)) + case "badger": + cache = cachex.NewBadgerCache(cachex.BadgerConfig{ + Path: cfg.Store.Badger.Path, + }, cachex.WithDelimiter(cfg.Store.Delimiter)) + default: + cache = cachex.NewMemoryCache(cachex.MemoryConfig{ + CleanupInterval: time.Second * time.Duration(cfg.Store.Memory.CleanupInterval), + }, cachex.WithDelimiter(cfg.Store.Delimiter)) + } + + auth := jwtx.New(jwtx.NewStoreWithCache(cache), opts...) + return auth, func() { + _ = auth.Release(ctx) + }, nil +} diff --git a/internal/wirex/wire.go b/internal/wirex/wire.go new file mode 100644 index 0000000..5fada4f --- /dev/null +++ b/internal/wirex/wire.go @@ -0,0 +1,27 @@ +//go:build wireinject +// +build wireinject + +package wirex + +// The build tag makes sure the stub is not built in the final build. + +import ( + "context" + + "github.com/google/wire" + + "github.guxuan/haibei/internal/mods" + "github.guxuan/haibei/pkg/util" +) + +func BuildInjector(ctx context.Context) (*Injector, func(), error) { + wire.Build( + InitCacher, + InitDB, + InitAuth, + wire.NewSet(wire.Struct(new(util.Trans), "*")), + wire.NewSet(wire.Struct(new(Injector), "*")), + mods.Set, + ) // end + return new(Injector), nil, nil +} diff --git a/internal/wirex/wire_gen.go b/internal/wirex/wire_gen.go new file mode 100644 index 0000000..16968cc --- /dev/null +++ b/internal/wirex/wire_gen.go @@ -0,0 +1,401 @@ +// Code generated by Wire. DO NOT EDIT. + +//go:generate go run -mod=mod github.com/google/wire/cmd/wire +//go:build !wireinject +// +build !wireinject + +package wirex + +import ( + "context" + "github.guxuan/haibei/internal/mods" + "github.guxuan/haibei/internal/mods/activity" + api5 "github.guxuan/haibei/internal/mods/activity/api" + biz5 "github.guxuan/haibei/internal/mods/activity/biz" + dal5 "github.guxuan/haibei/internal/mods/activity/dal" + "github.guxuan/haibei/internal/mods/app" + api7 "github.guxuan/haibei/internal/mods/app/api" + biz7 "github.guxuan/haibei/internal/mods/app/biz" + dal7 "github.guxuan/haibei/internal/mods/app/dal" + "github.guxuan/haibei/internal/mods/common" + api3 "github.guxuan/haibei/internal/mods/common/api" + biz3 "github.guxuan/haibei/internal/mods/common/biz" + dal3 "github.guxuan/haibei/internal/mods/common/dal" + "github.guxuan/haibei/internal/mods/customer" + api2 "github.guxuan/haibei/internal/mods/customer/api" + biz2 "github.guxuan/haibei/internal/mods/customer/biz" + dal2 "github.guxuan/haibei/internal/mods/customer/dal" + "github.guxuan/haibei/internal/mods/point" + api6 "github.guxuan/haibei/internal/mods/point/api" + biz6 "github.guxuan/haibei/internal/mods/point/biz" + dal6 "github.guxuan/haibei/internal/mods/point/dal" + "github.guxuan/haibei/internal/mods/product" + api4 "github.guxuan/haibei/internal/mods/product/api" + biz4 "github.guxuan/haibei/internal/mods/product/biz" + dal4 "github.guxuan/haibei/internal/mods/product/dal" + "github.guxuan/haibei/internal/mods/rbac" + "github.guxuan/haibei/internal/mods/rbac/api" + "github.guxuan/haibei/internal/mods/rbac/biz" + "github.guxuan/haibei/internal/mods/rbac/dal" + "github.guxuan/haibei/pkg/util" +) + +// Injectors from wire.go: + +func BuildInjector(ctx context.Context) (*Injector, func(), error) { + db, cleanup, err := InitDB(ctx) + if err != nil { + return nil, nil, err + } + cacher, cleanup2, err := InitCacher(ctx) + if err != nil { + cleanup() + return nil, nil, err + } + auther, cleanup3, err := InitAuth(ctx) + if err != nil { + cleanup2() + cleanup() + return nil, nil, err + } + trans := &util.Trans{ + DB: db, + } + menu := &dal.Menu{ + DB: db, + } + menuResource := &dal.MenuResource{ + DB: db, + } + roleMenu := &dal.RoleMenu{ + DB: db, + } + bizMenu := &biz.Menu{ + Cache: cacher, + Trans: trans, + MenuDAL: menu, + MenuResourceDAL: menuResource, + RoleMenuDAL: roleMenu, + } + apiMenu := &api.Menu{ + MenuBIZ: bizMenu, + } + role := &dal.Role{ + DB: db, + } + userRole := &dal.UserRole{ + DB: db, + } + bizRole := &biz.Role{ + Cache: cacher, + Trans: trans, + RoleDAL: role, + RoleMenuDAL: roleMenu, + UserRoleDAL: userRole, + } + apiRole := &api.Role{ + RoleBIZ: bizRole, + } + user := &dal.User{ + DB: db, + } + bizUser := &biz.User{ + Cache: cacher, + Trans: trans, + UserDAL: user, + UserRoleDAL: userRole, + } + apiUser := &api.User{ + UserBIZ: bizUser, + } + login := &biz.Login{ + Cache: cacher, + Auth: auther, + UserDAL: user, + UserRoleDAL: userRole, + MenuDAL: menu, + UserBIZ: bizUser, + } + apiLogin := &api.Login{ + LoginBIZ: login, + } + logger := &dal.Logger{ + DB: db, + } + bizLogger := &biz.Logger{ + LoggerDAL: logger, + } + apiLogger := &api.Logger{ + LoggerBIZ: bizLogger, + } + casbinx := &rbac.Casbinx{ + Cache: cacher, + MenuDAL: menu, + MenuResourceDAL: menuResource, + RoleDAL: role, + } + rbacRBAC := &rbac.RBAC{ + DB: db, + MenuAPI: apiMenu, + RoleAPI: apiRole, + UserAPI: apiUser, + LoginAPI: apiLogin, + LoggerAPI: apiLogger, + Casbinx: casbinx, + } + dalCustomer := &dal2.Customer{ + DB: db, + } + bizCustomer := &biz2.Customer{ + Cache: cacher, + Trans: trans, + CustomerDAL: dalCustomer, + Auth: auther, + } + apiCustomer := &api2.Customer{ + CustomerBIZ: bizCustomer, + } + balance := &dal2.Balance{ + DB: db, + } + bizBalance := &biz2.Balance{ + Trans: trans, + BalanceDAL: balance, + } + apiBalance := &api2.Balance{ + BalanceBIZ: bizBalance, + } + productOrder := &dal2.ProductOrder{ + DB: db, + } + bizProductOrder := &biz2.ProductOrder{ + Trans: trans, + ProductOrderDAL: productOrder, + } + apiProductOrder := &api2.ProductOrder{ + ProductOrderBIZ: bizProductOrder, + } + customerCustomer := &customer.Customer{ + DB: db, + CustomerAPI: apiCustomer, + BalanceAPI: apiBalance, + ProductOrderAPI: apiProductOrder, + } + dalCommon := &dal3.Common{ + DB: db, + } + bizCommon := &biz3.Common{ + Trans: trans, + CommonDAL: dalCommon, + } + apiCommon := &api3.Common{ + CommonBIZ: bizCommon, + } + banner := &dal3.Banner{ + DB: db, + } + bizBanner := &biz3.Banner{ + Trans: trans, + BannerDAL: banner, + } + apiBanner := &api3.Banner{ + BannerBIZ: bizBanner, + } + notice := &dal3.Notice{ + DB: db, + } + bizNotice := &biz3.Notice{ + Trans: trans, + NoticeDAL: notice, + } + apiNotice := &api3.Notice{ + NoticeBIZ: bizNotice, + } + company := &dal3.Company{ + DB: db, + } + bizCompany := &biz3.Company{ + Trans: trans, + CompanyDAL: company, + } + apiCompany := &api3.Company{ + CompanyBIZ: bizCompany, + } + area := &dal3.Area{ + DB: db, + } + bizArea := &biz3.Area{ + Trans: trans, + AreaDAL: area, + } + apiArea := &api3.Area{ + AreaBIZ: bizArea, + } + receptionCenter := &dal3.ReceptionCenter{ + DB: db, + } + bizReceptionCenter := &biz3.ReceptionCenter{ + Trans: trans, + ReceptionCenterDAL: receptionCenter, + } + apiReceptionCenter := &api3.ReceptionCenter{ + ReceptionCenterBIZ: bizReceptionCenter, + } + commonCommon := &common.Common{ + DB: db, + CommonAPI: apiCommon, + BannerAPI: apiBanner, + NoticeAPI: apiNotice, + CompanyAPI: apiCompany, + AreaAPI: apiArea, + ReceptionCenterAPI: apiReceptionCenter, + } + dalProduct := &dal4.Product{ + DB: db, + } + bizProduct := &biz4.Product{ + Trans: trans, + ProductDAL: dalProduct, + } + apiProduct := &api4.Product{ + ProductBIZ: bizProduct, + } + productCategory := &dal4.ProductCategory{ + DB: db, + } + bizProductCategory := &biz4.ProductCategory{ + Trans: trans, + ProductCategoryDAL: productCategory, + } + apiProductCategory := &api4.ProductCategory{ + ProductCategoryBIZ: bizProductCategory, + } + productProduct := &product.Product{ + DB: db, + ProductAPI: apiProduct, + ProductCategoryAPI: apiProductCategory, + } + dalActivity := &dal5.Activity{ + DB: db, + } + bizActivity := &biz5.Activity{ + Trans: trans, + ActivityDAL: dalActivity, + } + apiActivity := &api5.Activity{ + ActivityBIZ: bizActivity, + } + questionnaire := &dal5.Questionnaire{ + DB: db, + } + bizQuestionnaire := &biz5.Questionnaire{ + Trans: trans, + QuestionnaireDAL: questionnaire, + } + apiQuestionnaire := &api5.Questionnaire{ + QuestionnaireBIZ: bizQuestionnaire, + } + activityDetail := &dal5.ActivityDetail{ + DB: db, + } + bizActivityDetail := &biz5.ActivityDetail{ + Trans: trans, + ActivityDetailDAL: activityDetail, + } + apiActivityDetail := &api5.ActivityDetail{ + ActivityDetailBIZ: bizActivityDetail, + } + qeustionDetail := &dal5.QeustionDetail{ + DB: db, + } + bizQeustionDetail := &biz5.QeustionDetail{ + Trans: trans, + QeustionDetailDAL: qeustionDetail, + } + apiQeustionDetail := &api5.QeustionDetail{ + QeustionDetailBIZ: bizQeustionDetail, + } + activityActivity := &activity.Activity{ + DB: db, + ActivityAPI: apiActivity, + QuestionnaireAPI: apiQuestionnaire, + ActivityDetailAPI: apiActivityDetail, + QeustionDetailAPI: apiQeustionDetail, + } + dalPoint := &dal6.Point{ + DB: db, + } + bizPoint := &biz6.Point{ + Trans: trans, + PointDAL: dalPoint, + } + apiPoint := &api6.Point{ + PointBIZ: bizPoint, + } + grade := &dal6.Grade{ + DB: db, + } + bizGrade := &biz6.Grade{ + Trans: trans, + GradeDAL: grade, + } + apiGrade := &api6.Grade{ + GradeBIZ: bizGrade, + } + pointPoint := &point.Point{ + DB: db, + PointAPI: apiPoint, + GradeAPI: apiGrade, + } + dalApp := &dal7.App{ + DB: db, + } + bizApp := &biz7.App{ + DB: db, + Trans: trans, + AppDAL: dalApp, + BannerDal: banner, + NoticeDal: notice, + ProductDal: dalProduct, + ProductCategoryDal: productCategory, + ProductOrderDal: productOrder, + ActivityDal: dalActivity, + ActivityDetailDal: activityDetail, + QuestionnaireDal: questionnaire, + QeustionDetailDal: qeustionDetail, + CustomerDal: dalCustomer, + BalanceDal: balance, + } + apiApp := &api7.App{ + AppBIZ: bizApp, + CustomerBIZ: bizCustomer, + BannerBIZ: bizBanner, + ActivityBIZ: bizActivity, + QuestionnaireBIZ: bizQuestionnaire, + ProductBIZ: bizProduct, + } + appApp := &app.App{ + DB: db, + AppAPI: apiApp, + } + modsMods := &mods.Mods{ + RBAC: rbacRBAC, + Customer: customerCustomer, + Common: commonCommon, + Product: productProduct, + Activity: activityActivity, + Point: pointPoint, + App: appApp, + } + injector := &Injector{ + DB: db, + Cache: cacher, + Auth: auther, + M: modsMods, + } + return injector, func() { + cleanup3() + cleanup2() + cleanup() + }, nil +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..d63ae74 --- /dev/null +++ b/main.go @@ -0,0 +1,35 @@ +package main + +import ( + "github.guxuan/haibei/cmd" + "os" + + "github.com/urfave/cli/v2" +) + +// Usage: go build -ldflags "-X main.VERSION=x.x.x" +var VERSION = "v1.0.0" + +// @title haibei +// @version v1.0.0 +// @description 中海小程序 +// @securityDefinitions.apikey ApiKeyAuth +// @in header +// @name Authorization +// @schemes http https +// @basePath / +func main() { + app := cli.NewApp() + app.Name = "haibei" + app.Version = VERSION + app.Usage = "中海小程序" + app.Commands = []*cli.Command{ + cmd.StartCmd(), + cmd.StopCmd(), + cmd.VersionCmd(VERSION), + } + err := app.Run(os.Args) + if err != nil { + panic(err) + } +} diff --git a/pkg/cachex/badger.go b/pkg/cachex/badger.go new file mode 100644 index 0000000..26bfdc8 --- /dev/null +++ b/pkg/cachex/badger.go @@ -0,0 +1,167 @@ +package cachex + +import ( + "context" + "fmt" + "strings" + "time" + "unsafe" + + "github.com/dgraph-io/badger/v3" +) + +type BadgerConfig struct { + Path string +} + +// Create badger-based cache +func NewBadgerCache(cfg BadgerConfig, opts ...Option) Cacher { + defaultOpts := &options{ + Delimiter: defaultDelimiter, + } + + for _, o := range opts { + o(defaultOpts) + } + + badgerOpts := badger.DefaultOptions(cfg.Path) + badgerOpts = badgerOpts.WithLoggingLevel(badger.ERROR) + db, err := badger.Open(badgerOpts) + if err != nil { + panic(err) + } + + return &badgerCache{ + opts: defaultOpts, + db: db, + } +} + +type badgerCache struct { + opts *options + db *badger.DB +} + +func (a *badgerCache) getKey(ns, key string) string { + return fmt.Sprintf("%s%s%s", ns, a.opts.Delimiter, key) +} + +func (a *badgerCache) strToBytes(s string) []byte { + return *(*[]byte)(unsafe.Pointer( + &struct { + string + Cap int + }{s, len(s)}, + )) +} + +func (a *badgerCache) bytesToStr(b []byte) string { + return *(*string)(unsafe.Pointer(&b)) +} + +func (a *badgerCache) Set(ctx context.Context, ns, key, value string, expiration ...time.Duration) error { + return a.db.Update(func(txn *badger.Txn) error { + entry := badger.NewEntry(a.strToBytes(a.getKey(ns, key)), a.strToBytes(value)) + if len(expiration) > 0 { + entry = entry.WithTTL(expiration[0]) + } + return txn.SetEntry(entry) + }) +} + +func (a *badgerCache) Get(ctx context.Context, ns, key string) (string, bool, error) { + value := "" + ok := false + err := a.db.View(func(txn *badger.Txn) error { + item, err := txn.Get(a.strToBytes(a.getKey(ns, key))) + if err != nil { + if err == badger.ErrKeyNotFound { + return nil + } + return err + } + ok = true + val, err := item.ValueCopy(nil) + value = a.bytesToStr(val) + return err + }) + if err != nil { + return "", false, err + } + return value, ok, nil +} + +func (a *badgerCache) Exists(ctx context.Context, ns, key string) (bool, error) { + exists := false + err := a.db.View(func(txn *badger.Txn) error { + _, err := txn.Get(a.strToBytes(a.getKey(ns, key))) + if err != nil { + if err == badger.ErrKeyNotFound { + return nil + } + return err + } + exists = true + return nil + }) + return exists, err +} + +func (a *badgerCache) Delete(ctx context.Context, ns, key string) error { + b, err := a.Exists(ctx, ns, key) + if err != nil { + return err + } else if !b { + return nil + } + + return a.db.Update(func(txn *badger.Txn) error { + return txn.Delete(a.strToBytes(a.getKey(ns, key))) + }) +} + +func (a *badgerCache) GetAndDelete(ctx context.Context, ns, key string) (string, bool, error) { + value, ok, err := a.Get(ctx, ns, key) + if err != nil { + return "", false, err + } else if !ok { + return "", false, nil + } + + err = a.db.Update(func(txn *badger.Txn) error { + return txn.Delete(a.strToBytes(a.getKey(ns, key))) + }) + if err != nil { + return "", false, err + } + + return value, true, nil +} + +func (a *badgerCache) Iterator(ctx context.Context, ns string, fn func(ctx context.Context, key, value string) bool) error { + return a.db.View(func(txn *badger.Txn) error { + iterOpts := badger.DefaultIteratorOptions + iterOpts.Prefix = a.strToBytes(a.getKey(ns, "")) + it := txn.NewIterator(iterOpts) + defer it.Close() + + it.Rewind() + for it.Valid() { + item := it.Item() + val, err := item.ValueCopy(nil) + if err != nil { + return err + } + key := a.bytesToStr(item.Key()) + if !fn(ctx, strings.TrimPrefix(key, a.getKey(ns, "")), a.bytesToStr(val)) { + break + } + it.Next() + } + return nil + }) +} + +func (a *badgerCache) Close(ctx context.Context) error { + return a.db.Close() +} diff --git a/pkg/cachex/badger_test.go b/pkg/cachex/badger_test.go new file mode 100644 index 0000000..1fe6caf --- /dev/null +++ b/pkg/cachex/badger_test.go @@ -0,0 +1,56 @@ +package cachex + +import ( + "context" + "fmt" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestBadgerCache(t *testing.T) { + assert := assert.New(t) + + cache := NewBadgerCache(BadgerConfig{ + Path: "./tmp/badger", + }) + + ctx := context.Background() + err := cache.Set(ctx, "tt", "foo", "bar") + assert.Nil(err) + + val, exists, err := cache.Get(ctx, "tt", "foo") + assert.Nil(err) + assert.True(exists) + assert.Equal("bar", val) + + err = cache.Delete(ctx, "tt", "foo") + assert.Nil(err) + + val, exists, err = cache.Get(ctx, "tt", "foo") + assert.Nil(err) + assert.False(exists) + assert.Equal("", val) + + tmap := make(map[string]bool) + for i := 0; i < 10; i++ { + key := fmt.Sprintf("foo%d", i) + err = cache.Set(ctx, "tt", key, "bar") + assert.Nil(err) + tmap[key] = true + + err = cache.Set(ctx, "ff", key, "bar") + assert.Nil(err) + } + + err = cache.Iterator(ctx, "tt", func(ctx context.Context, key, value string) bool { + assert.True(tmap[key]) + assert.Equal("bar", value) + t.Log(key, value) + return true + }) + assert.Nil(err) + + err = cache.Close(ctx) + assert.Nil(err) +} diff --git a/pkg/cachex/cache.go b/pkg/cachex/cache.go new file mode 100644 index 0000000..3aa789d --- /dev/null +++ b/pkg/cachex/cache.go @@ -0,0 +1,119 @@ +package cachex + +import ( + "context" + "fmt" + "strings" + "time" + + "github.com/patrickmn/go-cache" +) + +// Cacher is the interface that wraps the basic Get, Set, and Delete methods. +type Cacher interface { + Set(ctx context.Context, ns, key, value string, expiration ...time.Duration) error + Get(ctx context.Context, ns, key string) (string, bool, error) + GetAndDelete(ctx context.Context, ns, key string) (string, bool, error) + Exists(ctx context.Context, ns, key string) (bool, error) + Delete(ctx context.Context, ns, key string) error + Iterator(ctx context.Context, ns string, fn func(ctx context.Context, key, value string) bool) error + Close(ctx context.Context) error +} + +var defaultDelimiter = ":" + +type options struct { + Delimiter string +} + +type Option func(*options) + +func WithDelimiter(delimiter string) Option { + return func(o *options) { + o.Delimiter = delimiter + } +} + +type MemoryConfig struct { + CleanupInterval time.Duration +} + +func NewMemoryCache(cfg MemoryConfig, opts ...Option) Cacher { + defaultOpts := &options{ + Delimiter: defaultDelimiter, + } + + for _, o := range opts { + o(defaultOpts) + } + + return &memCache{ + opts: defaultOpts, + cache: cache.New(0, cfg.CleanupInterval), + } +} + +type memCache struct { + opts *options + cache *cache.Cache +} + +func (a *memCache) getKey(ns, key string) string { + return fmt.Sprintf("%s%s%s", ns, a.opts.Delimiter, key) +} + +func (a *memCache) Set(ctx context.Context, ns, key, value string, expiration ...time.Duration) error { + var exp time.Duration + if len(expiration) > 0 { + exp = expiration[0] + } + + a.cache.Set(a.getKey(ns, key), value, exp) + return nil +} + +func (a *memCache) Get(ctx context.Context, ns, key string) (string, bool, error) { + val, ok := a.cache.Get(a.getKey(ns, key)) + if !ok { + return "", false, nil + } + return val.(string), ok, nil +} + +func (a *memCache) Exists(ctx context.Context, ns, key string) (bool, error) { + _, ok := a.cache.Get(a.getKey(ns, key)) + return ok, nil +} + +func (a *memCache) Delete(ctx context.Context, ns, key string) error { + a.cache.Delete(a.getKey(ns, key)) + return nil +} + +func (a *memCache) GetAndDelete(ctx context.Context, ns, key string) (string, bool, error) { + value, ok, err := a.Get(ctx, ns, key) + if err != nil { + return "", false, err + } else if !ok { + return "", false, nil + } + + a.cache.Delete(a.getKey(ns, key)) + return value, true, nil +} + +func (a *memCache) Iterator(ctx context.Context, ns string, fn func(ctx context.Context, key, value string) bool) error { + for k, v := range a.cache.Items() { + if strings.HasPrefix(k, a.getKey(ns, "")) { + if !fn(ctx, strings.TrimPrefix(k, a.getKey(ns, "")), v.Object.(string)) { + break + } + } + } + return nil +} + +func (a *memCache) Close(ctx context.Context) error { + a.cache.Flush() + return nil +} diff --git a/pkg/cachex/redis.go b/pkg/cachex/redis.go new file mode 100644 index 0000000..5b1acc1 --- /dev/null +++ b/pkg/cachex/redis.go @@ -0,0 +1,172 @@ +package cachex + +import ( + "context" + "fmt" + "strings" + "time" + + "github.com/redis/go-redis/v9" +) + +type RedisConfig struct { + Addr string + Username string + Password string + DB int +} + +// Create redis-based cache +func NewRedisCache(cfg RedisConfig, opts ...Option) Cacher { + cli := redis.NewClient(&redis.Options{ + Addr: cfg.Addr, + //Username: cfg.Username, + Password: cfg.Password, + DB: cfg.DB, + }) + + return newRedisCache(cli, opts...) +} + +// Use redis client create cache +func NewRedisCacheWithClient(cli *redis.Client, opts ...Option) Cacher { + return newRedisCache(cli, opts...) +} + +// Use redis cluster client create cache +func NewRedisCacheWithClusterClient(cli *redis.ClusterClient, opts ...Option) Cacher { + return newRedisCache(cli, opts...) +} + +func newRedisCache(cli redisClienter, opts ...Option) Cacher { + defaultOpts := &options{ + Delimiter: defaultDelimiter, + } + + for _, o := range opts { + o(defaultOpts) + } + + return &redisCache{ + opts: defaultOpts, + cli: cli, + } +} + +type redisClienter interface { + Set(ctx context.Context, key string, value interface{}, expiration time.Duration) *redis.StatusCmd + Get(ctx context.Context, key string) *redis.StringCmd + Exists(ctx context.Context, keys ...string) *redis.IntCmd + Del(ctx context.Context, keys ...string) *redis.IntCmd + Scan(ctx context.Context, cursor uint64, match string, count int64) *redis.ScanCmd + Close() error +} + +type redisCache struct { + opts *options + cli redisClienter +} + +func (a *redisCache) getKey(ns, key string) string { + return fmt.Sprintf("%s%s%s", ns, a.opts.Delimiter, key) +} + +func (a *redisCache) Set(ctx context.Context, ns, key, value string, expiration ...time.Duration) error { + var exp time.Duration + if len(expiration) > 0 { + exp = expiration[0] + } + + cmd := a.cli.Set(ctx, a.getKey(ns, key), value, exp) + return cmd.Err() +} + +func (a *redisCache) Get(ctx context.Context, ns, key string) (string, bool, error) { + cmd := a.cli.Get(ctx, a.getKey(ns, key)) + if err := cmd.Err(); err != nil { + if err == redis.Nil { + return "", false, nil + } + return "", false, err + } + return cmd.Val(), true, nil +} + +func (a *redisCache) Exists(ctx context.Context, ns, key string) (bool, error) { + cmd := a.cli.Exists(ctx, a.getKey(ns, key)) + if err := cmd.Err(); err != nil { + return false, err + } + return cmd.Val() > 0, nil +} + +func (a *redisCache) Delete(ctx context.Context, ns, key string) error { + b, err := a.Exists(ctx, ns, key) + if err != nil { + return err + } else if !b { + return nil + } + + cmd := a.cli.Del(ctx, a.getKey(ns, key)) + if err := cmd.Err(); err != nil && err != redis.Nil { + return err + } + return nil +} + +func (a *redisCache) GetAndDelete(ctx context.Context, ns, key string) (string, bool, error) { + value, ok, err := a.Get(ctx, ns, key) + if err != nil { + return "", false, err + } else if !ok { + return "", false, nil + } + + cmd := a.cli.Del(ctx, a.getKey(ns, key)) + if err := cmd.Err(); err != nil && err != redis.Nil { + return "", false, err + } + return value, true, nil +} + +func (a *redisCache) Iterator(ctx context.Context, ns string, fn func(ctx context.Context, key, value string) bool) error { + var cursor uint64 = 0 + +LB_LOOP: + for { + cmd := a.cli.Scan(ctx, cursor, a.getKey(ns, "*"), 100) + if err := cmd.Err(); err != nil { + return err + } + + keys, c, err := cmd.Result() + if err != nil { + return err + } + + for _, key := range keys { + cmd := a.cli.Get(ctx, key) + if err := cmd.Err(); err != nil { + if err == redis.Nil { + continue + } + return err + } + if next := fn(ctx, strings.TrimPrefix(key, a.getKey(ns, "")), cmd.Val()); !next { + break LB_LOOP + } + } + + if c == 0 { + break + } + cursor = c + } + + return nil +} + +func (a *redisCache) Close(ctx context.Context) error { + return a.cli.Close() +} diff --git a/pkg/cachex/redis_test.go b/pkg/cachex/redis_test.go new file mode 100644 index 0000000..b9252eb --- /dev/null +++ b/pkg/cachex/redis_test.go @@ -0,0 +1,56 @@ +package cachex + +import ( + "context" + "fmt" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestRedisCache(t *testing.T) { + assert := assert.New(t) + + cache := NewRedisCache(RedisConfig{ + Addr: "localhost:6379", + DB: 1, + }) + + ctx := context.Background() + err := cache.Set(ctx, "tt", "foo", "bar") + assert.Nil(err) + + val, exists, err := cache.Get(ctx, "tt", "foo") + assert.Nil(err) + assert.True(exists) + assert.Equal("bar", val) + + err = cache.Delete(ctx, "tt", "foo") + assert.Nil(err) + + val, exists, err = cache.Get(ctx, "tt", "foo") + assert.Nil(err) + assert.False(exists) + assert.Equal("", val) + + tmap := make(map[string]bool) + for i := 0; i < 10; i++ { + key := fmt.Sprintf("foo%d", i) + err = cache.Set(ctx, "tt", key, "bar") + assert.Nil(err) + tmap[key] = true + + err = cache.Set(ctx, "ff", key, "bar") + assert.Nil(err) + } + + err = cache.Iterator(ctx, "tt", func(ctx context.Context, key, value string) bool { + assert.True(tmap[key]) + assert.Equal("bar", value) + return true + }) + assert.Nil(err) + + err = cache.Close(ctx) + assert.Nil(err) +} diff --git a/pkg/crypto/aes/aes.go b/pkg/crypto/aes/aes.go new file mode 100644 index 0000000..e9d2170 --- /dev/null +++ b/pkg/crypto/aes/aes.go @@ -0,0 +1,69 @@ +package aes + +import ( + "bytes" + "crypto/aes" + "crypto/cipher" + "encoding/base64" +) + +var ( + // Define aes secret key 2^5 + SecretKey = []byte("2985BCFDB5FE43129843DB59825F8647") +) + +func PKCS5Padding(plaintext []byte, blockSize int) []byte { + padding := blockSize - len(plaintext)%blockSize + padtext := bytes.Repeat([]byte{byte(padding)}, padding) + return append(plaintext, padtext...) +} + +func PKCS5UnPadding(origData []byte) []byte { + length := len(origData) + unpadding := int(origData[length-1]) + return origData[:(length - unpadding)] +} + +func Encrypt(origData, key []byte) ([]byte, error) { + block, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + + blockSize := block.BlockSize() + origData = PKCS5Padding(origData, blockSize) + blockMode := cipher.NewCBCEncrypter(block, key[:blockSize]) + crypted := make([]byte, len(origData)) + blockMode.CryptBlocks(crypted, origData) + return crypted, nil +} + +func EncryptToBase64(origData, key []byte) (string, error) { + crypted, err := Encrypt(origData, key) + if err != nil { + return "", err + } + return base64.RawURLEncoding.EncodeToString(crypted), nil +} + +func Decrypt(crypted, key []byte) ([]byte, error) { + block, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + + blockSize := block.BlockSize() + blockMode := cipher.NewCBCDecrypter(block, key[:blockSize]) + origData := make([]byte, len(crypted)) + blockMode.CryptBlocks(origData, crypted) + origData = PKCS5UnPadding(origData) + return origData, nil +} + +func DecryptFromBase64(data string, key []byte) ([]byte, error) { + crypted, err := base64.RawURLEncoding.DecodeString(data) + if err != nil { + return nil, err + } + return Decrypt(crypted, key) +} diff --git a/pkg/crypto/aes/aes_test.go b/pkg/crypto/aes/aes_test.go new file mode 100644 index 0000000..63ccfe4 --- /dev/null +++ b/pkg/crypto/aes/aes_test.go @@ -0,0 +1,23 @@ +package aes + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestAESEncrypt(t *testing.T) { + assert := assert.New(t) + + data := []byte("hello world") + + bs64, err := EncryptToBase64(data, SecretKey) + assert.Nil(err) + assert.NotEmpty(bs64) + + t.Log(bs64) + + result, err := DecryptFromBase64(bs64, SecretKey) + assert.Nil(err) + assert.Equal(data, result) +} diff --git a/pkg/crypto/hash/hash.go b/pkg/crypto/hash/hash.go new file mode 100644 index 0000000..0686d88 --- /dev/null +++ b/pkg/crypto/hash/hash.go @@ -0,0 +1,47 @@ +package hash + +import ( + "crypto/md5" + "crypto/sha1" + "fmt" + + "golang.org/x/crypto/bcrypt" +) + +// md5 hash +func MD5(b []byte) string { + h := md5.New() + _, _ = h.Write(b) + return fmt.Sprintf("%x", h.Sum(nil)) +} + +// md5 hash +func MD5String(s string) string { + return MD5([]byte(s)) +} + +// sha1 hash +func SHA1(b []byte) string { + h := sha1.New() + _, _ = h.Write(b) + return fmt.Sprintf("%x", h.Sum(nil)) +} + +// sha1 hash +func SHA1String(s string) string { + return SHA1([]byte(s)) +} + +// Use bcrypt generate password hash +func GeneratePassword(password string) (string, error) { + b, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) + if err != nil { + return "", err + } + return string(b), nil +} + +// Use bcrypt compare hash password and password +func CompareHashAndPassword(hashedPassword, password string) error { + return bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password)) +} diff --git a/pkg/crypto/hash/hash_test.go b/pkg/crypto/hash/hash_test.go new file mode 100644 index 0000000..035747b --- /dev/null +++ b/pkg/crypto/hash/hash_test.go @@ -0,0 +1,26 @@ +package hash + +import ( + "testing" +) + +func TestGeneratePassword(t *testing.T) { + origin := "abc-123" + hashPwd, err := GeneratePassword(origin) + if err != nil { + t.Error("GeneratePassword Failed: ", err.Error()) + } + t.Log("test password: ", hashPwd, ",length: ", len(hashPwd)) + + if err := CompareHashAndPassword(hashPwd, origin); err != nil { + t.Error("Unmatched password: ", err.Error()) + } +} + +func TestMD5(t *testing.T) { + origin := "abc-123" + hashVal := "6351623c8cef86fefabfa7da046fc619" + if v := MD5String(origin); v != hashVal { + t.Error("Failed to generate MD5 hash: ", v) + } +} diff --git a/pkg/crypto/rand/rand.go b/pkg/crypto/rand/rand.go new file mode 100644 index 0000000..2aeeeb1 --- /dev/null +++ b/pkg/crypto/rand/rand.go @@ -0,0 +1,116 @@ +package rand + +import ( + "bytes" + "crypto/rand" + "errors" +) + +// define a flag that generates a random string +const ( + Ldigit = 1 << iota + LlowerCase + LupperCase + LlowerAndUpperCase = LlowerCase | LupperCase + LdigitAndLowerCase = Ldigit | LlowerCase + LdigitAndUpperCase = Ldigit | LupperCase + LdigitAndLetter = Ldigit | LlowerCase | LupperCase +) + +var ( + digits = []byte("0123456789") + lowerCaseLetters = []byte("abcdefghijklmnopqrstuvwxyz") + upperCaseLetters = []byte("ABCDEFGHIJKLMNOPQRSTUVWXYZ") +) + +// definition error +var ( + ErrInvalidFlag = errors.New("Invalid flag") +) + +// Random generate a random string specifying the length of the random number +// and the random flag +func Random(length, flag int) (string, error) { + if length < 1 { + length = 6 + } + + source, err := getFlagSource(flag) + if err != nil { + return "", err + } + + b, err := randomBytesMod(length, byte(len(source))) + if err != nil { + return "", err + } + + var buf bytes.Buffer + for _, c := range b { + buf.WriteByte(source[c]) + } + + return buf.String(), nil +} + +func getFlagSource(flag int) ([]byte, error) { + var source []byte + + if flag&Ldigit > 0 { + source = append(source, digits...) + } + + if flag&LlowerCase > 0 { + source = append(source, lowerCaseLetters...) + } + + if flag&LupperCase > 0 { + source = append(source, upperCaseLetters...) + } + + sourceLen := len(source) + if sourceLen == 0 { + return nil, ErrInvalidFlag + } + return source, nil +} + +func randomBytesMod(length int, mod byte) ([]byte, error) { + b := make([]byte, length) + max := 255 - 255%mod + i := 0 + +LROOT: + for { + r, err := randomBytes(length + length/4) + if err != nil { + return nil, err + } + + for _, c := range r { + if c >= max { + // Skip this number to avoid modulo bias + continue + } + + b[i] = c % mod + i++ + if i == length { + break LROOT + } + } + + } + + return b, nil +} + +func randomBytes(length int) ([]byte, error) { + b := make([]byte, length) + _, err := rand.Read(b) + if err != nil { + return nil, err + } + + return b, nil +} diff --git a/pkg/crypto/rand/rand_test.go b/pkg/crypto/rand/rand_test.go new file mode 100644 index 0000000..a14a3e7 --- /dev/null +++ b/pkg/crypto/rand/rand_test.go @@ -0,0 +1,27 @@ +package rand + +import ( + "strconv" + "testing" +) + +func TestRandom(t *testing.T) { + digits, err := Random(6, Ldigit) + if err != nil { + t.Error(err.Error()) + return + } else if len(digits) != 6 { + t.Error("invalid digit:", digits) + return + } + + for _, b := range digits { + d, err := strconv.Atoi(string(b)) + if err != nil { + t.Error(err.Error()) + return + } else if d > 10 || d < 0 { + t.Error("invalid digit:", d) + } + } +} diff --git a/pkg/encoding/json/json.go b/pkg/encoding/json/json.go new file mode 100644 index 0000000..681892a --- /dev/null +++ b/pkg/encoding/json/json.go @@ -0,0 +1,25 @@ +package json + +import ( + "fmt" + + jsoniter "github.com/json-iterator/go" +) + +var ( + json = jsoniter.ConfigCompatibleWithStandardLibrary + Marshal = json.Marshal + Unmarshal = json.Unmarshal + MarshalIndent = json.MarshalIndent + NewDecoder = json.NewDecoder + NewEncoder = json.NewEncoder +) + +func MarshalToString(v interface{}) string { + s, err := jsoniter.MarshalToString(v) + if err != nil { + fmt.Println("Failed to marshal json string: " + err.Error()) + return "" + } + return s +} diff --git a/pkg/encoding/toml/toml.go b/pkg/encoding/toml/toml.go new file mode 100644 index 0000000..6ece3b2 --- /dev/null +++ b/pkg/encoding/toml/toml.go @@ -0,0 +1,32 @@ +package toml + +import ( + "bytes" + + "github.com/BurntSushi/toml" +) + +var ( + Unmarshal = toml.Unmarshal + DecodeFile = toml.DecodeFile + Decode = toml.Decode +) + +type Value = toml.Primitive + +func Marshal(v interface{}) ([]byte, error) { + buf := new(bytes.Buffer) + err := toml.NewEncoder(buf).Encode(v) + if err != nil { + return nil, err + } + return buf.Bytes(), nil +} + +func MarshalToString(v interface{}) (string, error) { + b, err := Marshal(v) + if err != nil { + return "", err + } + return string(b), nil +} diff --git a/pkg/encoding/toml/toml_test.go b/pkg/encoding/toml/toml_test.go new file mode 100644 index 0000000..a71cf22 --- /dev/null +++ b/pkg/encoding/toml/toml_test.go @@ -0,0 +1,35 @@ +package toml + +import "testing" + +func TestTomlDecode(t *testing.T) { + var config struct { + Middlewares []struct { + Name string `toml:"name"` + Options Value `toml:"options"` + } `toml:"middlewares"` + } + + md, err := Decode(` + middlewares = [ + {name = "ratelimit", options = {max = 10, period = 10}}, + ] + `, &config) + if err != nil { + t.Error(err) + return + } + + var rateLimitConfig struct { + Max int `toml:"max"` + Period int `toml:"period"` + } + err = md.PrimitiveDecode(config.Middlewares[0].Options, &rateLimitConfig) + if err != nil { + t.Error(err) + return + } + if rateLimitConfig.Max != 10 || rateLimitConfig.Period != 10 { + t.Errorf("Expected {Max: 10, Period: 10}, got %v", rateLimitConfig) + } +} diff --git a/pkg/encoding/yaml/yaml.go b/pkg/encoding/yaml/yaml.go new file mode 100644 index 0000000..13e1dcc --- /dev/null +++ b/pkg/encoding/yaml/yaml.go @@ -0,0 +1,12 @@ +package yaml + +import ( + "gopkg.in/yaml.v3" +) + +var ( + Marshal = yaml.Marshal + Unmarshal = yaml.Unmarshal + NewDecoder = yaml.NewDecoder + NewEncoder = yaml.NewEncoder +) diff --git a/pkg/errors/errors.go b/pkg/errors/errors.go new file mode 100644 index 0000000..e6142f8 --- /dev/null +++ b/pkg/errors/errors.go @@ -0,0 +1,273 @@ +// Package errors provides a way to return detailed information +// for an request error. The error is normally JSON encoded. +package errors + +import ( + "encoding/json" + "fmt" + "net/http" + "sync" + + "github.com/pkg/errors" +) + +// Define alias +var ( + WithStack = errors.WithStack + Wrap = errors.Wrap + Wrapf = errors.Wrapf + Is = errors.Is + Errorf = errors.Errorf +) + +const ( + DefaultBadRequestID = "bad_request" + DefaultUnauthorizedID = "unauthorized" + DefaultForbiddenID = "forbidden" + DefaultNotFoundID = "not_found" + DefaultMethodNotAllowedID = "method_not_allowed" + DefaultTooManyRequestsID = "too_many_requests" + DefaultRequestEntityTooLargeID = "request_entity_too_large" + DefaultInternalServerErrorID = "internal_server_error" + DefaultConflictID = "conflict" + DefaultRequestTimeoutID = "request_timeout" +) + +// Customize the error structure for implementation errors.Error interface +type Error struct { + ID string `json:"id,omitempty"` + Code int32 `json:"code,omitempty"` + Detail string `json:"detail,omitempty"` + Status string `json:"status,omitempty"` +} + +func (e *Error) Error() string { + b, _ := json.Marshal(e) + return string(b) +} + +// New generates a custom error. +func New(id, detail string, code int32) error { + return &Error{ + ID: id, + Code: code, + Detail: detail, + Status: http.StatusText(int(code)), + } +} + +// Parse tries to parse a JSON string into an error. If that +// fails, it will set the given string as the error detail. +func Parse(err string) *Error { + e := new(Error) + errr := json.Unmarshal([]byte(err), e) + if errr != nil { + e.Detail = err + } + return e +} + +// BadRequest generates a 400 error. +func BadRequest(id, format string, a ...interface{}) error { + if id == "" { + id = DefaultBadRequestID + } + return &Error{ + ID: id, + Code: http.StatusBadRequest, + Detail: fmt.Sprintf(format, a...), + Status: http.StatusText(http.StatusBadRequest), + } +} + +// Unauthorized generates a 401 error. +func Unauthorized(id, format string, a ...interface{}) error { + if id == "" { + id = DefaultUnauthorizedID + } + return &Error{ + ID: id, + Code: http.StatusUnauthorized, + Detail: fmt.Sprintf(format, a...), + Status: http.StatusText(http.StatusUnauthorized), + } +} + +// Forbidden generates a 403 error. +func Forbidden(id, format string, a ...interface{}) error { + if id == "" { + id = DefaultForbiddenID + } + return &Error{ + ID: id, + Code: http.StatusForbidden, + Detail: fmt.Sprintf(format, a...), + Status: http.StatusText(http.StatusForbidden), + } +} + +// NotFound generates a 404 error. +func NotFound(id, format string, a ...interface{}) error { + if id == "" { + id = DefaultNotFoundID + } + return &Error{ + ID: id, + Code: http.StatusNotFound, + Detail: fmt.Sprintf(format, a...), + Status: http.StatusText(http.StatusNotFound), + } +} + +// MethodNotAllowed generates a 405 error. +func MethodNotAllowed(id, format string, a ...interface{}) error { + if id == "" { + id = DefaultMethodNotAllowedID + } + return &Error{ + ID: id, + Code: http.StatusMethodNotAllowed, + Detail: fmt.Sprintf(format, a...), + Status: http.StatusText(http.StatusMethodNotAllowed), + } +} + +// TooManyRequests generates a 429 error. +func TooManyRequests(id, format string, a ...interface{}) error { + if id == "" { + id = DefaultTooManyRequestsID + } + return &Error{ + ID: id, + Code: http.StatusTooManyRequests, + Detail: fmt.Sprintf(format, a...), + Status: http.StatusText(http.StatusTooManyRequests), + } +} + +// Timeout generates a 408 error. +func Timeout(id, format string, a ...interface{}) error { + if id == "" { + id = DefaultRequestTimeoutID + } + return &Error{ + ID: id, + Code: http.StatusRequestTimeout, + Detail: fmt.Sprintf(format, a...), + Status: http.StatusText(http.StatusRequestTimeout), + } +} + +// Conflict generates a 409 error. +func Conflict(id, format string, a ...interface{}) error { + if id == "" { + id = DefaultConflictID + } + return &Error{ + ID: id, + Code: http.StatusConflict, + Detail: fmt.Sprintf(format, a...), + Status: http.StatusText(http.StatusConflict), + } +} + +// RequestEntityTooLarge generates a 413 error. +func RequestEntityTooLarge(id, format string, a ...interface{}) error { + if id == "" { + id = DefaultRequestEntityTooLargeID + } + return &Error{ + ID: id, + Code: http.StatusRequestEntityTooLarge, + Detail: fmt.Sprintf(format, a...), + Status: http.StatusText(http.StatusRequestEntityTooLarge), + } +} + +// InternalServerError generates a 500 error. +func InternalServerError(id, format string, a ...interface{}) error { + if id == "" { + id = DefaultInternalServerErrorID + } + return &Error{ + ID: id, + Code: http.StatusInternalServerError, + Detail: fmt.Sprintf(format, a...), + Status: http.StatusText(http.StatusInternalServerError), + } +} + +// Equal tries to compare errors +func Equal(err1 error, err2 error) bool { + verr1, ok1 := err1.(*Error) + verr2, ok2 := err2.(*Error) + + if ok1 != ok2 { + return false + } + + if !ok1 { + return err1 == err2 + } + + if verr1.Code != verr2.Code { + return false + } + + return true +} + +// FromError try to convert go error to *Error +func FromError(err error) *Error { + if err == nil { + return nil + } + if verr, ok := err.(*Error); ok && verr != nil { + return verr + } + + return Parse(err.Error()) +} + +// As finds the first error in err's chain that matches *Error +func As(err error) (*Error, bool) { + if err == nil { + return nil, false + } + var merr *Error + if errors.As(err, &merr) { + return merr, true + } + return nil, false +} + +type MultiError struct { + lock *sync.Mutex + Errors []error +} + +func NewMultiError() *MultiError { + return &MultiError{ + lock: &sync.Mutex{}, + Errors: make([]error, 0), + } +} + +func (e *MultiError) Append(err error) { + e.Errors = append(e.Errors, err) +} + +func (e *MultiError) AppendWithLock(err error) { + e.lock.Lock() + defer e.lock.Unlock() + e.Append(err) +} + +func (e *MultiError) HasErrors() bool { + return len(e.Errors) > 0 +} + +func (e *MultiError) Error() string { + b, _ := json.Marshal(e) + return string(b) +} diff --git a/pkg/gormx/gorm.go b/pkg/gormx/gorm.go new file mode 100644 index 0000000..f00543b --- /dev/null +++ b/pkg/gormx/gorm.go @@ -0,0 +1,162 @@ +package gormx + +import ( + "database/sql" + "fmt" + "os" + "path/filepath" + "strings" + "time" + + sdmysql "github.com/go-sql-driver/mysql" + "go.uber.org/zap" + "gorm.io/driver/mysql" + "gorm.io/driver/postgres" + "gorm.io/driver/sqlite" + "gorm.io/gorm" + "gorm.io/gorm/logger" + "gorm.io/gorm/schema" + "gorm.io/plugin/dbresolver" +) + +type ResolverConfig struct { + DBType string // mysql/postgres/sqlite3 + Sources []string + Replicas []string + Tables []string +} + +type Config struct { + Debug bool + PrepareStmt bool + DBType string // mysql/postgres/sqlite3 + DSN string + MaxLifetime int + MaxIdleTime int + MaxOpenConns int + MaxIdleConns int + TablePrefix string + Resolver []ResolverConfig +} + +func New(cfg Config) (*gorm.DB, error) { + var dialector gorm.Dialector + + switch strings.ToLower(cfg.DBType) { + case "mysql": + if err := createDatabaseWithMySQL(cfg.DSN); err != nil { + return nil, err + } + dialector = mysql.Open(cfg.DSN) + case "postgres": + dialector = postgres.Open(cfg.DSN) + case "sqlite3": + _ = os.MkdirAll(filepath.Dir(cfg.DSN), os.ModePerm) + dialector = sqlite.Open(cfg.DSN) + default: + return nil, fmt.Errorf("unsupported database type: %s", cfg.DBType) + } + + ormCfg := &gorm.Config{ + NamingStrategy: schema.NamingStrategy{ + TablePrefix: cfg.TablePrefix, + SingularTable: true, + }, + Logger: logger.Discard, + PrepareStmt: cfg.PrepareStmt, + } + + if cfg.Debug { + ormCfg.Logger = logger.Default + } + + db, err := gorm.Open(dialector, ormCfg) + if err != nil { + return nil, err + } + + if len(cfg.Resolver) > 0 { + resolver := &dbresolver.DBResolver{} + for _, r := range cfg.Resolver { + resolverCfg := dbresolver.Config{} + var open func(dsn string) gorm.Dialector + dbType := strings.ToLower(r.DBType) + switch dbType { + case "mysql": + open = mysql.Open + case "postgres": + open = postgres.Open + case "sqlite3": + open = sqlite.Open + default: + continue + } + + for _, replica := range r.Replicas { + if dbType == "sqlite3" { + _ = os.MkdirAll(filepath.Dir(cfg.DSN), os.ModePerm) + } + resolverCfg.Replicas = append(resolverCfg.Replicas, open(replica)) + } + for _, source := range r.Sources { + if dbType == "sqlite3" { + _ = os.MkdirAll(filepath.Dir(cfg.DSN), os.ModePerm) + } + resolverCfg.Sources = append(resolverCfg.Sources, open(source)) + } + tables := stringSliceToInterfaceSlice(r.Tables) + resolver.Register(resolverCfg, tables...) + zap.L().Info(fmt.Sprintf("Use resolver, #tables: %v, #replicas: %v, #sources: %v \n", + tables, r.Replicas, r.Sources)) + } + + resolver.SetMaxIdleConns(cfg.MaxIdleConns). + SetMaxOpenConns(cfg.MaxOpenConns). + SetConnMaxLifetime(time.Duration(cfg.MaxLifetime) * time.Second). + SetConnMaxIdleTime(time.Duration(cfg.MaxIdleTime) * time.Second) + if err := db.Use(resolver); err != nil { + return nil, err + } + } + + if cfg.Debug { + db = db.Debug() + } + + sqlDB, err := db.DB() + if err != nil { + return nil, err + } + + sqlDB.SetMaxIdleConns(cfg.MaxIdleConns) + sqlDB.SetMaxOpenConns(cfg.MaxOpenConns) + sqlDB.SetConnMaxLifetime(time.Duration(cfg.MaxLifetime) * time.Second) + sqlDB.SetConnMaxIdleTime(time.Duration(cfg.MaxIdleTime) * time.Second) + + return db, nil +} + +func stringSliceToInterfaceSlice(s []string) []interface{} { + r := make([]interface{}, len(s)) + for i, v := range s { + r[i] = v + } + return r +} + +func createDatabaseWithMySQL(dsn string) error { + cfg, err := sdmysql.ParseDSN(dsn) + if err != nil { + return err + } + + db, err := sql.Open("mysql", fmt.Sprintf("%s:%s@tcp(%s)/", cfg.User, cfg.Passwd, cfg.Addr)) + if err != nil { + return err + } + defer db.Close() + + query := fmt.Sprintf("CREATE DATABASE IF NOT EXISTS `%s` DEFAULT CHARACTER SET = `utf8mb4`;", cfg.DBName) + _, err = db.Exec(query) + return err +} diff --git a/pkg/jwtx/cache.go b/pkg/jwtx/cache.go new file mode 100644 index 0000000..30da157 --- /dev/null +++ b/pkg/jwtx/cache.go @@ -0,0 +1,62 @@ +package jwtx + +import ( + "context" + "fmt" + "time" + + "github.com/patrickmn/go-cache" +) + +var defaultDelimiter = ":" + +type MemoryConfig struct { + CleanupInterval time.Duration +} + +func NewMemoryCache(cfg MemoryConfig) Cacher { + return &memCache{ + cache: cache.New(0, cfg.CleanupInterval), + } +} + +type memCache struct { + cache *cache.Cache +} + +func (a *memCache) getKey(ns, key string) string { + return fmt.Sprintf("%s%s%s", ns, defaultDelimiter, key) +} + +func (a *memCache) Set(ctx context.Context, ns, key, value string, expiration ...time.Duration) error { + var exp time.Duration + if len(expiration) > 0 { + exp = expiration[0] + } + + a.cache.Set(a.getKey(ns, key), value, exp) + return nil +} + +func (a *memCache) Get(ctx context.Context, ns, key string) (string, bool, error) { + val, ok := a.cache.Get(a.getKey(ns, key)) + if !ok { + return "", false, nil + } + return val.(string), ok, nil +} + +func (a *memCache) Exists(ctx context.Context, ns, key string) (bool, error) { + _, ok := a.cache.Get(a.getKey(ns, key)) + return ok, nil +} + +func (a *memCache) Delete(ctx context.Context, ns, key string) error { + a.cache.Delete(a.getKey(ns, key)) + return nil +} + +func (a *memCache) Close(ctx context.Context) error { + a.cache.Flush() + return nil +} diff --git a/pkg/jwtx/jwt.go b/pkg/jwtx/jwt.go new file mode 100644 index 0000000..4b15b4d --- /dev/null +++ b/pkg/jwtx/jwt.go @@ -0,0 +1,194 @@ +package jwtx + +import ( + "context" + "errors" + "time" + + "github.com/golang-jwt/jwt" +) + +type Auther interface { + // Generate a JWT (JSON Web Token) with the provided subject. + GenerateToken(ctx context.Context, subject string) (TokenInfo, error) + // Invalidate a token by removing it from the token store. + DestroyToken(ctx context.Context, accessToken string) error + // Parse the subject (or user identifier) from a given access token. + ParseSubject(ctx context.Context, accessToken string) (string, error) + // Release any resources held by the JWTAuth instance. + Release(ctx context.Context) error +} + +const defaultKey = "CG24SDVP8OHPK395GB5G" + +var ErrInvalidToken = errors.New("Invalid token") + +type options struct { + signingMethod jwt.SigningMethod + signingKey []byte + signingKey2 []byte + keyFuncs []func(*jwt.Token) (interface{}, error) + expired int + tokenType string +} +type JwtSubject struct { + ID interface{} `json:"id"` + Typer string `json:"type"` +} + +type Option func(*options) + +func SetSigningMethod(method jwt.SigningMethod) Option { + return func(o *options) { + o.signingMethod = method + } +} + +func SetSigningKey(key, oldKey string) Option { + return func(o *options) { + o.signingKey = []byte(key) + if oldKey != "" && key != oldKey { + o.signingKey2 = []byte(oldKey) + } + } +} + +func SetExpired(expired int) Option { + return func(o *options) { + o.expired = expired + } +} + +func New(store Storer, opts ...Option) Auther { + o := options{ + tokenType: "Bearer", + expired: 7200, + signingMethod: jwt.SigningMethodHS512, + signingKey: []byte(defaultKey), + } + + for _, opt := range opts { + opt(&o) + } + + o.keyFuncs = append(o.keyFuncs, func(t *jwt.Token) (interface{}, error) { + if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok { + return nil, ErrInvalidToken + } + return o.signingKey, nil + }) + + if o.signingKey2 != nil { + o.keyFuncs = append(o.keyFuncs, func(t *jwt.Token) (interface{}, error) { + if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok { + return nil, ErrInvalidToken + } + return o.signingKey2, nil + }) + } + + return &JWTAuth{ + opts: &o, + store: store, + } +} + +type JWTAuth struct { + opts *options + store Storer +} + +func (a *JWTAuth) GenerateToken(ctx context.Context, subject string) (TokenInfo, error) { + now := time.Now() + expiresAt := now.Add(time.Duration(a.opts.expired) * time.Second).Unix() + + token := jwt.NewWithClaims(a.opts.signingMethod, &jwt.StandardClaims{ + IssuedAt: now.Unix(), + ExpiresAt: expiresAt, + NotBefore: now.Unix(), + Subject: subject, + }) + + tokenStr, err := token.SignedString(a.opts.signingKey) + if err != nil { + return nil, err + } + + tokenInfo := &tokenInfo{ + ExpiresAt: expiresAt, + TokenType: a.opts.tokenType, + AccessToken: tokenStr, + } + return tokenInfo, nil +} + +func (a *JWTAuth) parseToken(tokenStr string) (*jwt.StandardClaims, error) { + var ( + token *jwt.Token + err error + ) + + for _, keyFunc := range a.opts.keyFuncs { + token, err = jwt.ParseWithClaims(tokenStr, &jwt.StandardClaims{}, keyFunc) + if err != nil || token == nil || !token.Valid { + continue + } + break + } + + if err != nil || token == nil || !token.Valid { + return nil, ErrInvalidToken + } + + return token.Claims.(*jwt.StandardClaims), nil +} + +func (a *JWTAuth) callStore(fn func(Storer) error) error { + if store := a.store; store != nil { + return fn(store) + } + return nil +} + +func (a *JWTAuth) DestroyToken(ctx context.Context, tokenStr string) error { + claims, err := a.parseToken(tokenStr) + if err != nil { + return err + } + + return a.callStore(func(store Storer) error { + expired := time.Until(time.Unix(claims.ExpiresAt, 0)) + return store.Set(ctx, tokenStr, expired) + }) +} + +func (a *JWTAuth) ParseSubject(ctx context.Context, tokenStr string) (string, error) { + if tokenStr == "" { + return "", ErrInvalidToken + } + + claims, err := a.parseToken(tokenStr) + if err != nil { + return "", err + } + + err = a.callStore(func(store Storer) error { + if exists, err := store.Check(ctx, tokenStr); err != nil { + return err + } else if exists { + return ErrInvalidToken + } + return nil + }) + if err != nil { + return "", err + } + + return claims.Subject, nil +} + +func (a *JWTAuth) Release(ctx context.Context) error { + return a.callStore(func(store Storer) error { + return store.Close(ctx) + }) +} diff --git a/pkg/jwtx/jwt_test.go b/pkg/jwtx/jwt_test.go new file mode 100644 index 0000000..d7e1277 --- /dev/null +++ b/pkg/jwtx/jwt_test.go @@ -0,0 +1,37 @@ +package jwtx + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestAuth(t *testing.T) { + cache := NewMemoryCache(MemoryConfig{CleanupInterval: time.Second}) + + store := NewStoreWithCache(cache) + ctx := context.Background() + jwtAuth := New(store) + + userID := "test" + token, err := jwtAuth.GenerateToken(ctx, userID) + assert.Nil(t, err) + assert.NotNil(t, token) + + id, err := jwtAuth.ParseSubject(ctx, token.GetAccessToken()) + assert.Nil(t, err) + assert.Equal(t, userID, id) + + err = jwtAuth.DestroyToken(ctx, token.GetAccessToken()) + assert.Nil(t, err) + + id, err = jwtAuth.ParseSubject(ctx, token.GetAccessToken()) + assert.NotNil(t, err) + assert.EqualError(t, err, ErrInvalidToken.Error()) + assert.Empty(t, id) + + err = jwtAuth.Release(ctx) + assert.Nil(t, err) +} diff --git a/pkg/jwtx/store.go b/pkg/jwtx/store.go new file mode 100644 index 0000000..01d357e --- /dev/null +++ b/pkg/jwtx/store.go @@ -0,0 +1,68 @@ +package jwtx + +import ( + "context" + "time" +) + +// Storer is the interface that storage the token. +type Storer interface { + Set(ctx context.Context, tokenStr string, expiration time.Duration) error + Delete(ctx context.Context, tokenStr string) error + Check(ctx context.Context, tokenStr string) (bool, error) + Close(ctx context.Context) error +} + +type storeOptions struct { + CacheNS string // default "jwt" +} + +type StoreOption func(*storeOptions) + +func WithCacheNS(ns string) StoreOption { + return func(o *storeOptions) { + o.CacheNS = ns + } +} + +type Cacher interface { + Set(ctx context.Context, ns, key, value string, expiration ...time.Duration) error + Get(ctx context.Context, ns, key string) (string, bool, error) + Exists(ctx context.Context, ns, key string) (bool, error) + Delete(ctx context.Context, ns, key string) error + Close(ctx context.Context) error +} + +func NewStoreWithCache(cache Cacher, opts ...StoreOption) Storer { + s := &storeImpl{ + c: cache, + opts: &storeOptions{ + CacheNS: "jwt", + }, + } + for _, opt := range opts { + opt(s.opts) + } + return s +} + +type storeImpl struct { + opts *storeOptions + c Cacher +} + +func (s *storeImpl) Set(ctx context.Context, tokenStr string, expiration time.Duration) error { + return s.c.Set(ctx, s.opts.CacheNS, tokenStr, "", expiration) +} + +func (s *storeImpl) Delete(ctx context.Context, tokenStr string) error { + return s.c.Delete(ctx, s.opts.CacheNS, tokenStr) +} + +func (s *storeImpl) Check(ctx context.Context, tokenStr string) (bool, error) { + return s.c.Exists(ctx, s.opts.CacheNS, tokenStr) +} + +func (s *storeImpl) Close(ctx context.Context) error { + return s.c.Close(ctx) +} diff --git a/pkg/jwtx/token.go b/pkg/jwtx/token.go new file mode 100644 index 0000000..19f3178 --- /dev/null +++ b/pkg/jwtx/token.go @@ -0,0 +1,34 @@ +package jwtx + +import ( + jsoniter "github.com/json-iterator/go" +) + +type TokenInfo interface { + GetAccessToken() string + GetTokenType() string + GetExpiresAt() int64 + EncodeToJSON() ([]byte, error) +} + +type tokenInfo struct { + AccessToken string `json:"access_token"` + TokenType string `json:"token_type"` + ExpiresAt int64 `json:"expires_at"` +} + +func (t *tokenInfo) GetAccessToken() string { + return t.AccessToken +} + +func (t *tokenInfo) GetTokenType() string { + return t.TokenType +} + +func (t *tokenInfo) GetExpiresAt() int64 { + return t.ExpiresAt +} + +func (t *tokenInfo) EncodeToJSON() ([]byte, error) { + return jsoniter.Marshal(t) +} diff --git a/pkg/logging/gorm.go b/pkg/logging/gorm.go new file mode 100644 index 0000000..c357ec6 --- /dev/null +++ b/pkg/logging/gorm.go @@ -0,0 +1,97 @@ +package logging + +import ( + "time" + + jsoniter "github.com/json-iterator/go" + "github.com/rs/xid" + "gorm.io/gorm" +) + +type Logger struct { + ID string `gorm:"size:20;primaryKey;" json:"id"` // Unique ID + Level string `gorm:"size:20;index;" json:"level"` // Log level + TraceID string `gorm:"size:64;index;" json:"trace_id"` // Trace ID + UserID string `gorm:"size:20;index;" json:"user_id"` // User ID + Tag string `gorm:"size:32;index;" json:"tag"` // Log tag + Message string `gorm:"size:1024;" json:"message"` // Log message + Stack string `gorm:"type:text;" json:"stack"` // Error stack + Data string `gorm:"type:text;" json:"data"` // Log data + CreatedAt time.Time `gorm:"index;" json:"created_at"` // Create time +} + +func NewGormHook(db *gorm.DB) *GormHook { + err := db.AutoMigrate(new(Logger)) + if err != nil { + panic(err) + } + + return &GormHook{ + db: db, + } +} + +// Gorm Logger Hook +type GormHook struct { + db *gorm.DB +} + +func (h *GormHook) Exec(extra map[string]string, b []byte) error { + msg := &Logger{ + ID: xid.New().String(), + } + data := make(map[string]interface{}) + err := jsoniter.Unmarshal(b, &data) + if err != nil { + return err + } + + if v, ok := data["ts"]; ok { + msg.CreatedAt = time.UnixMilli(int64(v.(float64))) + delete(data, "ts") + } + if v, ok := data["msg"]; ok { + msg.Message = v.(string) + delete(data, "msg") + } + if v, ok := data["tag"]; ok { + msg.Tag = v.(string) + delete(data, "tag") + } + if v, ok := data["trace_id"]; ok { + msg.TraceID = v.(string) + delete(data, "trace_id") + } + if v, ok := data["user_id"]; ok { + msg.UserID = v.(string) + delete(data, "user_id") + } + if v, ok := data["level"]; ok { + msg.Level = v.(string) + delete(data, "level") + } + if v, ok := data["stack"]; ok { + msg.Stack = v.(string) + delete(data, "stack") + } + delete(data, "caller") + + for k, v := range extra { + data[k] = v + } + + if len(data) > 0 { + buf, _ := jsoniter.Marshal(data) + msg.Data = string(buf) + } + + return h.db.Create(msg).Error +} + +func (h *GormHook) Close() error { + db, err := h.db.DB() + if err != nil { + return err + } + return db.Close() +} diff --git a/pkg/logging/hook.go b/pkg/logging/hook.go new file mode 100644 index 0000000..ced5c4f --- /dev/null +++ b/pkg/logging/hook.go @@ -0,0 +1,125 @@ +package logging + +import ( + "fmt" + "sync" + "sync/atomic" +) + +type HookExecuter interface { + Exec(extra map[string]string, b []byte) error + Close() error +} + +type hookOptions struct { + maxJobs int + maxWorkers int + extra map[string]string +} + +// Set the number of buffers +func SetHookMaxJobs(maxJobs int) HookOption { + return func(o *hookOptions) { + o.maxJobs = maxJobs + } +} + +// Set the number of worker threads +func SetHookMaxWorkers(maxWorkers int) HookOption { + return func(o *hookOptions) { + o.maxWorkers = maxWorkers + } +} + +// Set extended parameters +func SetHookExtra(extra map[string]string) HookOption { + return func(o *hookOptions) { + o.extra = extra + } +} + +// HookOption a hook parameter options +type HookOption func(*hookOptions) + +// Creates a hook to be added to an instance of logger +func NewHook(exec HookExecuter, opt ...HookOption) *Hook { + opts := &hookOptions{ + maxJobs: 1024, + maxWorkers: 2, + } + + for _, o := range opt { + o(opts) + } + + wg := new(sync.WaitGroup) + wg.Add(opts.maxWorkers) + + h := &Hook{ + opts: opts, + q: make(chan []byte, opts.maxJobs), + wg: wg, + e: exec, + } + h.dispatch() + return h +} + +// Hook to send logs to a mongo database +type Hook struct { + opts *hookOptions + q chan []byte + wg *sync.WaitGroup + e HookExecuter + closed int32 +} + +func (h *Hook) dispatch() { + for i := 0; i < h.opts.maxWorkers; i++ { + go func() { + defer func() { + h.wg.Done() + if r := recover(); r != nil { + fmt.Println("Recovered from panic in logger hook:", r) + } + }() + + for data := range h.q { + err := h.e.Exec(h.opts.extra, data) + if err != nil { + fmt.Println("Failed to write entry:", err.Error()) + } + } + }() + } +} + +func (h *Hook) Write(p []byte) (int, error) { + if atomic.LoadInt32(&h.closed) == 1 { + return len(p), nil + } + if len(h.q) == h.opts.maxJobs { + fmt.Println("Too many jobs, waiting for queue to be empty, discard") + return len(p), nil + } + + data := make([]byte, len(p)) + copy(data, p) + h.q <- data + + return len(p), nil +} + +// Waits for the log queue to be empty +func (h *Hook) Flush() { + if atomic.LoadInt32(&h.closed) == 1 { + return + } + atomic.StoreInt32(&h.closed, 1) + close(h.q) + h.wg.Wait() + err := h.e.Close() + if err != nil { + fmt.Println("Failed to close logger hook:", err.Error()) + } +} diff --git a/pkg/logging/init.go b/pkg/logging/init.go new file mode 100644 index 0000000..dd84d2c --- /dev/null +++ b/pkg/logging/init.go @@ -0,0 +1,158 @@ +package logging + +import ( + "context" + "os" + "path/filepath" + + "github.com/pelletier/go-toml" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + "gopkg.in/natefinch/lumberjack.v2" +) + +type Config struct { + Logger LoggerConfig +} + +type LoggerConfig struct { + Debug bool + Level string // debug/info/warn/error/dpanic/panic/fatal + CallerSkip int + File struct { + Enable bool + Path string + MaxSize int + MaxBackups int + } + Hooks []*HookConfig +} + +type HookConfig struct { + Enable bool + Level string + Type string // gorm + MaxBuffer int + MaxThread int + Options map[string]string + Extra map[string]string +} + +type HookHandlerFunc func(ctx context.Context, hookCfg *HookConfig) (*Hook, error) + +func LoadConfigFromToml(filename string) (*LoggerConfig, error) { + cfg := &Config{} + buf, err := os.ReadFile(filename) + if err != nil { + return nil, err + } + if err := toml.Unmarshal(buf, cfg); err != nil { + return nil, err + } + return &cfg.Logger, nil +} + +func InitWithConfig(ctx context.Context, cfg *LoggerConfig, hookHandle ...HookHandlerFunc) (func(), error) { + var zconfig zap.Config + if cfg.Debug { + cfg.Level = "debug" + zconfig = zap.NewDevelopmentConfig() + } else { + zconfig = zap.NewProductionConfig() + } + + level, err := zapcore.ParseLevel(cfg.Level) + if err != nil { + return nil, err + } + zconfig.Level.SetLevel(level) + + var ( + logger *zap.Logger + cleanFns []func() + ) + + if cfg.File.Enable { + filename := cfg.File.Path + _ = os.MkdirAll(filepath.Dir(filename), 0777) + fileWriter := &lumberjack.Logger{ + Filename: filename, + MaxSize: cfg.File.MaxSize, + MaxBackups: cfg.File.MaxBackups, + Compress: false, + LocalTime: true, + } + + cleanFns = append(cleanFns, func() { + _ = fileWriter.Close() + }) + + zc := zapcore.NewCore( + zapcore.NewJSONEncoder(zconfig.EncoderConfig), + zapcore.AddSync(fileWriter), + zconfig.Level, + ) + logger = zap.New(zc) + } else { + ilogger, err := zconfig.Build() + if err != nil { + return nil, err + } + logger = ilogger + } + + skip := cfg.CallerSkip + if skip <= 0 { + skip = 2 + } + + logger = logger.WithOptions( + zap.WithCaller(true), + zap.AddStacktrace(zap.ErrorLevel), + zap.AddCallerSkip(skip), + ) + + for _, h := range cfg.Hooks { + if !h.Enable || len(hookHandle) == 0 { + continue + } + + writer, err := hookHandle[0](ctx, h) + if err != nil { + return nil, err + } else if writer == nil { + continue + } + + cleanFns = append(cleanFns, func() { + writer.Flush() + }) + + hookLevel := zap.NewAtomicLevel() + if level, err := zapcore.ParseLevel(h.Level); err == nil { + hookLevel.SetLevel(level) + } else { + hookLevel.SetLevel(zap.InfoLevel) + } + + hookEncoder := zap.NewProductionEncoderConfig() + hookEncoder.EncodeTime = zapcore.EpochMillisTimeEncoder + hookEncoder.EncodeDuration = zapcore.MillisDurationEncoder + hookCore := zapcore.NewCore( + zapcore.NewJSONEncoder(hookEncoder), + zapcore.AddSync(writer), + hookLevel, + ) + + logger = logger.WithOptions(zap.WrapCore(func(core zapcore.Core) zapcore.Core { + return zapcore.NewTee(core, hookCore) + })) + } + + zap.ReplaceGlobals(logger) + return func() { + for _, fn := range cleanFns { + fn() + } + }, nil +} diff --git a/pkg/logging/logger.go b/pkg/logging/logger.go new file mode 100644 index 0000000..02ae448 --- /dev/null +++ b/pkg/logging/logger.go @@ -0,0 +1,135 @@ +package logging + +import ( + "context" + "fmt" + + "go.uber.org/zap" +) + +const ( + TagKeyMain = "main" + TagKeyRecovery = "recovery" + TagKeyRequest = "request" + TagKeyLogin = "login" + TagKeyAppLogin = "applogin" + + TagKeyLogout = "logout" + TagKeySystem = "system" + TagKeyOperate = "operate" +) + +type ( + ctxLoggerKey struct{} + ctxTraceIDKey struct{} + ctxUserIDKey struct{} + ctxTagKey struct{} + ctxStackKey struct{} +) + +func NewLogger(ctx context.Context, logger *zap.Logger) context.Context { + return context.WithValue(ctx, ctxLoggerKey{}, logger) +} + +func FromLogger(ctx context.Context) *zap.Logger { + v := ctx.Value(ctxLoggerKey{}) + if v != nil { + if vv, ok := v.(*zap.Logger); ok { + return vv + } + } + return zap.L() +} + +func NewTraceID(ctx context.Context, traceID string) context.Context { + return context.WithValue(ctx, ctxTraceIDKey{}, traceID) +} + +func FromTraceID(ctx context.Context) string { + v := ctx.Value(ctxTraceIDKey{}) + if v != nil { + if s, ok := v.(string); ok { + return s + } + } + return "" +} + +func NewUserID(ctx context.Context, userID string) context.Context { + return context.WithValue(ctx, ctxUserIDKey{}, userID) +} + +func FromUserID(ctx context.Context) string { + v := ctx.Value(ctxUserIDKey{}) + if v != nil { + if s, ok := v.(string); ok { + return s + } + } + return "" +} + +func NewCustomerID(ctx context.Context, userID uint) context.Context { + return context.WithValue(ctx, ctxUserIDKey{}, userID) +} + +func FromCustomerID(ctx context.Context) uint { + v := ctx.Value(ctxUserIDKey{}) + if v != nil { + if s, ok := v.(uint); ok { + return s + } + } + return 0 +} + +func NewTag(ctx context.Context, tag string) context.Context { + return context.WithValue(ctx, ctxTagKey{}, tag) +} + +func FromTag(ctx context.Context) string { + v := ctx.Value(ctxTagKey{}) + if v != nil { + if s, ok := v.(string); ok { + return s + } + } + return "" +} + +func NewStack(ctx context.Context, stack string) context.Context { + return context.WithValue(ctx, ctxStackKey{}, stack) +} + +func FromStack(ctx context.Context) string { + v := ctx.Value(ctxStackKey{}) + if v != nil { + if s, ok := v.(string); ok { + return s + } + } + return "" +} + +func Context(ctx context.Context) *zap.Logger { + var fields []zap.Field + if v := FromTraceID(ctx); v != "" { + fields = append(fields, zap.String("trace_id", v)) + } + if v := FromUserID(ctx); v != "" { + fields = append(fields, zap.String("user_id", v)) + } + if v := FromTag(ctx); v != "" { + fields = append(fields, zap.String("tag", v)) + } + if v := FromStack(ctx); v != "" { + fields = append(fields, zap.String("stack", v)) + } + return FromLogger(ctx).With(fields...) +} + +type PrintLogger struct{} + +func (a *PrintLogger) Printf(format string, args ...interface{}) { + zap.L().Info(fmt.Sprintf(format, args...)) +} diff --git a/pkg/mail/mail.go b/pkg/mail/mail.go new file mode 100644 index 0000000..39f1f94 --- /dev/null +++ b/pkg/mail/mail.go @@ -0,0 +1,72 @@ +package mail + +import ( + "context" + "sync" + "time" + + "gopkg.in/gomail.v2" +) + +var ( + globalSender *SmtpSender + once sync.Once +) + +// Set a global SMTP sender +func SetSender(sender *SmtpSender) { + once.Do(func() { + globalSender = sender + }) +} + +// Use smtp client send email with to/cc/bcc +func Send(ctx context.Context, to []string, cc []string, bcc []string, subject string, body string, file ...string) error { + return globalSender.Send(ctx, to, cc, bcc, subject, body, file...) +} + +// Use smtp client send email, use to specify recipients +func SendTo(ctx context.Context, to []string, subject string, body string, file ...string) error { + return globalSender.SendTo(ctx, to, subject, body, file...) +} + +// A smtp email client +type SmtpSender struct { + SmtpHost string + Port int + FromName string + FromMail string + UserName string + AuthCode string +} + +func (s *SmtpSender) Send(ctx context.Context, to []string, cc []string, bcc []string, subject string, body string, file ...string) error { + msg := gomail.NewMessage(gomail.SetEncoding(gomail.Base64)) + msg.SetHeader("From", msg.FormatAddress(s.FromMail, s.FromName)) + msg.SetHeader("To", to...) + msg.SetHeader("Cc", cc...) + msg.SetHeader("Bcc", bcc...) + msg.SetHeader("Subject", subject) + msg.SetBody("text/html;charset=utf-8", body) + + for _, v := range file { + msg.Attach(v) + } + + d := gomail.NewDialer(s.SmtpHost, s.Port, s.UserName, s.AuthCode) + return d.DialAndSend(msg) +} + +func (s *SmtpSender) SendTo(ctx context.Context, to []string, subject string, body string, file ...string) error { + var err error + for i := 0; i < 3; i++ { + err = s.Send(ctx, to, nil, nil, subject, body, file...) + if err != nil { + time.Sleep(time.Millisecond * 500) + continue + } + err = nil + break + } + return err +} diff --git a/pkg/middleware/auth.go b/pkg/middleware/auth.go new file mode 100644 index 0000000..5322448 --- /dev/null +++ b/pkg/middleware/auth.go @@ -0,0 +1,50 @@ +package middleware + +import ( + "encoding/json" + "fmt" + "github.com/gin-gonic/gin" + "github.guxuan/haibei/pkg/jwtx" + "github.guxuan/haibei/pkg/logging" + "github.guxuan/haibei/pkg/util" +) + +type AuthConfig struct { + AllowedPathPrefixes []string + SkippedPathPrefixes []string + RootID string + Skipper func(c *gin.Context) bool + ParseUserID func(c *gin.Context) (string, error) +} + +func AuthWithConfig(config AuthConfig) gin.HandlerFunc { + return func(c *gin.Context) { + if !AllowedPathPrefixes(c, config.AllowedPathPrefixes...) || + SkippedPathPrefixes(c, config.SkippedPathPrefixes...) || + (config.Skipper != nil && config.Skipper(c)) { + c.Next() + return + } + + userID, err := config.ParseUserID(c) + if err != nil { + util.ResError(c, jwtx.ErrInvalidToken, 401) + return + } + var subject jwtx.JwtSubject + err = json.Unmarshal([]byte(userID), &subject) + if err != nil { + util.ResError(c, jwtx.ErrInvalidToken, 401) + return + } + ctx := util.NewUserID(c.Request.Context(), subject.ID) + ctx = util.NewUserPlaform(ctx, subject.Typer) + ctx = logging.NewUserID(ctx, fmt.Sprintf("%v", subject)) + if userID == config.RootID { + ctx = util.NewIsRootUser(ctx) + } + c.Request = c.Request.WithContext(ctx) + + c.Next() + } +} diff --git a/pkg/middleware/casbin.go b/pkg/middleware/casbin.go new file mode 100644 index 0000000..257f26c --- /dev/null +++ b/pkg/middleware/casbin.go @@ -0,0 +1,46 @@ +package middleware + +import ( + "github.com/casbin/casbin/v2" + "github.com/gin-gonic/gin" + "github.guxuan/haibei/pkg/errors" + "github.guxuan/haibei/pkg/util" +) + +var ErrCasbinDenied = errors.Forbidden("com.casbin.denied", "Permission denied") + +type CasbinConfig struct { + AllowedPathPrefixes []string + SkippedPathPrefixes []string + Skipper func(c *gin.Context) bool + GetEnforcer func(c *gin.Context) *casbin.Enforcer + GetSubjects func(c *gin.Context) []string +} + +func CasbinWithConfig(config CasbinConfig) gin.HandlerFunc { + return func(c *gin.Context) { + if !AllowedPathPrefixes(c, config.AllowedPathPrefixes...) || + SkippedPathPrefixes(c, config.SkippedPathPrefixes...) || + (config.Skipper != nil && config.Skipper(c)) { + c.Next() + return + } + + enforcer := config.GetEnforcer(c) + if enforcer == nil { + util.ResError(c, ErrCasbinDenied) + return + } + + for _, sub := range config.GetSubjects(c) { + if b, err := enforcer.Enforce(sub, c.Request.URL.Path, c.Request.Method); err != nil { + util.ResError(c, err) + return + } else if b { + c.Next() + return + } + } + util.ResError(c, ErrCasbinDenied) + } +} diff --git a/pkg/middleware/copybody.go b/pkg/middleware/copybody.go new file mode 100644 index 0000000..fb1a65e --- /dev/null +++ b/pkg/middleware/copybody.go @@ -0,0 +1,66 @@ +package middleware + +import ( + "bytes" + "compress/gzip" + "io" + "net/http" + + "github.com/gin-gonic/gin" + "github.guxuan/haibei/pkg/errors" + "github.guxuan/haibei/pkg/util" +) + +type CopyBodyConfig struct { + AllowedPathPrefixes []string + SkippedPathPrefixes []string + MaxContentLen int64 +} + +var DefaultCopyBodyConfig = CopyBodyConfig{ + MaxContentLen: 32 << 20, // 32MB +} + +func CopyBody() gin.HandlerFunc { + return CopyBodyWithConfig(DefaultCopyBodyConfig) +} + +func CopyBodyWithConfig(config CopyBodyConfig) gin.HandlerFunc { + return func(c *gin.Context) { + if !AllowedPathPrefixes(c, config.AllowedPathPrefixes...) || + SkippedPathPrefixes(c, config.SkippedPathPrefixes...) || + c.Request.Body == nil { + c.Next() + return + } + + var ( + requestBody []byte + err error + ) + + isGzip := false + safe := http.MaxBytesReader(c.Writer, c.Request.Body, config.MaxContentLen) + if c.GetHeader("Content-Encoding") == "gzip" { + if reader, ierr := gzip.NewReader(safe); ierr == nil { + isGzip = true + requestBody, err = io.ReadAll(reader) + } + } + + if !isGzip { + requestBody, err = io.ReadAll(safe) + } + + if err != nil { + util.ResError(c, errors.RequestEntityTooLarge("", "Request body too large, limit %d byte", config.MaxContentLen)) + return + } + + c.Request.Body.Close() + bf := bytes.NewBuffer(requestBody) + c.Request.Body = io.NopCloser(bf) + c.Set(util.ReqBodyKey, requestBody) + c.Next() + } +} diff --git a/pkg/middleware/cors.go b/pkg/middleware/cors.go new file mode 100644 index 0000000..d97a35d --- /dev/null +++ b/pkg/middleware/cors.go @@ -0,0 +1,65 @@ +package middleware + +import ( + "time" + + "github.com/gin-contrib/cors" + "github.com/gin-gonic/gin" +) + +type CORSConfig struct { + Enable bool + AllowAllOrigins bool + // AllowOrigins is a list of origins a cross-domain request can be executed from. + // If the special "*" value is present in the list, all origins will be allowed. + // Default value is [] + AllowOrigins []string + // AllowMethods is a list of methods the client is allowed to use with + // cross-domain requests. Default value is simple methods (GET, POST, PUT, PATCH, DELETE, HEAD, and OPTIONS) + AllowMethods []string + // AllowHeaders is list of non simple headers the client is allowed to use with + // cross-domain requests. + AllowHeaders []string + // AllowCredentials indicates whether the request can include user credentials like + // cookies, HTTP authentication or client side SSL certificates. + AllowCredentials bool + // ExposeHeaders indicates which headers are safe to expose to the API of a CORS + // API specification + ExposeHeaders []string + // MaxAge indicates how long (with second-precision) the results of a preflight request + // can be cached + MaxAge int + // Allows to add origins like http://some-domain/*, https://api.* or http://some.*.subdomain.com + AllowWildcard bool + // Allows usage of popular browser extensions schemas + AllowBrowserExtensions bool + // Allows usage of WebSocket protocol + AllowWebSockets bool + // Allows usage of file:// schema (dangerous!) use it only when you 100% sure it's needed + AllowFiles bool +} + +var DefaultCORSConfig = CORSConfig{ + AllowOrigins: []string{"*"}, + AllowMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"}, +} + +func CORSWithConfig(cfg CORSConfig) gin.HandlerFunc { + if !cfg.Enable { + return Empty() + } + + return cors.New(cors.Config{ + AllowAllOrigins: cfg.AllowAllOrigins, + AllowOrigins: cfg.AllowOrigins, + AllowMethods: cfg.AllowMethods, + AllowHeaders: cfg.AllowHeaders, + AllowCredentials: cfg.AllowCredentials, + ExposeHeaders: cfg.ExposeHeaders, + MaxAge: time.Second * time.Duration(cfg.MaxAge), + AllowWildcard: cfg.AllowWildcard, + AllowBrowserExtensions: cfg.AllowBrowserExtensions, + AllowWebSockets: cfg.AllowWebSockets, + AllowFiles: cfg.AllowFiles, + }) +} diff --git a/pkg/middleware/logger.go b/pkg/middleware/logger.go new file mode 100644 index 0000000..3b4ba8b --- /dev/null +++ b/pkg/middleware/logger.go @@ -0,0 +1,88 @@ +package middleware + +import ( + "fmt" + "mime" + "net/http" + "time" + + "github.com/gin-gonic/gin" + "github.guxuan/haibei/pkg/logging" + "github.guxuan/haibei/pkg/util" + "go.uber.org/zap" +) + +type LoggerConfig struct { + AllowedPathPrefixes []string + SkippedPathPrefixes []string + MaxOutputRequestBodyLen int + MaxOutputResponseBodyLen int +} + +var DefaultLoggerConfig = LoggerConfig{ + MaxOutputRequestBodyLen: 1024 * 1024, + MaxOutputResponseBodyLen: 1024 * 1024, +} + +// Record detailed request logs for quick troubleshooting. +func Logger() gin.HandlerFunc { + return LoggerWithConfig(DefaultLoggerConfig) +} + +func LoggerWithConfig(config LoggerConfig) gin.HandlerFunc { + return func(c *gin.Context) { + if !AllowedPathPrefixes(c, config.AllowedPathPrefixes...) || + SkippedPathPrefixes(c, config.SkippedPathPrefixes...) { + c.Next() + return + } + + start := time.Now() + contentType := c.Request.Header.Get("Content-Type") + + fields := []zap.Field{ + zap.String("client_ip", c.ClientIP()), + zap.String("method", c.Request.Method), + zap.String("path", c.Request.URL.Path), + zap.String("user_agent", c.Request.UserAgent()), + zap.String("referer", c.Request.Referer()), + zap.String("uri", c.Request.RequestURI), + zap.String("host", c.Request.Host), + zap.String("remote_addr", c.Request.RemoteAddr), + zap.String("proto", c.Request.Proto), + zap.Int64("content_length", c.Request.ContentLength), + zap.String("content_type", contentType), + zap.String("pragma", c.Request.Header.Get("Pragma")), + } + + c.Next() + + if c.Request.Method == http.MethodPost || c.Request.Method == http.MethodPut { + mediaType, _, _ := mime.ParseMediaType(contentType) + if mediaType == "application/json" { + if v, ok := c.Get(util.ReqBodyKey); ok { + if b, ok := v.([]byte); ok && len(b) <= config.MaxOutputRequestBodyLen { + fields = append(fields, zap.String("body", string(b))) + } + } + } + } + + cost := time.Since(start).Nanoseconds() / 1e6 + fields = append(fields, zap.Int64("cost", cost)) + fields = append(fields, zap.Int("status", c.Writer.Status())) + fields = append(fields, zap.String("res_time", time.Now().Format("2006-01-02 15:04:05.999"))) + fields = append(fields, zap.Int("res_size", c.Writer.Size())) + + if v, ok := c.Get(util.ResBodyKey); ok { + if b, ok := v.([]byte); ok && len(b) <= config.MaxOutputResponseBodyLen { + fields = append(fields, zap.String("res_body", string(b))) + } + } + + ctx := c.Request.Context() + ctx = logging.NewTag(ctx, logging.TagKeyRequest) + logging.Context(ctx).Info(fmt.Sprintf("[HTTP] %s-%s-%d (%dms)", + c.Request.URL.Path, c.Request.Method, c.Writer.Status(), cost), fields...) + } +} diff --git a/pkg/middleware/middleware.go b/pkg/middleware/middleware.go new file mode 100644 index 0000000..f9fa0b7 --- /dev/null +++ b/pkg/middleware/middleware.go @@ -0,0 +1,41 @@ +package middleware + +import ( + "github.com/gin-gonic/gin" +) + +func SkippedPathPrefixes(c *gin.Context, prefixes ...string) bool { + if len(prefixes) == 0 { + return false + } + + path := c.Request.URL.Path + pathLen := len(path) + for _, p := range prefixes { + if pl := len(p); pathLen >= pl && path[:pl] == p { + return true + } + } + return false +} + +func AllowedPathPrefixes(c *gin.Context, prefixes ...string) bool { + if len(prefixes) == 0 { + return true + } + + path := c.Request.URL.Path + pathLen := len(path) + for _, p := range prefixes { + if pl := len(p); pathLen >= pl && path[:pl] == p { + return true + } + } + return false +} + +func Empty() gin.HandlerFunc { + return func(c *gin.Context) { + c.Next() + } +} diff --git a/pkg/middleware/ratelimiter.go b/pkg/middleware/ratelimiter.go new file mode 100644 index 0000000..67a71ae --- /dev/null +++ b/pkg/middleware/ratelimiter.go @@ -0,0 +1,144 @@ +package middleware + +import ( + "context" + "time" + + "github.com/gin-gonic/gin" + "github.com/go-redis/redis/v8" + "github.com/go-redis/redis_rate/v9" + "github.com/patrickmn/go-cache" + "github.guxuan/haibei/pkg/errors" + "github.guxuan/haibei/pkg/logging" + "github.guxuan/haibei/pkg/util" + "go.uber.org/zap" + "golang.org/x/time/rate" +) + +type RateLimiterConfig struct { + Enable bool + AllowedPathPrefixes []string + SkippedPathPrefixes []string + Period int + MaxRequestsPerIP int + MaxRequestsPerUser int + StoreType string // memory/redis + MemoryStoreConfig RateLimiterMemoryConfig + RedisStoreConfig RateLimiterRedisConfig +} + +func RateLimiterWithConfig(config RateLimiterConfig) gin.HandlerFunc { + if !config.Enable { + return Empty() + } + + var store RateLimiterStorer + switch config.StoreType { + case "redis": + store = NewRateLimiterRedisStore(config.RedisStoreConfig) + default: + store = NewRateLimiterMemoryStore(config.MemoryStoreConfig) + } + + return func(c *gin.Context) { + if !AllowedPathPrefixes(c, config.AllowedPathPrefixes...) || + SkippedPathPrefixes(c, config.SkippedPathPrefixes...) { + c.Next() + return + } + + var ( + allowed bool + err error + ) + + ctx := c.Request.Context() + if userID := util.FromUserID(ctx); userID != "" { + allowed, err = store.Allow(ctx, userID.(string), time.Second*time.Duration(config.Period), config.MaxRequestsPerUser) + } else { + allowed, err = store.Allow(ctx, c.ClientIP(), time.Second*time.Duration(config.Period), config.MaxRequestsPerIP) + } + + if err != nil { + logging.Context(ctx).Error("Rate limiter middleware error", zap.Error(err)) + util.ResError(c, errors.InternalServerError("", "Internal server error, please try again later.")) + } else if allowed { + c.Next() + } else { + util.ResError(c, errors.TooManyRequests("", "Too many requests, please try again later.")) + } + } +} + +type RateLimiterStorer interface { + Allow(ctx context.Context, identifier string, period time.Duration, maxRequests int) (bool, error) +} + +func NewRateLimiterMemoryStore(config RateLimiterMemoryConfig) RateLimiterStorer { + return &RateLimiterMemoryStore{ + cache: cache.New(config.Expiration, config.CleanupInterval), + } +} + +type RateLimiterMemoryConfig struct { + Expiration time.Duration + CleanupInterval time.Duration +} + +type RateLimiterMemoryStore struct { + cache *cache.Cache +} + +func (s *RateLimiterMemoryStore) Allow(ctx context.Context, identifier string, period time.Duration, maxRequests int) (bool, error) { + if period.Seconds() <= 0 || maxRequests <= 0 { + return true, nil + } + + if limiter, exists := s.cache.Get(identifier); exists { + isAllow := limiter.(*rate.Limiter).Allow() + s.cache.SetDefault(identifier, limiter) + return isAllow, nil + } + + limiter := rate.NewLimiter(rate.Every(period), maxRequests) + limiter.Allow() + s.cache.SetDefault(identifier, limiter) + + return true, nil +} + +type RateLimiterRedisConfig struct { + Addr string + Username string + Password string + DB int +} + +func NewRateLimiterRedisStore(config RateLimiterRedisConfig) RateLimiterStorer { + rdb := redis.NewClient(&redis.Options{ + Addr: config.Addr, + Username: config.Username, + Password: config.Password, + DB: config.DB, + }) + + return &RateLimiterRedisStore{ + limiter: redis_rate.NewLimiter(rdb), + } +} + +type RateLimiterRedisStore struct { + limiter *redis_rate.Limiter +} + +func (s *RateLimiterRedisStore) Allow(ctx context.Context, identifier string, period time.Duration, maxRequests int) (bool, error) { + if period.Seconds() <= 0 || maxRequests <= 0 { + return true, nil + } + + result, err := s.limiter.Allow(ctx, identifier, redis_rate.PerSecond(maxRequests/int(period.Seconds()))) + if err != nil { + return false, err + } + return result.Allowed > 0, nil +} diff --git a/pkg/middleware/recover.go b/pkg/middleware/recover.go new file mode 100644 index 0000000..7d188a4 --- /dev/null +++ b/pkg/middleware/recover.go @@ -0,0 +1,59 @@ +package middleware + +import ( + "fmt" + "net/http/httputil" + "strings" + "time" + + "github.com/gin-gonic/gin" + "github.guxuan/haibei/pkg/errors" + "github.guxuan/haibei/pkg/logging" + "github.guxuan/haibei/pkg/util" + "go.uber.org/zap" +) + +type RecoveryConfig struct { + Skip int // default: 3 +} + +var DefaultRecoveryConfig = RecoveryConfig{ + Skip: 3, +} + +// Recovery from any panics and writes a 500 if there was one. +func Recovery() gin.HandlerFunc { + return RecoveryWithConfig(DefaultRecoveryConfig) +} + +func RecoveryWithConfig(config RecoveryConfig) gin.HandlerFunc { + return func(c *gin.Context) { + defer func() { + if rv := recover(); rv != nil { + ctx := c.Request.Context() + ctx = logging.NewTag(ctx, logging.TagKeyRecovery) + + var fields []zap.Field + fields = append(fields, zap.Strings("error", []string{fmt.Sprintf("%v", rv)})) + fields = append(fields, zap.StackSkip("stack", config.Skip)) + + if gin.IsDebugging() { + httpRequest, _ := httputil.DumpRequest(c.Request, false) + headers := strings.Split(string(httpRequest), "\r\n") + for idx, header := range headers { + current := strings.Split(header, ":") + if current[0] == "Authorization" { + headers[idx] = current[0] + ": *" + } + } + fields = append(fields, zap.Strings("headers", headers)) + } + + logging.Context(ctx).Error(fmt.Sprintf("[Recovery] %s panic recovered", time.Now().Format("2006/01/02 - 15:04:05")), fields...) + util.ResError(c, errors.InternalServerError("", "Internal server error, please try again later")) + } + }() + + c.Next() + } +} diff --git a/pkg/middleware/static.go b/pkg/middleware/static.go new file mode 100644 index 0000000..abbc746 --- /dev/null +++ b/pkg/middleware/static.go @@ -0,0 +1,31 @@ +package middleware + +import ( + "os" + "path/filepath" + + "github.com/gin-gonic/gin" +) + +type StaticConfig struct { + SkippedPathPrefixes []string + Root string +} + +func StaticWithConfig(config StaticConfig) gin.HandlerFunc { + return func(c *gin.Context) { + if SkippedPathPrefixes(c, config.SkippedPathPrefixes...) { + c.Next() + return + } + + p := c.Request.URL.Path + fpath := filepath.Join(config.Root, filepath.FromSlash(p)) + _, err := os.Stat(fpath) + if err != nil && os.IsNotExist(err) { + fpath = filepath.Join(config.Root, "index.html") + } + c.File(fpath) + c.Abort() + } +} diff --git a/pkg/middleware/trace.go b/pkg/middleware/trace.go new file mode 100644 index 0000000..7d9e84d --- /dev/null +++ b/pkg/middleware/trace.go @@ -0,0 +1,48 @@ +package middleware + +import ( + "fmt" + "strings" + + "github.com/gin-gonic/gin" + "github.com/rs/xid" + "github.guxuan/haibei/pkg/logging" + "github.guxuan/haibei/pkg/util" +) + +type TraceConfig struct { + AllowedPathPrefixes []string + SkippedPathPrefixes []string + RequestHeaderKey string + ResponseTraceKey string +} + +var DefaultTraceConfig = TraceConfig{ + RequestHeaderKey: "X-Request-Id", + ResponseTraceKey: "X-Trace-Id", +} + +func Trace() gin.HandlerFunc { + return TraceWithConfig(DefaultTraceConfig) +} + +func TraceWithConfig(config TraceConfig) gin.HandlerFunc { + return func(c *gin.Context) { + if !AllowedPathPrefixes(c, config.AllowedPathPrefixes...) || + SkippedPathPrefixes(c, config.SkippedPathPrefixes...) { + c.Next() + return + } + + traceID := c.GetHeader(config.RequestHeaderKey) + if traceID == "" { + traceID = fmt.Sprintf("TRACE-%s", strings.ToUpper(xid.New().String())) + } + + ctx := util.NewTraceID(c.Request.Context(), traceID) + ctx = logging.NewTraceID(ctx, traceID) + c.Request = c.Request.WithContext(ctx) + c.Writer.Header().Set(config.ResponseTraceKey, traceID) + c.Next() + } +} diff --git a/pkg/oss/minio.go b/pkg/oss/minio.go new file mode 100644 index 0000000..16d0242 --- /dev/null +++ b/pkg/oss/minio.go @@ -0,0 +1,134 @@ +package oss + +import ( + "context" + "io" + "strings" + + "github.com/minio/minio-go/v7" + "github.com/minio/minio-go/v7/pkg/credentials" +) + +type MinioClientConfig struct { + Domain string + Endpoint string + AccessKeyID string + SecretAccessKey string + BucketName string + Prefix string +} + +var _ IClient = (*MinioClient)(nil) + +type MinioClient struct { + config MinioClientConfig + client *minio.Client +} + +func NewMinioClient(config MinioClientConfig) (*MinioClient, error) { + client, err := minio.New(config.Endpoint, &minio.Options{ + Creds: credentials.NewStaticV4(config.AccessKeyID, config.SecretAccessKey, ""), + }) + if err != nil { + return nil, err + } + + ctx := context.Background() + if exists, err := client.BucketExists(ctx, config.BucketName); err != nil { + return nil, err + } else if !exists { + if err := client.MakeBucket(ctx, config.BucketName, minio.MakeBucketOptions{}); err != nil { + return nil, err + } + } + + return &MinioClient{ + config: config, + client: client, + }, nil +} + +func (c *MinioClient) PutObject(ctx context.Context, bucketName, objectName string, reader io.ReadSeeker, objectSize int64, options ...PutObjectOptions) (*PutObjectResult, error) { + if bucketName == "" { + bucketName = c.config.BucketName + } + + var opt PutObjectOptions + if len(options) > 0 { + opt = options[0] + } + + objectName = formatObjectName(c.config.Prefix, objectName) + output, err := c.client.PutObject(ctx, bucketName, objectName, reader, objectSize, minio.PutObjectOptions{ + ContentType: opt.ContentType, + UserMetadata: opt.UserMetadata, + }) + if err != nil { + return nil, err + } + + return &PutObjectResult{ + URL: c.config.Domain + "/" + objectName, + Key: output.Key, + ETag: output.ETag, + Size: output.Size, + }, nil +} + +func (c *MinioClient) GetObject(ctx context.Context, bucketName, objectName string) (io.ReadCloser, error) { + if bucketName == "" { + bucketName = c.config.BucketName + } + + objectName = formatObjectName(c.config.Prefix, objectName) + return c.client.GetObject(ctx, bucketName, objectName, minio.GetObjectOptions{}) +} + +func (c *MinioClient) RemoveObject(ctx context.Context, bucketName, objectName string) error { + if bucketName == "" { + bucketName = c.config.BucketName + } + + objectName = formatObjectName(c.config.Prefix, objectName) + return c.client.RemoveObject(ctx, bucketName, objectName, minio.RemoveObjectOptions{}) +} + +func (c *MinioClient) RemoveObjectByURL(ctx context.Context, urlStr string) error { + prefix := c.config.Domain + "/" + if !strings.HasPrefix(urlStr, prefix) { + return nil + } + + objectName := strings.TrimPrefix(urlStr, prefix) + return c.RemoveObject(ctx, "", objectName) +} + +func (c *MinioClient) StatObjectByURL(ctx context.Context, urlStr string) (*ObjectStat, error) { + prefix := c.config.Domain + "/" + if !strings.HasPrefix(urlStr, prefix) { + return nil, nil + } + + objectName := strings.TrimPrefix(urlStr, prefix) + return c.StatObject(ctx, "", objectName) +} + +func (c *MinioClient) StatObject(ctx context.Context, bucketName, objectName string) (*ObjectStat, error) { + if bucketName == "" { + bucketName = c.config.BucketName + } + + objectName = formatObjectName(c.config.Prefix, objectName) + info, err := c.client.StatObject(ctx, bucketName, objectName, minio.StatObjectOptions{}) + if err != nil { + return nil, err + } + + return &ObjectStat{ + Key: info.Key, + Size: info.Size, + ETag: info.ETag, + ContentType: info.ContentType, + UserMetadata: info.UserMetadata, + }, nil +} diff --git a/pkg/oss/oss.go b/pkg/oss/oss.go new file mode 100644 index 0000000..e111e2b --- /dev/null +++ b/pkg/oss/oss.go @@ -0,0 +1,75 @@ +package oss + +import ( + "context" + "io" + "path/filepath" + "sync" + "time" + + "github.com/rs/xid" +) + +var ( + Ins IClient + once sync.Once +) + +// Set the global oss client +func SetGlobal(h func() IClient) { + once.Do(func() { + Ins = h() + }) +} + +// IClient is an interface for oss client +type IClient interface { + PutObject(ctx context.Context, bucketName, objectName string, reader io.ReadSeeker, objectSize int64, options ...PutObjectOptions) (*PutObjectResult, error) + GetObject(ctx context.Context, bucketName, objectName string) (io.ReadCloser, error) + RemoveObject(ctx context.Context, bucketName, objectName string) error + RemoveObjectByURL(ctx context.Context, urlStr string) error + StatObject(ctx context.Context, bucketName, objectName string) (*ObjectStat, error) + StatObjectByURL(ctx context.Context, urlStr string) (*ObjectStat, error) +} + +// PutObjectOptions represents options specified by user for PutObject call +type PutObjectOptions struct { + ContentType string + UserMetadata map[string]string +} + +type PutObjectResult struct { + URL string `json:"url,omitempty"` + Key string `json:"key,omitempty"` + ETag string `json:"e_tag,omitempty"` + Size int64 `json:"size,omitempty"` +} + +type ObjectStat struct { + Key string + ETag string + LastModified time.Time + Size int64 + ContentType string + UserMetadata map[string]string +} + +func (a *ObjectStat) GetName() string { + if name, ok := a.UserMetadata["name"]; ok { + return name + } + return filepath.Base(a.Key) +} + +func formatObjectName(prefix, objectName string) string { + if objectName == "" { + objectName = xid.New().String() + } + if objectName[0] == '/' { + objectName = objectName[1:] + } + if prefix != "" { + objectName = prefix + "/" + objectName + } + return objectName +} diff --git a/pkg/oss/s3.go b/pkg/oss/s3.go new file mode 100644 index 0000000..a4d8ea0 --- /dev/null +++ b/pkg/oss/s3.go @@ -0,0 +1,179 @@ +package oss + +import ( + "context" + "io" + "strings" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/credentials" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/s3" +) + +type S3ClientConfig struct { + Domain string + Region string + AccessKeyID string + SecretAccessKey string + BucketName string + Prefix string +} + +var _ IClient = (*S3Client)(nil) + +type S3Client struct { + config S3ClientConfig + session *session.Session + client *s3.S3 +} + +func NewS3Client(config S3ClientConfig) (*S3Client, error) { + awsConfig := aws.NewConfig() + awsConfig.WithRegion(config.Region) + awsConfig.WithCredentials(credentials.NewStaticCredentials(config.AccessKeyID, config.SecretAccessKey, "")) + session, err := session.NewSession(awsConfig) + if err != nil { + return nil, err + } + + return &S3Client{ + config: config, + session: session, + client: s3.New(session), + }, nil +} + +func (c *S3Client) PutObject(ctx context.Context, bucketName, objectName string, reader io.ReadSeeker, objectSize int64, options ...PutObjectOptions) (*PutObjectResult, error) { + if bucketName == "" { + bucketName = c.config.BucketName + } + + var opt PutObjectOptions + if len(options) > 0 { + opt = options[0] + } + + objectName = formatObjectName(c.config.Prefix, objectName) + input := &s3.PutObjectInput{ + Bucket: aws.String(bucketName), + Key: aws.String(objectName), + Body: reader, + ContentType: aws.String(opt.ContentType), + ContentDisposition: aws.String("inline"), + ACL: aws.String("public-read"), + } + + if len(opt.UserMetadata) > 0 { + input.Metadata = make(map[string]*string) + for k, v := range opt.UserMetadata { + input.Metadata[k] = aws.String(v) + } + } + + output, err := c.client.PutObject(input) + if err != nil { + return nil, err + } + + return &PutObjectResult{ + URL: c.config.Domain + "/" + objectName, + Key: *input.Key, + ETag: *output.ETag, + Size: objectSize, + }, nil +} + +func (c *S3Client) GetObject(ctx context.Context, bucketName, objectName string) (io.ReadCloser, error) { + if bucketName == "" { + bucketName = c.config.BucketName + } + + objectName = formatObjectName(c.config.Prefix, objectName) + input := &s3.GetObjectInput{ + Bucket: aws.String(bucketName), + Key: aws.String(objectName), + } + + output, err := c.client.GetObject(input) + if err != nil { + return nil, err + } + + return output.Body, nil +} + +func (c *S3Client) RemoveObject(ctx context.Context, bucketName, objectName string) error { + if bucketName == "" { + bucketName = c.config.BucketName + } + + objectName = formatObjectName(c.config.Prefix, objectName) + input := &s3.DeleteObjectInput{ + Bucket: aws.String(bucketName), + Key: aws.String(objectName), + } + + _, err := c.client.DeleteObject(input) + return err +} + +func (c *S3Client) RemoveObjectByURL(ctx context.Context, urlStr string) error { + prefix := c.config.Domain + "/" + if !strings.HasPrefix(urlStr, prefix) { + return nil + } + + objectName := strings.TrimPrefix(urlStr, prefix) + input := &s3.DeleteObjectInput{ + Bucket: aws.String(c.config.BucketName), + Key: aws.String(objectName), + } + + _, err := c.client.DeleteObject(input) + return err +} + +func (c *S3Client) StatObjectByURL(ctx context.Context, urlStr string) (*ObjectStat, error) { + prefix := c.config.Domain + "/" + if !strings.HasPrefix(urlStr, prefix) { + return nil, nil + } + + objectName := strings.TrimPrefix(urlStr, prefix) + return c.StatObject(ctx, c.config.BucketName, objectName) +} + +func (c *S3Client) StatObject(ctx context.Context, bucketName, objectName string) (*ObjectStat, error) { + if bucketName == "" { + bucketName = c.config.BucketName + } + + objectName = formatObjectName(c.config.Prefix, objectName) + input := &s3.HeadObjectInput{ + Bucket: aws.String(bucketName), + Key: aws.String(objectName), + } + + output, err := c.client.HeadObject(input) + if err != nil { + return nil, err + } + + var metadata map[string]string + if output.Metadata != nil { + metadata = make(map[string]string) + for k, v := range output.Metadata { + metadata[k] = *v + } + } + + return &ObjectStat{ + Key: objectName, + ETag: *output.ETag, + LastModified: *output.LastModified, + Size: *output.ContentLength, + ContentType: *output.ContentType, + UserMetadata: metadata, + }, nil +} diff --git a/pkg/promx/gin.go b/pkg/promx/gin.go new file mode 100644 index 0000000..d4f5b6e --- /dev/null +++ b/pkg/promx/gin.go @@ -0,0 +1,41 @@ +package promx + +import ( + "fmt" + "strings" + "time" + + "github.com/gin-gonic/gin" +) + +type AdapterGin struct { + prom *PrometheusWrapper +} + +func NewAdapterGin(p *PrometheusWrapper) *AdapterGin { + return &AdapterGin{prom: p} +} + +func (a *AdapterGin) Middleware(enable bool, reqKey string) gin.HandlerFunc { + return func(ctx *gin.Context) { + if !enable { + ctx.Next() + return + } + + start := time.Now() + recvBytes := 0 + if v, ok := ctx.Get(reqKey); ok { + if b, ok := v.([]byte); ok { + recvBytes = len(b) + } + } + ctx.Next() + latency := float64(time.Since(start).Milliseconds()) + p := ctx.Request.URL.Path + for _, param := range ctx.Params { + p = strings.Replace(p, param.Value, ":"+param.Key, -1) + } + a.prom.Log(p, ctx.Request.Method, fmt.Sprintf("%d", ctx.Writer.Status()), float64(ctx.Writer.Size()), float64(recvBytes), latency) + } +} diff --git a/pkg/promx/prom.go b/pkg/promx/prom.go new file mode 100644 index 0000000..3d1d341 --- /dev/null +++ b/pkg/promx/prom.go @@ -0,0 +1,278 @@ +package promx + +import ( + "fmt" + "log" + "net/http" + + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/collectors" + "github.com/prometheus/client_golang/prometheus/promhttp" +) + +type Config struct { + Enable bool + App string + ListenPort int + BasicUserName string + BasicPassword string + LogApi map[string]struct{} + LogMethod map[string]struct{} + Buckets []float64 + Objectives map[float64]float64 + DefaultCollect bool +} + +type PrometheusWrapper struct { + c Config + reg *prometheus.Registry + gaugeState *prometheus.GaugeVec + histogramLatency *prometheus.HistogramVec + summaryLatency *prometheus.SummaryVec + counterRequests, counterSendBytes *prometheus.CounterVec + counterRcvdBytes, counterException *prometheus.CounterVec + counterEvent, counterSiteEvent *prometheus.CounterVec +} + +func (p *PrometheusWrapper) init() { + p.counterRequests = prometheus.NewCounterVec( + prometheus.CounterOpts{ + Name: "counter_requests", + Help: "number of module requests", + }, + []string{"app", "module", "api", "method", "code"}, + ) + p.reg.MustRegister(p.counterRequests) + + p.counterSendBytes = prometheus.NewCounterVec( + prometheus.CounterOpts{ + Name: "counter_send_bytes", + Help: "number of module send bytes", + }, + []string{"app", "module", "api", "method", "code"}, + ) + p.reg.MustRegister(p.counterSendBytes) + + p.counterRcvdBytes = prometheus.NewCounterVec( + prometheus.CounterOpts{ + Name: "counter_rcvd_bytes", + Help: "number of module receive bytes", + }, + []string{"app", "module", "api", "method", "code"}, + ) + p.reg.MustRegister(p.counterRcvdBytes) + + p.histogramLatency = prometheus.NewHistogramVec( + prometheus.HistogramOpts{ + Name: "histogram_latency", + Help: "histogram of module latency", + Buckets: p.c.Buckets, + }, + []string{"app", "module", "api", "method"}, + ) + p.reg.MustRegister(p.histogramLatency) + + p.summaryLatency = prometheus.NewSummaryVec( + prometheus.SummaryOpts{ + Name: "summary_latency", + Help: "summary of module latency", + Objectives: p.c.Objectives, + }, + []string{"app", "module", "api", "method"}, + ) + p.reg.MustRegister(p.summaryLatency) + + p.gaugeState = prometheus.NewGaugeVec( + prometheus.GaugeOpts{ + Name: "gauge_state", + Help: "gauge of app state", + }, + []string{"app", "module", "state"}, + ) + p.reg.MustRegister(p.gaugeState) + + p.counterException = prometheus.NewCounterVec( + prometheus.CounterOpts{ + Name: "counter_exception", + Help: "number of module exception", + }, + []string{"app", "module", "exception"}, + ) + p.reg.MustRegister(p.counterException) + + p.counterEvent = prometheus.NewCounterVec( + prometheus.CounterOpts{ + Name: "counter_event", + Help: "number of module event", + }, + []string{"app", "module", "event"}, + ) + p.reg.MustRegister(p.counterEvent) + + p.counterSiteEvent = prometheus.NewCounterVec( + prometheus.CounterOpts{ + Name: "counter_site_event", + Help: "number of module site event", + }, + []string{"app", "module", "event", "site"}, + ) + p.reg.MustRegister(p.counterSiteEvent) + + if p.c.DefaultCollect { + p.reg.MustRegister(collectors.NewProcessCollector(collectors.ProcessCollectorOpts{})) + p.reg.MustRegister(collectors.NewGoCollector()) + } +} + +func (p *PrometheusWrapper) run() { + if p.c.ListenPort == 0 { + return + } + + go func() { + handle := promhttp.HandlerFor(p.reg, promhttp.HandlerOpts{}) + http.Handle("/metrics", promhttp.InstrumentMetricHandler( + p.reg, + http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + username, pwd, ok := req.BasicAuth() + if !ok || !(username == p.c.BasicUserName && pwd == p.c.BasicPassword) { + w.WriteHeader(http.StatusUnauthorized) + _, _ = w.Write([]byte("401 Unauthorized")) + return + } + handle.ServeHTTP(w, req) + })), + ) + log.Printf("Prometheus listening on: %d", p.c.ListenPort) + log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", p.c.ListenPort), nil)) + }() +} + +func (p *PrometheusWrapper) Log(api, method, code string, sendBytes, rcvdBytes, latency float64) { + if !p.c.Enable { + return + } + if len(p.c.LogMethod) > 0 { + if _, ok := p.c.LogMethod[method]; !ok { + return + } + } + if len(p.c.LogApi) > 0 { + if _, ok := p.c.LogApi[api]; !ok { + return + } + } + + p.counterRequests.WithLabelValues(p.c.App, "self", api, method, code).Inc() + if sendBytes > 0 { + p.counterSendBytes.WithLabelValues(p.c.App, "self", api, method, code).Add(sendBytes) + } + if rcvdBytes > 0 { + p.counterRcvdBytes.WithLabelValues(p.c.App, "self", api, method, code).Add(rcvdBytes) + } + if len(p.c.Buckets) > 0 { + p.histogramLatency.WithLabelValues(p.c.App, "self", api, method).Observe(latency) + } + if len(p.c.Objectives) > 0 { + p.summaryLatency.WithLabelValues(p.c.App, "self", api, method).Observe(latency) + } +} + +func (p *PrometheusWrapper) RequestLog(module, api, method, code string) { + if !p.c.Enable { + return + } + p.counterRequests.WithLabelValues(p.c.App, module, api, method, code).Inc() +} + +func (p *PrometheusWrapper) SendBytesLog(module, api, method, code string, byte float64) { + if !p.c.Enable { + return + } + p.counterSendBytes.WithLabelValues(p.c.App, module, api, method, code).Add(byte) +} + +func (p *PrometheusWrapper) RcvdBytesLog(module, api, method, code string, byte float64) { + if !p.c.Enable { + return + } + p.counterRcvdBytes.WithLabelValues(p.c.App, module, api, method, code).Add(byte) +} + +func (p *PrometheusWrapper) HistogramLatencyLog(module, api, method string, latency float64) { + if !p.c.Enable { + return + } + p.histogramLatency.WithLabelValues(p.c.App, module, api, method).Observe(latency) +} + +func (p *PrometheusWrapper) SummaryLatencyLog(module, api, method string, latency float64) { + if !p.c.Enable { + return + } + p.summaryLatency.WithLabelValues(p.c.App, module, api, method).Observe(latency) +} + +func (p *PrometheusWrapper) ExceptionLog(module, exception string) { + if !p.c.Enable { + return + } + p.counterException.WithLabelValues(p.c.App, module, exception).Inc() +} + +func (p *PrometheusWrapper) EventLog(module, event string) { + if !p.c.Enable { + return + } + p.counterEvent.WithLabelValues(p.c.App, module, event).Inc() +} + +func (p *PrometheusWrapper) SiteEventLog(module, event, site string) { + if !p.c.Enable { + return + } + p.counterSiteEvent.WithLabelValues(p.c.App, module, event, site).Inc() +} + +func (p *PrometheusWrapper) StateLog(module, state string, value float64) { + if !p.c.Enable { + return + } + p.gaugeState.WithLabelValues(p.c.App, module, state).Set(value) +} + +func (p *PrometheusWrapper) ResetCounter() { + if !p.c.Enable { + return + } + p.counterSiteEvent.Reset() + p.counterEvent.Reset() + p.counterException.Reset() + p.counterRcvdBytes.Reset() + p.counterSendBytes.Reset() +} + +func (p *PrometheusWrapper) RegCustomCollector(c prometheus.Collector) { + p.reg.MustRegister(c) +} + +func NewPrometheusWrapper(conf *Config) *PrometheusWrapper { + if conf.App == "" { + conf.App = "app" + } + if conf.Enable && conf.ListenPort == 0 { + conf.ListenPort = 9100 + } + + w := &PrometheusWrapper{ + c: *conf, + reg: prometheus.NewRegistry(), + } + + if conf.Enable { + w.init() + w.run() + } + + return w +} diff --git a/pkg/sms/sms.go b/pkg/sms/sms.go new file mode 100644 index 0000000..3db8ce6 --- /dev/null +++ b/pkg/sms/sms.go @@ -0,0 +1,10 @@ +package sms + +import "fmt" + +func sendVerificationCode(phone, code string) error { + // 这里应该是调用短信服务商的API发送短信 + // 这里只是模拟 + fmt.Printf("向手机号 %s 发送验证码: %s\n", phone, code) + return nil +} diff --git a/pkg/util/baseModel.go b/pkg/util/baseModel.go new file mode 100644 index 0000000..bcec984 --- /dev/null +++ b/pkg/util/baseModel.go @@ -0,0 +1,23 @@ +package util + +import ( + "time" + + "gorm.io/plugin/soft_delete" +) + +type BaseModel struct { + ID uint `json:"id" gorm:"primaryKey"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` + DeletedAt soft_delete.DeletedAt `json:"deletedAt" gorm:"index" ` + CreatedID uint `json:"createdId" gorm:"column:created_id;index;comment:创建人ID"` + DeletedID uint `json:"deletedId" gorm:"column:deleted_id;index;comment:删除人ID"` +} + +type BaseModelWithoutDeletedAt struct { + ID uint `json:"id" gorm:"primaryKey"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` + CreatedID uint `json:"createdId" gorm:"column:created_id;index;comment:创建人ID"` +} diff --git a/pkg/util/command.go b/pkg/util/command.go new file mode 100644 index 0000000..7033d87 --- /dev/null +++ b/pkg/util/command.go @@ -0,0 +1,45 @@ +package util + +import ( + "context" + "os" + "os/signal" + "syscall" + "time" + + "github.guxuan/haibei/pkg/logging" + "go.uber.org/zap" +) + +// The Run function sets up a signal handler and executes a handler function until a termination signal +// is received. +func Run(ctx context.Context, handler func(ctx context.Context) (func(), error)) error { + state := 1 + sc := make(chan os.Signal, 1) + signal.Notify(sc, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT) + cleanFn, err := handler(ctx) + if err != nil { + return err + } + +EXIT: + for { + sig := <-sc + logging.Context(ctx).Info("Received signal", zap.String("signal", sig.String())) + + switch sig { + case syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT: + state = 0 + break EXIT + case syscall.SIGHUP: + default: + break EXIT + } + } + + cleanFn() + logging.Context(ctx).Info("Server exit, bye...") + time.Sleep(time.Millisecond * 100) + os.Exit(state) + return nil +} diff --git a/pkg/util/context.go b/pkg/util/context.go new file mode 100644 index 0000000..33ec1b0 --- /dev/null +++ b/pkg/util/context.go @@ -0,0 +1,126 @@ +package util + +import ( + "context" + + "github.guxuan/haibei/pkg/encoding/json" + "gorm.io/gorm" +) + +type ( + traceIDCtx struct{} + transCtx struct{} + rowLockCtx struct{} + userIDCtx struct{} + formPlatformCtx struct{} + userTokenCtx struct{} + isRootUserCtx struct{} + userCacheCtx struct{} +) + +func NewTraceID(ctx context.Context, traceID string) context.Context { + return context.WithValue(ctx, traceIDCtx{}, traceID) +} + +func FromTraceID(ctx context.Context) string { + v := ctx.Value(traceIDCtx{}) + if v != nil { + return v.(string) + } + return "" +} + +func NewTrans(ctx context.Context, db *gorm.DB) context.Context { + return context.WithValue(ctx, transCtx{}, db) +} + +func FromTrans(ctx context.Context) (*gorm.DB, bool) { + v := ctx.Value(transCtx{}) + if v != nil { + return v.(*gorm.DB), true + } + return nil, false +} + +func NewRowLock(ctx context.Context) context.Context { + return context.WithValue(ctx, rowLockCtx{}, true) +} + +func FromRowLock(ctx context.Context) bool { + v := ctx.Value(rowLockCtx{}) + return v != nil && v.(bool) +} + +func NewUserID(ctx context.Context, userID interface{}) context.Context { + return context.WithValue(ctx, userIDCtx{}, userID) +} + +func FromUserID(ctx context.Context) interface{} { + v := ctx.Value(userIDCtx{}) + if v != nil { + return v + } + return "" +} +func NewUserPlaform(ctx context.Context, platfrom string) context.Context { + return context.WithValue(ctx, formPlatformCtx{}, platfrom) +} + +func FromUserPlaform(ctx context.Context) interface{} { + v := ctx.Value(formPlatformCtx{}) + if v != nil { + return v.(string) + } + return "" +} +func NewUserToken(ctx context.Context, userToken string) context.Context { + return context.WithValue(ctx, userTokenCtx{}, userToken) +} + +func FromUserToken(ctx context.Context) string { + v := ctx.Value(userTokenCtx{}) + if v != nil { + return v.(string) + } + return "" +} + +func NewIsRootUser(ctx context.Context) context.Context { + return context.WithValue(ctx, isRootUserCtx{}, true) +} + +func FromIsRootUser(ctx context.Context) bool { + id := FromUserID(ctx) + return id == "root" +} + +// Set user cache object +type UserCache struct { + RoleIDs []string `json:"rids"` +} + +func ParseUserCache(s string) UserCache { + var a UserCache + if s == "" { + return a + } + + _ = json.Unmarshal([]byte(s), &a) + return a +} + +func (a UserCache) String() string { + return json.MarshalToString(a) +} + +func NewUserCache(ctx context.Context, userCache UserCache) context.Context { + return context.WithValue(ctx, userCacheCtx{}, userCache) +} + +func FromUserCache(ctx context.Context) UserCache { + v := ctx.Value(userCacheCtx{}) + if v != nil { + return v.(UserCache) + } + return UserCache{} +} diff --git a/pkg/util/db.go b/pkg/util/db.go new file mode 100644 index 0000000..5d38bf7 --- /dev/null +++ b/pkg/util/db.go @@ -0,0 +1,124 @@ +package util + +import ( + "context" + + "gorm.io/gorm" + "gorm.io/gorm/clause" +) + +type Trans struct { + DB *gorm.DB +} + +type TransFunc func(context.Context) error + +func (a *Trans) Exec(ctx context.Context, fn TransFunc) error { + if _, ok := FromTrans(ctx); ok { + return fn(ctx) + } + + return a.DB.Transaction(func(db *gorm.DB) error { + return fn(NewTrans(ctx, db)) + }) +} + +func GetDB(ctx context.Context, defDB *gorm.DB) *gorm.DB { + db := defDB + if tdb, ok := FromTrans(ctx); ok { + db = tdb + } + if FromRowLock(ctx) { + db = db.Clauses(clause.Locking{Strength: "UPDATE"}) + } + return db.WithContext(ctx) +} + +func wrapQueryOptions(db *gorm.DB, opts QueryOptions) *gorm.DB { + if len(opts.SelectFields) > 0 { + db = db.Select(opts.SelectFields) + } + if len(opts.OmitFields) > 0 { + db = db.Omit(opts.OmitFields...) + } + if len(opts.OrderFields) > 0 { + db = db.Order(opts.OrderFields.ToSQL()) + } + return db +} + +func WrapPageQuery(ctx context.Context, db *gorm.DB, pp PaginationParam, opts QueryOptions, out interface{}) (*PaginationResult, error) { + if pp.OnlyCount { + var count int64 + err := db.Count(&count).Error + if err != nil { + return nil, err + } + return &PaginationResult{Total: count}, nil + } else if !pp.Pagination { + pageSize := pp.PageSize + if pageSize > 0 { + db = db.Limit(pageSize) + } + + db = wrapQueryOptions(db, opts) + err := db.Find(out).Error + return nil, err + } + + total, err := FindPage(ctx, db, pp, opts, out) + if err != nil { + return nil, err + } + + return &PaginationResult{ + Total: total, + Current: pp.Current, + PageSize: pp.PageSize, + }, nil +} + +func FindPage(ctx context.Context, db *gorm.DB, pp PaginationParam, opts QueryOptions, out interface{}) (int64, error) { + db = db.WithContext(ctx) + var count int64 + err := db.Count(&count).Error + if err != nil { + return 0, err + } else if count == 0 { + return count, nil + } + + current, pageSize := pp.Current, pp.PageSize + if current > 0 && pageSize > 0 { + db = db.Offset((current - 1) * pageSize).Limit(pageSize) + } else if pageSize > 0 { + db = db.Limit(pageSize) + } + + db = wrapQueryOptions(db, opts) + err = db.Find(out).Error + return count, err +} + +func FindOne(ctx context.Context, db *gorm.DB, opts QueryOptions, out interface{}) (bool, error) { + db = db.WithContext(ctx) + db = wrapQueryOptions(db, opts) + result := db.First(out) + if err := result.Error; err != nil { + if err == gorm.ErrRecordNotFound { + return false, nil + } + return false, err + } + return true, nil +} + +func Exists(ctx context.Context, db *gorm.DB) (bool, error) { + db = db.WithContext(ctx) + var count int64 + result := db.Count(&count) + if err := result.Error; err != nil { + return false, err + } + return count > 0, nil +} diff --git a/pkg/util/gin.go b/pkg/util/gin.go new file mode 100644 index 0000000..6538838 --- /dev/null +++ b/pkg/util/gin.go @@ -0,0 +1,136 @@ +package util + +import ( + "fmt" + "net/http" + "reflect" + "strings" + + "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/binding" + "github.guxuan/haibei/pkg/encoding/json" + "github.guxuan/haibei/pkg/errors" + "github.guxuan/haibei/pkg/logging" + "go.uber.org/zap" +) + +// Get access token from header or query parameter +func GetToken(c *gin.Context) string { + var token string + auth := c.GetHeader("Authorization") + prefix := "Bearer " + + if auth != "" && strings.HasPrefix(auth, prefix) { + token = auth[len(prefix):] + } else { + token = auth + } + + if token == "" { + token = c.Query("accessToken") + } + + return token +} + +// Get body data from context +func GetBodyData(c *gin.Context) []byte { + if v, ok := c.Get(ReqBodyKey); ok { + if b, ok := v.([]byte); ok { + return b + } + } + return nil +} + +// Parse body json data to struct +func ParseJSON(c *gin.Context, obj interface{}) error { + if err := c.ShouldBindJSON(obj); err != nil { + return errors.BadRequest("", "Failed to parse json: %s", err.Error()) + } + return nil +} + +// Parse query parameter to struct +func ParseQuery(c *gin.Context, obj interface{}) error { + if err := c.ShouldBindQuery(obj); err != nil { + return errors.BadRequest("", "Failed to parse query: %s", err.Error()) + } + return nil +} + +// Parse body form data to struct +func ParseForm(c *gin.Context, obj interface{}) error { + if err := c.ShouldBindWith(obj, binding.Form); err != nil { + return errors.BadRequest("", "Failed to parse form: %s", err.Error()) + } + return nil +} + +// Response json data with status code +func ResJSON(c *gin.Context, status int, v interface{}) { + buf, err := json.Marshal(v) + if err != nil { + panic(err) + } + + c.Set(ResBodyKey, buf) + c.Data(status, "application/json; charset=utf-8", buf) + c.Abort() +} + +func ResSuccess(c *gin.Context, v interface{}) { + ResJSON(c, http.StatusOK, ResponseResult{ + Success: true, + Data: v, + }) +} + +func ResOK(c *gin.Context) { + ResJSON(c, http.StatusOK, ResponseResult{ + Success: true, + }) +} + +func ResPage(c *gin.Context, v interface{}, pr *PaginationResult) { + var total int64 + if pr != nil { + total = pr.Total + } + + reflectValue := reflect.Indirect(reflect.ValueOf(v)) + if reflectValue.IsNil() { + v = make([]interface{}, 0) + } + + ResJSON(c, http.StatusOK, ResponseResult{ + Success: true, + Data: v, + Total: total, + }) +} + +func ResError(c *gin.Context, err error, status ...int) { + var ierr *errors.Error + if e, ok := errors.As(err); ok { + ierr = e + } else { + ierr = errors.FromError(errors.InternalServerError("", err.Error())) + } + + code := int(ierr.Code) + if len(status) > 0 { + code = status[0] + } + + if code >= 500 { + ctx := c.Request.Context() + ctx = logging.NewTag(ctx, logging.TagKeySystem) + ctx = logging.NewStack(ctx, fmt.Sprintf("%+v", err)) + logging.Context(ctx).Error("Internal server error", zap.Error(err)) + ierr.Detail = http.StatusText(http.StatusInternalServerError) + } + + ierr.Code = int32(code) + ResJSON(c, code, ResponseResult{Error: ierr}) +} diff --git a/pkg/util/id.go b/pkg/util/id.go new file mode 100644 index 0000000..48d7b6c --- /dev/null +++ b/pkg/util/id.go @@ -0,0 +1,20 @@ +package util + +import ( + "github.com/google/uuid" + "github.com/rs/xid" +) + +// The function "NewXID" generates a new unique identifier (XID) and returns it as a string. +func NewXID() string { + return xid.New().String() +} + +// The function generates a new UUID and panics if there is an error. +func MustNewUUID() string { + v, err := uuid.NewRandom() + if err != nil { + panic(err) + } + return v.String() +} diff --git a/pkg/util/id_test.go b/pkg/util/id_test.go new file mode 100644 index 0000000..4e8f9aa --- /dev/null +++ b/pkg/util/id_test.go @@ -0,0 +1,14 @@ +package util + +import ( + "strings" + "testing" +) + +func TestNewXID(t *testing.T) { + t.Logf("xid: %s", strings.ToUpper(NewXID())) +} + +func TestMustNewUUID(t *testing.T) { + t.Logf("uuid: %s", strings.ToUpper(MustNewUUID())) +} diff --git a/pkg/util/option.go b/pkg/util/option.go new file mode 100644 index 0000000..f60b227 --- /dev/null +++ b/pkg/util/option.go @@ -0,0 +1,7 @@ +package util + +type Option struct { + Value interface{} `json:"value"` + Label string `json:"label"` + Children []Option `json:"children"` +} diff --git a/pkg/util/queryParam.go b/pkg/util/queryParam.go new file mode 100644 index 0000000..25176b1 --- /dev/null +++ b/pkg/util/queryParam.go @@ -0,0 +1,16 @@ +package util + +import ( + "github.com/gin-gonic/gin" + "strconv" +) + +func GetQueryID(c *gin.Context) (uint, error) { + u64, err := strconv.ParseUint(c.Param("id"), 10, 32) + if err != nil { + return 0, err + + } + return uint(u64), nil + +} diff --git a/pkg/util/rand.go b/pkg/util/rand.go new file mode 100644 index 0000000..60f6e06 --- /dev/null +++ b/pkg/util/rand.go @@ -0,0 +1,22 @@ +package util + +import ( + "encoding/binary" + "math/rand" + "strconv" + "strings" + "time" +) + +// The RandomizedIPAddr function generates a random IP address. +func RandomizedIPAddr() string { + raw := make([]byte, 4) + rd := rand.New(rand.NewSource(time.Now().UnixNano())) + binary.LittleEndian.PutUint32(raw, rd.Uint32()) + + ips := make([]string, len(raw)) + for i, b := range raw { + ips[i] = strconv.FormatInt(int64(b), 10) + } + return strings.Join(ips, ".") +} diff --git a/pkg/util/schema.go b/pkg/util/schema.go new file mode 100644 index 0000000..9e89f7d --- /dev/null +++ b/pkg/util/schema.go @@ -0,0 +1,61 @@ +package util + +import "github.guxuan/haibei/pkg/errors" + +const ( + ReqBodyKey = "req-body" + ResBodyKey = "res-body" + TreePathDelimiter = "." +) + +type ResponseResult struct { + Success bool `json:"success"` + Data interface{} `json:"data,omitempty"` + Total int64 `json:"total,omitempty"` + Error *errors.Error `json:"error,omitempty"` +} + +type PaginationResult struct { + Total int64 `json:"total"` + Current int `json:"current"` + PageSize int `json:"pageSize"` +} + +type PaginationParam struct { + Pagination bool `form:"-"` + OnlyCount bool `form:"-"` + Current int `form:"current"` + PageSize int `form:"pageSize" binding:"max=100"` +} + +type QueryOptions struct { + SelectFields []string + OmitFields []string + OrderFields OrderByParams +} + +type Direction string + +const ( + ASC Direction = "ASC" + DESC Direction = "DESC" +) + +type OrderByParam struct { + Field string + Direction Direction +} + +type OrderByParams []OrderByParam + +func (a OrderByParams) ToSQL() string { + if len(a) == 0 { + return "" + } + + var sql string + for _, v := range a { + sql += v.Field + " " + string(v.Direction) + "," + } + return sql[:len(sql)-1] +} diff --git a/scripts/restart.sh b/scripts/restart.sh new file mode 100644 index 0000000..0371195 --- /dev/null +++ b/scripts/restart.sh @@ -0,0 +1,2 @@ +bash ./stop.sh +bash ./start.sh \ No newline at end of file diff --git a/scripts/start.sh b/scripts/start.sh new file mode 100644 index 0000000..354d72d --- /dev/null +++ b/scripts/start.sh @@ -0,0 +1 @@ +./ginadmin start -d configs -c dev -s dist --daemon \ No newline at end of file diff --git a/scripts/stop.sh b/scripts/stop.sh new file mode 100644 index 0000000..ff1a008 --- /dev/null +++ b/scripts/stop.sh @@ -0,0 +1 @@ +./ginadmin stop \ No newline at end of file diff --git a/shibei-linux b/shibei-linux new file mode 100644 index 0000000..3cd24d2 Binary files /dev/null and b/shibei-linux differ diff --git a/test/menu.js b/test/menu.js new file mode 100644 index 0000000..3f3b919 --- /dev/null +++ b/test/menu.js @@ -0,0 +1,54 @@ + + +const menus =[ + { + name: '数据看板/首页', + desc :"内容暂定", + child:[] + }, + { + name: '区域管理', + desc :"区域列表/查询/修改/删除/添加", + }, + { + name: '用户管理', + desc :"用户列表/查询/修改/删除/添加", + child:[ + "业务列表", + "意向客户列表", + "游客列表" + ] + }, + { + name: '活动管理', + desc :"活动列表/查询/修改/删除/添加", + + },{ + name: '问卷管理', + desc :"问卷列表/查询/修改/删除/添加", + },{ + name: '轮播图管理', + desc :"罗伯图列表/查询/修改/删除/添加", + + },{ + name: '通知管理', + desc :"通知列表/查询/修改/删除/添加", + + },{ + name: '积分管理', + desc :"积分列表/查询/修改/删除/添加", + + },{ + name: '商品管理', + desc :"商品列表/查询/修改/删除/添加", + + },{ + name: '分类管理', + desc :"分类列表/查询/修改/删除/添加", + + },{ + name: '订单管理', + desc :"积分列表/查询/修改/删除/添加", + + } +] \ No newline at end of file diff --git a/test/menu_test.go b/test/menu_test.go new file mode 100644 index 0000000..0f51abc --- /dev/null +++ b/test/menu_test.go @@ -0,0 +1,59 @@ +package test + +import ( + "net/http" + "testing" + + "github.com/stretchr/testify/assert" + "github.gu + "github.guxuan/haibei/internal/mods/rbac/schema" + "github.com/stretchr/testify/assert" +) + +func TestMenu(t *testing.T) { + e := tester(t) + + menuFormItem := schema.MenuForm{ + Code: "menu", + Name: "Menu management", + Description: "Menu management", + Sequence: 9, + Type: "page", + Path: "/system/menu", + Properties: `{"icon":"menu"}`, + Status: schema.MenuStatusEnabled, + } + + var menu schema.Menu + e.POST(baseAPI + "/menus").WithJSON(menuFormItem). + Expect().Status(http.StatusOK).JSON().Decode(&util.ResponseResult{Data: &menu}) + + assert := assert.New(t) + assert.NotEmpty(menu.ID) + assert.Equal(menuFormItem.Code, menu.Code) + assert.Equal(menuFormItem.Name, menu.Name) + assert.Equal(menuFormItem.Description, menu.Description) + assert.Equal(menuFormItem.Sequence, menu.Sequence) + assert.Equal(menuFormItem.Type, menu.Type) + assert.Equal(menuFormItem.Path, menu.Path) + assert.Equal(menuFormItem.Properties, menu.Properties) + assert.Equal(menuFormItem.Status, menu.Status) + + var menus schema.Menus + e.GET(baseAPI + "/menus").Expect().Status(http.StatusOK).JSON().Decode(&util.ResponseResult{Data: &menus}) + assert.GreaterOrEqual(len(menus), 1) + + newName := "Menu management 1" + newStatus := schema.MenuStatusDisabled + menu.Name = newName + menu.Status = newStatus + e.PUT(baseAPI + "/menus/" + menu.ID).WithJSON(menu).Expect().Status(http.StatusOK) + + var getMenu schema.Menu + e.GET(baseAPI + "/menus/" + menu.ID).Expect().Status(http.StatusOK).JSON().Decode(&util.ResponseResult{Data: &getMenu}) + assert.Equal(newName, getMenu.Name) + assert.Equal(newStatus, getMenu.Status) + + e.DELETE(baseAPI + "/menus/" + menu.ID).Expect().Status(http.StatusOK) + e.GET(baseAPI + "/menus/" + menu.ID).Expect().Status(http.StatusNotFound) +} diff --git a/test/role_test.go b/test/role_test.go new file mode 100644 index 0000000..91ac83a --- /dev/null +++ b/test/role_test.go @@ -0,0 +1,82 @@ +package test + +import ( + "net/http" + "testing" + + "github.com/stretchr/testify/assert" + "github.guxuan/haibei/internal/mods/rbac/schema" + "github.guxuan/haibei/pkg/util" +) + +func TestRole(t *testing.T) { + e := tester(t) + + menuFormItem := schema.MenuForm{ + Code: "role", + Name: "Role management", + Description: "Role management", + Sequence: 8, + Type: "page", + Path: "/system/role", + Properties: `{"icon":"role"}`, + Status: schema.MenuStatusEnabled, + } + + var menu schema.Menu + e.POST(baseAPI + "/menus").WithJSON(menuFormItem). + Expect().Status(http.StatusOK).JSON().Decode(&util.ResponseResult{Data: &menu}) + + assert := assert.New(t) + assert.NotEmpty(menu.ID) + assert.Equal(menuFormItem.Code, menu.Code) + assert.Equal(menuFormItem.Name, menu.Name) + assert.Equal(menuFormItem.Description, menu.Description) + assert.Equal(menuFormItem.Sequence, menu.Sequence) + assert.Equal(menuFormItem.Type, menu.Type) + assert.Equal(menuFormItem.Path, menu.Path) + assert.Equal(menuFormItem.Properties, menu.Properties) + assert.Equal(menuFormItem.Status, menu.Status) + + roleFormItem := schema.RoleForm{ + Code: "admin", + Name: "Administrator", + Menus: schema.RoleMenus{ + {MenuID: menu.ID}, + }, + Description: "Administrator", + Sequence: 9, + Status: schema.RoleStatusEnabled, + } + + var role schema.Role + e.POST(baseAPI + "/roles").WithJSON(roleFormItem).Expect().Status(http.StatusOK).JSON().Decode(&util.ResponseResult{Data: &role}) + assert.NotEmpty(role.ID) + assert.Equal(roleFormItem.Code, role.Code) + assert.Equal(roleFormItem.Name, role.Name) + assert.Equal(roleFormItem.Description, role.Description) + assert.Equal(roleFormItem.Sequence, role.Sequence) + assert.Equal(roleFormItem.Status, role.Status) + assert.Equal(len(roleFormItem.Menus), len(role.Menus)) + + var roles schema.Roles + e.GET(baseAPI + "/roles").Expect().Status(http.StatusOK).JSON().Decode(&util.ResponseResult{Data: &roles}) + assert.GreaterOrEqual(len(roles), 1) + + newName := "Administrator 1" + newStatus := schema.RoleStatusDisabled + role.Name = newName + role.Status = newStatus + e.PUT(baseAPI + "/roles/" + role.ID).WithJSON(role).Expect().Status(http.StatusOK) + + var getRole schema.Role + e.GET(baseAPI + "/roles/" + role.ID).Expect().Status(http.StatusOK).JSON().Decode(&util.ResponseResult{Data: &getRole}) + assert.Equal(newName, getRole.Name) + assert.Equal(newStatus, getRole.Status) + + e.DELETE(baseAPI + "/roles/" + role.ID).Expect().Status(http.StatusOK) + e.GET(baseAPI + "/roles/" + role.ID).Expect().Status(http.StatusNotFound) + + e.DELETE(baseAPI + "/menus/" + menu.ID).Expect().Status(http.StatusOK) + e.GET(baseAPI + "/menus/" + menu.ID).Expect().Status(http.StatusNotFound) +} diff --git a/test/test.go b/test/test.go new file mode 100644 index 0000000..0dd2c6c --- /dev/null +++ b/test/test.go @@ -0,0 +1,55 @@ +package test + +import ( + "context" + "net/http" + "os" + "testing" + + "github.com/gavv/httpexpect/v2" + "github.com/gin-gonic/gin" + "github.guxuan/haibei/internal/config" + "github.guxuan/haibei/internal/wirex" +) + +const ( + baseAPI = "/api/v1" +) + +var ( + app *gin.Engine +) + +func init() { + config.MustLoad("") + + _ = os.RemoveAll(config.C.Storage.DB.DSN) + ctx := context.Background() + injector, _, err := wirex.BuildInjector(ctx) + if err != nil { + panic(err) + } + + if err := injector.M.Init(ctx); err != nil { + panic(err) + } + + app = gin.New() + err = injector.M.RegisterRouters(ctx, app) + if err != nil { + panic(err) + } +} + +func tester(t *testing.T) *httpexpect.Expect { + return httpexpect.WithConfig(httpexpect.Config{ + Client: &http.Client{ + Transport: httpexpect.NewBinder(app), + Jar: httpexpect.NewCookieJar(), + }, + Reporter: httpexpect.NewAssertReporter(t), + Printers: []httpexpect.Printer{ + httpexpect.NewDebugPrinter(t, true), + }, + }) +} diff --git a/test/user_test.go b/test/user_test.go new file mode 100644 index 0000000..46b6081 --- /dev/null +++ b/test/user_test.go @@ -0,0 +1,108 @@ +package test + +import ( + "net/http" + "testing" + + "github.com/stretchr/testify/assert" + "github.guxuan/haibei/internal/mods/rbac/schema" + "github.guxuan/haibei/pkg/crypto/hash" + "github.guxuan/haibei/pkg/util" +) + +func TestUser(t *testing.T) { + e := tester(t) + + menuFormItem := schema.MenuForm{ + Code: "user", + Name: "User management", + Description: "User management", + Sequence: 7, + Type: "page", + Path: "/system/user", + Properties: `{"icon":"user"}`, + Status: schema.MenuStatusEnabled, + } + + var menu schema.Menu + e.POST(baseAPI + "/menus").WithJSON(menuFormItem). + Expect().Status(http.StatusOK).JSON().Decode(&util.ResponseResult{Data: &menu}) + + assert := assert.New(t) + assert.NotEmpty(menu.ID) + assert.Equal(menuFormItem.Code, menu.Code) + assert.Equal(menuFormItem.Name, menu.Name) + assert.Equal(menuFormItem.Description, menu.Description) + assert.Equal(menuFormItem.Sequence, menu.Sequence) + assert.Equal(menuFormItem.Type, menu.Type) + assert.Equal(menuFormItem.Path, menu.Path) + assert.Equal(menuFormItem.Properties, menu.Properties) + assert.Equal(menuFormItem.Status, menu.Status) + + roleFormItem := schema.RoleForm{ + Code: "user", + Name: "Normal", + Menus: schema.RoleMenus{ + {MenuID: menu.ID}, + }, + Description: "Normal", + Sequence: 8, + Status: schema.RoleStatusEnabled, + } + + var role schema.Role + e.POST(baseAPI + "/roles").WithJSON(roleFormItem).Expect().Status(http.StatusOK).JSON().Decode(&util.ResponseResult{Data: &role}) + assert.NotEmpty(role.ID) + assert.Equal(roleFormItem.Code, role.Code) + assert.Equal(roleFormItem.Name, role.Name) + assert.Equal(roleFormItem.Description, role.Description) + assert.Equal(roleFormItem.Sequence, role.Sequence) + assert.Equal(roleFormItem.Status, role.Status) + assert.Equal(len(roleFormItem.Menus), len(role.Menus)) + + userFormItem := schema.UserForm{ + Username: "test", + Name: "Test", + Password: hash.MD5String("test"), + Phone: "0720", + Email: "test@gmail.com", + Remark: "test user", + Status: schema.UserStatusActivated, + Roles: schema.UserRoles{{RoleID: role.ID}}, + } + + var user schema.User + e.POST(baseAPI + "/users").WithJSON(userFormItem).Expect().Status(http.StatusOK).JSON().Decode(&util.ResponseResult{Data: &user}) + assert.NotEmpty(user.ID) + assert.Equal(userFormItem.Username, user.Username) + assert.Equal(userFormItem.Name, user.Name) + assert.Equal(userFormItem.Phone, user.Phone) + assert.Equal(userFormItem.Email, user.Email) + assert.Equal(userFormItem.Remark, user.Remark) + assert.Equal(userFormItem.Status, user.Status) + assert.Equal(len(userFormItem.Roles), len(user.Roles)) + + var users schema.Users + e.GET(baseAPI+"/users").WithQuery("username", userFormItem.Username).Expect().Status(http.StatusOK).JSON().Decode(&util.ResponseResult{Data: &users}) + assert.GreaterOrEqual(len(users), 1) + + newName := "Test 1" + newStatus := schema.UserStatusFreezed + user.Name = newName + user.Status = newStatus + e.PUT(baseAPI + "/users/" + user.ID).WithJSON(user).Expect().Status(http.StatusOK) + + var getUser schema.User + e.GET(baseAPI + "/users/" + user.ID).Expect().Status(http.StatusOK).JSON().Decode(&util.ResponseResult{Data: &getUser}) + assert.Equal(newName, getUser.Name) + assert.Equal(newStatus, getUser.Status) + + e.DELETE(baseAPI + "/users/" + user.ID).Expect().Status(http.StatusOK) + e.GET(baseAPI + "/users/" + user.ID).Expect().Status(http.StatusNotFound) + + e.DELETE(baseAPI + "/roles/" + role.ID).Expect().Status(http.StatusOK) + e.GET(baseAPI + "/roles/" + role.ID).Expect().Status(http.StatusNotFound) + + e.DELETE(baseAPI + "/menus/" + menu.ID).Expect().Status(http.StatusOK) + e.GET(baseAPI + "/menus/" + menu.ID).Expect().Status(http.StatusNotFound) +}