From 91436ca88a9744f5503861f0adef5cc2c0a5d23f Mon Sep 17 00:00:00 2001 From: Daniel Date: Thu, 19 Jun 2025 10:30:46 +0800 Subject: [PATCH] first commit --- .devcontainer/.env | 5 + .devcontainer/Dockerfile | 13 + .devcontainer/devcontainer.json | 9 + .devcontainer/docker-compose.yml | 42 + .gitignore | 33 + Dockerfile | 28 + Makefile | 51 + README.md | 28 + README_EN.md | 250 + cmd/start.go | 105 + cmd/stop.go | 40 + cmd/version.go | 19 + configs/dev/logging.toml | 26 + configs/dev/middleware.toml | 76 + configs/dev/server.toml | 89 + configs/menu.json | 240 + configs/menu_cn.json | 240 + configs/rbac_model.conf | 14 + go.mod | 114 + go.sum | 528 ++ internal/bootstrap/bootstrap.go | 106 + internal/bootstrap/http.go | 192 + internal/bootstrap/logger.go | 43 + internal/config/config.go | 166 + internal/config/consts.go | 16 + internal/config/middleware.go | 90 + internal/config/parse.go | 84 + internal/mods/mods.go | 59 + internal/mods/rbac/api/article.api.go | 127 + internal/mods/rbac/api/banner.api.go | 127 + internal/mods/rbac/api/job.api.go | 244 + internal/mods/rbac/api/logger.api.go | 45 + internal/mods/rbac/api/login.api.go | 182 + internal/mods/rbac/api/memorabilia.api.go | 128 + internal/mods/rbac/api/menu.api.go | 132 + internal/mods/rbac/api/product.api.go | 178 + internal/mods/rbac/api/role.api.go | 133 + internal/mods/rbac/api/team.api.go | 126 + internal/mods/rbac/api/upload.api.go | 24 + internal/mods/rbac/api/user.api.go | 152 + internal/mods/rbac/api/video.api.go | 127 + internal/mods/rbac/api/web.go | 56 + internal/mods/rbac/api/webSite.api.go | 82 + internal/mods/rbac/biz/article.biz.go | 100 + internal/mods/rbac/biz/banner.biz.go | 99 + internal/mods/rbac/biz/job.biz.go | 178 + internal/mods/rbac/biz/logger.biz.go | 31 + internal/mods/rbac/biz/login.biz.go | 374 ++ internal/mods/rbac/biz/memorabilia.biz.go | 99 + internal/mods/rbac/biz/menu.biz.go | 515 ++ internal/mods/rbac/biz/product.biz.go | 102 + internal/mods/rbac/biz/role.biz.go | 179 + internal/mods/rbac/biz/team.biz.go | 94 + internal/mods/rbac/biz/upload.biz.go | 54 + internal/mods/rbac/biz/user.biz.go | 227 + internal/mods/rbac/biz/video.biz.go | 99 + internal/mods/rbac/biz/webSite.biz.go | 63 + internal/mods/rbac/casbin.go | 222 + internal/mods/rbac/dal/article.dal.go | 89 + internal/mods/rbac/dal/banner.dal.go | 88 + internal/mods/rbac/dal/job.dal.go | 83 + internal/mods/rbac/dal/jobArea.dal.go | 58 + internal/mods/rbac/dal/logger.dal.go | 64 + internal/mods/rbac/dal/memorabilia.dal.go | 86 + internal/mods/rbac/dal/menu.dal.go | 165 + internal/mods/rbac/dal/menu_resource.dal.go | 101 + internal/mods/rbac/dal/product.dal.go | 83 + internal/mods/rbac/dal/productCategory.dal.go | 59 + internal/mods/rbac/dal/role.dal.go | 100 + internal/mods/rbac/dal/role_menu.dal.go | 98 + internal/mods/rbac/dal/team.go | 81 + internal/mods/rbac/dal/user.dal.go | 124 + internal/mods/rbac/dal/user_role.dal.go | 108 + internal/mods/rbac/dal/video.dal.go | 88 + internal/mods/rbac/dal/website.dal.go | 46 + internal/mods/rbac/main.go | 217 + internal/mods/rbac/resp/job.go | 17 + internal/mods/rbac/resp/web.go | 1 + internal/mods/rbac/schema/article.go | 91 + internal/mods/rbac/schema/banner.go | 84 + internal/mods/rbac/schema/job.go | 108 + internal/mods/rbac/schema/logger.go | 53 + internal/mods/rbac/schema/login.go | 38 + internal/mods/rbac/schema/memorabilia.go | 79 + internal/mods/rbac/schema/menu.go | 178 + internal/mods/rbac/schema/menu_resource.go | 56 + internal/mods/rbac/schema/product.go | 114 + internal/mods/rbac/schema/role.go | 80 + internal/mods/rbac/schema/role_menu.go | 54 + internal/mods/rbac/schema/team.go | 84 + internal/mods/rbac/schema/user.go | 105 + internal/mods/rbac/schema/user_role.go | 74 + internal/mods/rbac/schema/video.go | 93 + internal/mods/rbac/schema/website.go | 59 + internal/mods/rbac/wire.go | 66 + internal/swagger/docs.go | 5436 +++++++++++++++++ internal/swagger/swagger.json | 5410 ++++++++++++++++ internal/swagger/swagger.yaml | 3430 +++++++++++ internal/utility/prom/prom.go | 36 + internal/wirex/injector.go | 129 + internal/wirex/wire.go | 27 + internal/wirex/wire_gen.go | 253 + main.go | 35 + pkg/cachex/badger.go | 167 + pkg/cachex/badger_test.go | 56 + pkg/cachex/cache.go | 119 + pkg/cachex/redis.go | 172 + pkg/cachex/redis_test.go | 56 + pkg/crypto/aes/aes.go | 69 + pkg/crypto/aes/aes_test.go | 23 + pkg/crypto/hash/hash.go | 47 + pkg/crypto/hash/hash_test.go | 26 + pkg/crypto/rand/rand.go | 116 + pkg/crypto/rand/rand_test.go | 27 + pkg/encoding/json/json.go | 25 + pkg/encoding/toml/toml.go | 32 + pkg/encoding/toml/toml_test.go | 35 + pkg/encoding/yaml/yaml.go | 12 + pkg/errors/errors.go | 273 + pkg/gormx/gorm.go | 162 + pkg/jwtx/cache.go | 62 + pkg/jwtx/jwt.go | 190 + pkg/jwtx/jwt_test.go | 37 + pkg/jwtx/store.go | 68 + pkg/jwtx/token.go | 34 + pkg/logging/gorm.go | 97 + pkg/logging/hook.go | 125 + pkg/logging/init.go | 158 + pkg/logging/logger.go | 119 + pkg/mail/mail.go | 72 + pkg/middleware/auth.go | 40 + pkg/middleware/casbin.go | 46 + pkg/middleware/copybody.go | 66 + pkg/middleware/cors.go | 65 + pkg/middleware/logger.go | 88 + pkg/middleware/middleware.go | 41 + pkg/middleware/ratelimiter.go | 144 + pkg/middleware/recover.go | 59 + pkg/middleware/static.go | 31 + pkg/middleware/trace.go | 48 + pkg/oss/minio.go | 134 + pkg/oss/oss.go | 75 + pkg/oss/s3.go | 179 + pkg/promx/gin.go | 41 + pkg/promx/prom.go | 278 + pkg/util/command.go | 45 + pkg/util/context.go | 115 + pkg/util/db.go | 124 + pkg/util/gin.go | 136 + pkg/util/id.go | 20 + pkg/util/id_test.go | 14 + pkg/util/rand.go | 22 + pkg/util/schema.go | 61 + scripts/restart.sh | 2 + scripts/start.sh | 1 + scripts/stop.sh | 1 + 156 files changed, 29188 insertions(+) create mode 100644 .devcontainer/.env create mode 100644 .devcontainer/Dockerfile create mode 100644 .devcontainer/devcontainer.json create mode 100644 .devcontainer/docker-compose.yml create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 Makefile create mode 100644 README.md create mode 100644 README_EN.md create mode 100644 cmd/start.go create mode 100644 cmd/stop.go create mode 100644 cmd/version.go create mode 100644 configs/dev/logging.toml create mode 100644 configs/dev/middleware.toml create mode 100644 configs/dev/server.toml create mode 100644 configs/menu.json create mode 100644 configs/menu_cn.json create mode 100644 configs/rbac_model.conf create mode 100644 go.mod create mode 100644 go.sum create mode 100644 internal/bootstrap/bootstrap.go create mode 100644 internal/bootstrap/http.go create mode 100644 internal/bootstrap/logger.go create mode 100644 internal/config/config.go create mode 100644 internal/config/consts.go create mode 100644 internal/config/middleware.go create mode 100644 internal/config/parse.go create mode 100644 internal/mods/mods.go create mode 100644 internal/mods/rbac/api/article.api.go create mode 100644 internal/mods/rbac/api/banner.api.go create mode 100644 internal/mods/rbac/api/job.api.go create mode 100644 internal/mods/rbac/api/logger.api.go create mode 100644 internal/mods/rbac/api/login.api.go create mode 100644 internal/mods/rbac/api/memorabilia.api.go create mode 100644 internal/mods/rbac/api/menu.api.go create mode 100644 internal/mods/rbac/api/product.api.go create mode 100644 internal/mods/rbac/api/role.api.go create mode 100644 internal/mods/rbac/api/team.api.go create mode 100644 internal/mods/rbac/api/upload.api.go create mode 100644 internal/mods/rbac/api/user.api.go create mode 100644 internal/mods/rbac/api/video.api.go create mode 100644 internal/mods/rbac/api/web.go create mode 100644 internal/mods/rbac/api/webSite.api.go create mode 100644 internal/mods/rbac/biz/article.biz.go create mode 100644 internal/mods/rbac/biz/banner.biz.go create mode 100644 internal/mods/rbac/biz/job.biz.go create mode 100644 internal/mods/rbac/biz/logger.biz.go create mode 100644 internal/mods/rbac/biz/login.biz.go create mode 100644 internal/mods/rbac/biz/memorabilia.biz.go create mode 100644 internal/mods/rbac/biz/menu.biz.go create mode 100644 internal/mods/rbac/biz/product.biz.go create mode 100644 internal/mods/rbac/biz/role.biz.go create mode 100644 internal/mods/rbac/biz/team.biz.go create mode 100644 internal/mods/rbac/biz/upload.biz.go create mode 100644 internal/mods/rbac/biz/user.biz.go create mode 100644 internal/mods/rbac/biz/video.biz.go create mode 100644 internal/mods/rbac/biz/webSite.biz.go create mode 100644 internal/mods/rbac/casbin.go create mode 100644 internal/mods/rbac/dal/article.dal.go create mode 100644 internal/mods/rbac/dal/banner.dal.go create mode 100644 internal/mods/rbac/dal/job.dal.go create mode 100644 internal/mods/rbac/dal/jobArea.dal.go create mode 100644 internal/mods/rbac/dal/logger.dal.go create mode 100644 internal/mods/rbac/dal/memorabilia.dal.go create mode 100644 internal/mods/rbac/dal/menu.dal.go create mode 100644 internal/mods/rbac/dal/menu_resource.dal.go create mode 100644 internal/mods/rbac/dal/product.dal.go create mode 100644 internal/mods/rbac/dal/productCategory.dal.go create mode 100644 internal/mods/rbac/dal/role.dal.go create mode 100644 internal/mods/rbac/dal/role_menu.dal.go create mode 100644 internal/mods/rbac/dal/team.go create mode 100644 internal/mods/rbac/dal/user.dal.go create mode 100644 internal/mods/rbac/dal/user_role.dal.go create mode 100644 internal/mods/rbac/dal/video.dal.go create mode 100644 internal/mods/rbac/dal/website.dal.go create mode 100644 internal/mods/rbac/main.go create mode 100644 internal/mods/rbac/resp/job.go create mode 100644 internal/mods/rbac/resp/web.go create mode 100644 internal/mods/rbac/schema/article.go create mode 100644 internal/mods/rbac/schema/banner.go create mode 100644 internal/mods/rbac/schema/job.go create mode 100644 internal/mods/rbac/schema/logger.go create mode 100644 internal/mods/rbac/schema/login.go create mode 100644 internal/mods/rbac/schema/memorabilia.go create mode 100644 internal/mods/rbac/schema/menu.go create mode 100644 internal/mods/rbac/schema/menu_resource.go create mode 100644 internal/mods/rbac/schema/product.go create mode 100644 internal/mods/rbac/schema/role.go create mode 100644 internal/mods/rbac/schema/role_menu.go create mode 100644 internal/mods/rbac/schema/team.go create mode 100644 internal/mods/rbac/schema/user.go create mode 100644 internal/mods/rbac/schema/user_role.go create mode 100644 internal/mods/rbac/schema/video.go create mode 100644 internal/mods/rbac/schema/website.go create mode 100644 internal/mods/rbac/wire.go create mode 100644 internal/swagger/docs.go create mode 100644 internal/swagger/swagger.json create mode 100644 internal/swagger/swagger.yaml create mode 100644 internal/utility/prom/prom.go create mode 100644 internal/wirex/injector.go create mode 100644 internal/wirex/wire.go create mode 100644 internal/wirex/wire_gen.go create mode 100644 main.go create mode 100644 pkg/cachex/badger.go create mode 100644 pkg/cachex/badger_test.go create mode 100644 pkg/cachex/cache.go create mode 100644 pkg/cachex/redis.go create mode 100644 pkg/cachex/redis_test.go create mode 100644 pkg/crypto/aes/aes.go create mode 100644 pkg/crypto/aes/aes_test.go create mode 100644 pkg/crypto/hash/hash.go create mode 100644 pkg/crypto/hash/hash_test.go create mode 100644 pkg/crypto/rand/rand.go create mode 100644 pkg/crypto/rand/rand_test.go create mode 100644 pkg/encoding/json/json.go create mode 100644 pkg/encoding/toml/toml.go create mode 100644 pkg/encoding/toml/toml_test.go create mode 100644 pkg/encoding/yaml/yaml.go create mode 100644 pkg/errors/errors.go create mode 100644 pkg/gormx/gorm.go create mode 100644 pkg/jwtx/cache.go create mode 100644 pkg/jwtx/jwt.go create mode 100644 pkg/jwtx/jwt_test.go create mode 100644 pkg/jwtx/store.go create mode 100644 pkg/jwtx/token.go create mode 100644 pkg/logging/gorm.go create mode 100644 pkg/logging/hook.go create mode 100644 pkg/logging/init.go create mode 100644 pkg/logging/logger.go create mode 100644 pkg/mail/mail.go create mode 100644 pkg/middleware/auth.go create mode 100644 pkg/middleware/casbin.go create mode 100644 pkg/middleware/copybody.go create mode 100644 pkg/middleware/cors.go create mode 100644 pkg/middleware/logger.go create mode 100644 pkg/middleware/middleware.go create mode 100644 pkg/middleware/ratelimiter.go create mode 100644 pkg/middleware/recover.go create mode 100644 pkg/middleware/static.go create mode 100644 pkg/middleware/trace.go create mode 100644 pkg/oss/minio.go create mode 100644 pkg/oss/oss.go create mode 100644 pkg/oss/s3.go create mode 100644 pkg/promx/gin.go create mode 100644 pkg/promx/prom.go create mode 100644 pkg/util/command.go create mode 100644 pkg/util/context.go create mode 100644 pkg/util/db.go create mode 100644 pkg/util/gin.go create mode 100644 pkg/util/id.go create mode 100644 pkg/util/id_test.go create mode 100644 pkg/util/rand.go create mode 100644 pkg/util/schema.go create mode 100644 scripts/restart.sh create mode 100644 scripts/start.sh create mode 100644 scripts/stop.sh 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..2947da2 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,13 @@ +FROM mcr.microsoft.com/devcontainers/go:1.22-bookworm + +ARG APP=hailinservice + +# 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..42a822d --- /dev/null +++ b/.gitignore @@ -0,0 +1,33 @@ +# 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 +/hailinservice +/hailinservice_linux_amd64 +/hailinservice.lock +/release +/data +/uploads +/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..53d242c --- /dev/null +++ b/Dockerfile @@ -0,0 +1,28 @@ +FROM golang:alpine as builder + +ARG APP=hailinservice +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=hailinservice +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 ["hailinservice", "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..d156e7a --- /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 = hailinservice +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..06647c6 --- /dev/null +++ b/README.md @@ -0,0 +1,28 @@ +# hailinService + +> Hailinservice API service + +## 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..c4eff05 --- /dev/null +++ b/cmd/start.go @@ -0,0 +1,105 @@ +package cmd + +import ( + "context" + "fmt" + "os" + "os/exec" + "path/filepath" + "strings" + + "github.com/guxuan/hailin_service/internal/bootstrap" + "github.com/guxuan/hailin_service/internal/config" + "github.com/urfave/cli/v2" +) + +// 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..c52793f --- /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 = "data/log/hailinservice.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 = "sqlite3" # sqlite3/mysql/postgres +DSN = "data/hailinservice.db" +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..1454768 --- /dev/null +++ b/configs/dev/middleware.toml @@ -0,0 +1,76 @@ +[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 = true +SkippedPathPrefixes = ["/api/v1/captcha/", "/api/v1/login", "/api/v1/web/articles", "/api/v1/web/jobs"] +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 = "" # If empty, then use the same configuration as Storage.Cache.Redis +Username = "" +Password = "" +DB = 2 + +[Middleware.RateLimiter] +Enable = false +Period = 10 # seconds +MaxRequestsPerIP = 1000 +MaxRequestsPerUser = 500 + +[Middleware.RateLimiter.Store] +Type = "memory" # memory/redis + +[Middleware.RateLimiter.Store.Memory] +Expiration = 3600 +CleanupInterval = 60 + +[Middleware.RateLimiter.Store.Redis] +Addr = "" # If empty, then use the same configuration as Storage.Cache.Redis +Username = "" +Password = "" +DB = 10 + +[Middleware.Casbin] +Disable = true +SkippedPathPrefixes = ["/api/v1/captcha/", "/api/v1/login", "/api/v1/current/", "/api/v1/web/articles", "/api/v1/web/jobs"] +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..bdbe338 --- /dev/null +++ b/configs/dev/server.toml @@ -0,0 +1,89 @@ +[General] +AppName = "hailinservice" +Version = "v10.1.0" +Debug = true +PprofAddr = "" # Pprof monitor address, "localhost:6060" +DisableSwagger = false +DisablePrintConfig = false +DefaultLoginPwd = "6351623c8cef86fefabfa7da046fc619" # MD5("abc-123") +MenuFile = "menu.json" # Or use "menu_cn.json" +DenyOperateMenu = false + +[General.HTTP] +Addr = ":8060" +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 = "memory" # memory/badger/redis +Delimiter = ":" + +[Storage.Cache.Memory] +CleanupInterval = 60 + +[Storage.Cache.Badger] +Path = "data/cache" + +[Storage.Cache.Redis] +Addr = "127.0.0.1:6379" +Username = "" +Password = "" +DB = 1 + +[Storage.DB] +Debug = true +Type = "mysql" # sqlite3/mysql/postgres +# SQLite3 DSN +#DSN = "data/hailinservice.db" +# MySQL DSN + DSN = "hailin:z2askDphamsxk5BH@tcp(115.239.217.220:3306)/hailin?charset=utf8mb4&parseTime=True&loc=Local" +# PostgreSQL DSN +# DSN = "host=db user=postgres password=123456 dbname=hailinservice 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 = "" # If empty, then use the same configuration as Storage.Cache.Redis +Username = "" +Password = "" +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..748840c --- /dev/null +++ b/go.mod @@ -0,0 +1,114 @@ +module github.com/guxuan/hailin_service + +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/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/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 +) + +require ( + github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible // indirect + github.com/KyleBanks/depth v1.2.1 // 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/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/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/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-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/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/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/xrash/smetrics v0.0.0-20201216005158-039620a65673 // 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 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..a41b53e --- /dev/null +++ b/go.sum @@ -0,0 +1,528 @@ +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/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 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/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/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/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.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/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/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/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.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.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-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +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.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/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-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 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/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= +github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +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 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/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/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/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.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.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-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-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-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-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-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.4.4 h1:gIufGoR0dQzjkyqDyYSCvsYR6fba1Gw5YKDqKeChxFc= +gorm.io/driver/sqlite v1.4.4/go.mod h1:0Aq3iPO+v9ZKbcdiz8gLWRw5VOPcBOPUQJFLq5e2ecI= +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= +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= +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..b681733 --- /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.com/guxuan/hailin_service/internal/config" + _ "github.com/guxuan/hailin_service/internal/swagger" + "github.com/guxuan/hailin_service/internal/utility/prom" + "github.com/guxuan/hailin_service/internal/wirex" + "github.com/guxuan/hailin_service/pkg/logging" + "github.com/guxuan/hailin_service/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..7716188 --- /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/guxuan/hailin_service/internal/config" + "github.com/guxuan/hailin_service/internal/utility/prom" + "github.com/guxuan/hailin_service/internal/wirex" + "github.com/guxuan/hailin_service/pkg/errors" + "github.com/guxuan/hailin_service/pkg/logging" + "github.com/guxuan/hailin_service/pkg/middleware" + "github.com/guxuan/hailin_service/pkg/util" + "github.com/casbin/casbin/v2" + "github.com/gin-gonic/gin" + swaggerFiles "github.com/swaggo/files" + ginSwagger "github.com/swaggo/gin-swagger" + "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..de32118 --- /dev/null +++ b/internal/bootstrap/logger.go @@ -0,0 +1,43 @@ +package bootstrap + +import ( + "context" + + "github.com/guxuan/hailin_service/internal/config" + "github.com/guxuan/hailin_service/pkg/gormx" + "github.com/guxuan/hailin_service/pkg/logging" + "github.com/spf13/cast" +) + +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..f6e6387 --- /dev/null +++ b/internal/config/config.go @@ -0,0 +1,166 @@ +package config + +import ( + "fmt" + + "github.com/guxuan/hailin_service/pkg/encoding/json" + "github.com/guxuan/hailin_service/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:"hailinservice"` + 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/hailinservice.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..4db686f --- /dev/null +++ b/internal/config/consts.go @@ -0,0 +1,16 @@ +package config + +const ( + CacheNSForUser = "user" + 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..770a62d --- /dev/null +++ b/internal/config/parse.go @@ -0,0 +1,84 @@ +package config + +import ( + "os" + "path/filepath" + "strings" + "sync" + + "github.com/guxuan/hailin_service/pkg/encoding/json" + "github.com/guxuan/hailin_service/pkg/encoding/toml" + "github.com/guxuan/hailin_service/pkg/errors" + "github.com/creasty/defaults" +) + +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/mods.go b/internal/mods/mods.go new file mode 100644 index 0000000..013d842 --- /dev/null +++ b/internal/mods/mods.go @@ -0,0 +1,59 @@ +package mods + +import ( + "context" + + "github.com/guxuan/hailin_service/internal/config" + + "github.com/gin-gonic/gin" + "github.com/google/wire" + "github.com/guxuan/hailin_service/internal/mods/rbac" +) + +const ( + apiPrefix = "/api/" +) + +// Collection of wire providers +var Set = wire.NewSet( + wire.Struct(new(Mods), "*"), + rbac.Set, +) + +type Mods struct { + RBAC *rbac.RBAC +} + +func (a *Mods) Init(ctx context.Context) error { + if err := a.RBAC.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) + e.Static(config.C.FileConfig.StaticPrefix, config.C.FileConfig.UploadDir) + v1 := gAPI.Group("v1") + + if err := a.RBAC.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 + } + + return nil +} diff --git a/internal/mods/rbac/api/article.api.go b/internal/mods/rbac/api/article.api.go new file mode 100644 index 0000000..107312c --- /dev/null +++ b/internal/mods/rbac/api/article.api.go @@ -0,0 +1,127 @@ +package api + +import ( + "github.com/gin-gonic/gin" + "github.com/guxuan/hailin_service/internal/mods/rbac/biz" + "github.com/guxuan/hailin_service/internal/mods/rbac/schema" + "github.com/guxuan/hailin_service/pkg/util" +) + +// Banner management for RBAC +type Article struct { + ArticleBIZ *biz.Article +} + +// @Tags ArticleAPI +// @Security ApiKeyAuth +// @Summary Query Article list +// @Param current query int true "pagination index" default(1) +// @Param pageSize query int true "pagination size" default(10) +// @Param name query string false "name of Article" +// @Param status query string false "Status of banner (disabled,enabled)" +// @Success 200 {object} util.ResponseResult{data=[]schema.Article} +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/articles [get] +func (a *Article) Query(c *gin.Context) { + ctx := c.Request.Context() + var params schema.ArticleQueryParam + if err := util.ParseQuery(c, ¶ms); err != nil { + util.ResError(c, err) + return + } + + result, err := a.ArticleBIZ.Query(ctx, params) + if err != nil { + util.ResError(c, err) + return + } + util.ResPage(c, result.Data, result.PageResult) +} + +// @Tags ArticleAPI +// @Security ApiKeyAuth +// @Summary Get Article record by ID +// @Param id path string true "unique id" +// @Success 200 {object} util.ResponseResult{data=schema.Article} +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/articles/{id} [get] +func (a *Article) Get(c *gin.Context) { + ctx := c.Request.Context() + item, err := a.ArticleBIZ.Get(ctx, c.Param("id")) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, item) +} + +// @Tags ArticleAPI +// @Security ApiKeyAuth +// @Summary Create Article record +// @Param body body schema.ArticleForm true "Request body" +// @Success 200 {object} util.ResponseResult{data=schema.Article} +// @Failure 400 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/articles [post] +func (a *Article) Create(c *gin.Context) { + ctx := c.Request.Context() + item := new(schema.ArticleForm) + if err := util.ParseJSON(c, item); err != nil { + util.ResError(c, err) + return + } + + result, err := a.ArticleBIZ.Create(ctx, item) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, result) +} + +// @Tags ArticleAPI +// @Security ApiKeyAuth +// @Summary Update Article record by ID +// @Param id path string true "unique id" +// @Param body body schema.ArticleForm 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/articles/{id} [put] +func (a *Article) Update(c *gin.Context) { + ctx := c.Request.Context() + item := new(schema.ArticleForm) + if err := util.ParseJSON(c, item); err != nil { + util.ResError(c, err) + return + } + + err := a.ArticleBIZ.Update(ctx, c.Param("id"), item) + if err != nil { + util.ResError(c, err) + return + } + util.ResOK(c) +} + +// @Tags ArticleAPI +// @Security ApiKeyAuth +// @Summary Delete Article 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/articles/{id} [delete] +func (a *Article) Delete(c *gin.Context) { + ctx := c.Request.Context() + err := a.ArticleBIZ.Delete(ctx, c.Param("id")) + if err != nil { + util.ResError(c, err) + return + } + util.ResOK(c) +} diff --git a/internal/mods/rbac/api/banner.api.go b/internal/mods/rbac/api/banner.api.go new file mode 100644 index 0000000..2ea2b82 --- /dev/null +++ b/internal/mods/rbac/api/banner.api.go @@ -0,0 +1,127 @@ +package api + +import ( + "github.com/gin-gonic/gin" + "github.com/guxuan/hailin_service/internal/mods/rbac/biz" + "github.com/guxuan/hailin_service/internal/mods/rbac/schema" + "github.com/guxuan/hailin_service/pkg/util" +) + +// Banner management for RBAC +type Banner struct { + BannerBIZ *biz.Banner +} + +// @Tags BannerAPI +// @Security ApiKeyAuth +// @Summary Query banner list +// @Param current query int true "pagination index" default(1) +// @Param pageSize query int true "pagination size" default(10) +// @Param name query string false "Name of banner" +// @Param status query string false "Status of banner (disabled,enabled)" +// @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 BannerAPI +// @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() + item, err := a.BannerBIZ.Get(ctx, c.Param("id")) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, item) +} + +// @Tags BannerAPI +// @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 + } + + result, err := a.BannerBIZ.Create(ctx, item) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, result) +} + +// @Tags BannerAPI +// @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 + } + + err := a.BannerBIZ.Update(ctx, c.Param("id"), item) + if err != nil { + util.ResError(c, err) + return + } + util.ResOK(c) +} + +// @Tags BannerAPI +// @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() + err := a.BannerBIZ.Delete(ctx, c.Param("id")) + if err != nil { + util.ResError(c, err) + return + } + util.ResOK(c) +} diff --git a/internal/mods/rbac/api/job.api.go b/internal/mods/rbac/api/job.api.go new file mode 100644 index 0000000..2195abb --- /dev/null +++ b/internal/mods/rbac/api/job.api.go @@ -0,0 +1,244 @@ +package api + +import ( + "github.com/gin-gonic/gin" + "github.com/guxuan/hailin_service/internal/mods/rbac/biz" + "github.com/guxuan/hailin_service/internal/mods/rbac/resp" + "github.com/guxuan/hailin_service/internal/mods/rbac/schema" + "github.com/guxuan/hailin_service/pkg/util" +) + +type Job struct { + JobBIZ *biz.Job +} + +// @Tags 职位模块 +// @Security ApiKeyAuth +// @Summary Query Job list +// @Param current query int true "pagination index" default(1) +// @Param pageSize query int true "pagination size" default(10) +// @Param title query string false "title of Job" +// @Param jobAreaId query string false "jobAreaId of Job" +// @Param status query string false "Status of banner (disabled,enabled)" +// @Success 200 {object} util.ResponseResult{data=[]schema.Job} +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/jobs [get] +func (a *Job) Query(c *gin.Context) { + ctx := c.Request.Context() + var params schema.JobQueryParam + if err := util.ParseQuery(c, ¶ms); err != nil { + util.ResError(c, err) + return + } + + result, err := a.JobBIZ.Query(ctx, params) + if err != nil { + util.ResError(c, err) + return + } + list := result.Data + var newList []resp.JobAdminListResp + areas, err := a.JobBIZ.QueryJobArea(ctx) + if err != nil { + util.ResError(c, err) + return + } + for _, item := range list { + var newitem resp.JobAdminListResp + newitem.ID = item.ID + newitem.Status = item.Status + newitem.JobAreaID = item.JobAreaID + newitem.Title = item.Title + newitem.Introduce = item.Introduce + newitem.Duty = item.Duty + newitem.Salary = item.Salary + newitem.Sequence = item.Sequence + newitem.CreatedAt = item.CreatedAt + newitem.UpdatedAt = item.UpdatedAt + for _, area := range *areas { + if item.JobAreaID == area.ID { + newitem.AreaName = area.Name + } + } + newList = append(newList, newitem) + } + util.ResPage(c, newList, result.PageResult) +} + +// @Tags 职位模块 +// @Security ApiKeyAuth +// @Summary Get Job record by ID +// @Param id path string true "unique id" +// @Success 200 {object} util.ResponseResult{data=schema.Job} +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/jobs/{id} [get] +func (a *Job) Get(c *gin.Context) { + ctx := c.Request.Context() + item, err := a.JobBIZ.Get(ctx, c.Param("id")) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, item) +} + +// @Tags 职位模块 +// @Security ApiKeyAuth +// @Summary Create Job record +// @Param body body schema.JobForm true "Request body" +// @Success 200 {object} util.ResponseResult{data=schema.Job} +// @Failure 400 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/jobs [post] +func (a *Job) Create(c *gin.Context) { + ctx := c.Request.Context() + item := new(schema.JobForm) + if err := util.ParseJSON(c, item); err != nil { + util.ResError(c, err) + return + } + + result, err := a.JobBIZ.Create(ctx, item) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, result) +} + +// @Tags 职位模块 +// @Security ApiKeyAuth +// @Summary Update Job record by ID +// @Param id path string true "unique id" +// @Param body body schema.JobForm 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/jobs/{id} [put] +func (a *Job) Update(c *gin.Context) { + ctx := c.Request.Context() + item := new(schema.JobForm) + if err := util.ParseJSON(c, item); err != nil { + util.ResError(c, err) + return + } + + err := a.JobBIZ.Update(ctx, c.Param("id"), item) + if err != nil { + util.ResError(c, err) + return + } + util.ResOK(c) +} + +// @Tags 职位模块 +// @Security ApiKeyAuth +// @Summary Delete Job 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/jobs/{id} [delete] +func (a *Job) Delete(c *gin.Context) { + ctx := c.Request.Context() + err := a.JobBIZ.Delete(ctx, c.Param("id")) + if err != nil { + util.ResError(c, err) + return + } + util.ResOK(c) +} + +// @Tags 职位模块 +// @Security ApiKeyAuth +// @Summary Query JobArea list +// @Param current query int true "pagination index" default(1) +// @Param pageSize query int true "pagination size" default(10) +// @Param status query string false "Status of banner (disabled,enabled)" +// @Success 200 {object} util.ResponseResult{data=[]schema.JobArea} +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/jobs/job_areas [get] +func (a *Job) QueryJobArea(c *gin.Context) { + ctx := c.Request.Context() + + result, err := a.JobBIZ.QueryJobArea(ctx) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, result) +} + +// @Tags 职位模块 +// @Security ApiKeyAuth +// @Summary Create JobArea record +// @Param body body schema.JobAreaForm true "Request body" +// @Success 200 {object} util.ResponseResult{data=schema.JobAreaForm} +// @Failure 400 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/jobs/job_areas [post] +func (a *Job) CreateJobArea(c *gin.Context) { + ctx := c.Request.Context() + item := new(schema.JobAreaForm) + if err := util.ParseJSON(c, item); err != nil { + util.ResError(c, err) + return + } + + result, err := a.JobBIZ.CreateJobArea(ctx, item) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, result) +} + +// @Tags 职位模块 +// @Security ApiKeyAuth +// @Summary Update JobArea record by ID +// @Param id path string true "unique id" +// @Param body body schema.JobAreaForm 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/jobs/job_areas/{id} [put] +func (a *Job) UpdateJobArea(c *gin.Context) { + ctx := c.Request.Context() + item := new(schema.JobAreaForm) + if err := util.ParseJSON(c, item); err != nil { + util.ResError(c, err) + return + } + + err := a.JobBIZ.UpdateJobArea(ctx, c.Param("id"), item) + if err != nil { + util.ResError(c, err) + return + } + util.ResOK(c) +} + +// @Tags 职位模块 +// @Security ApiKeyAuth +// @Summary Delete JobArea 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/jobs/job_areas/{id} [delete] +func (a *Job) DeleteJobArea(c *gin.Context) { + ctx := c.Request.Context() + err := a.JobBIZ.DeleteJobArea(ctx, c.Param("id")) + if err != nil { + util.ResError(c, err) + return + } + util.ResOK(c) +} diff --git a/internal/mods/rbac/api/logger.api.go b/internal/mods/rbac/api/logger.api.go new file mode 100644 index 0000000..6c73189 --- /dev/null +++ b/internal/mods/rbac/api/logger.api.go @@ -0,0 +1,45 @@ +package api + +import ( + "github.com/guxuan/hailin_service/internal/mods/rbac/biz" + "github.com/guxuan/hailin_service/internal/mods/rbac/schema" + "github.com/guxuan/hailin_service/pkg/util" + "github.com/gin-gonic/gin" +) + +// 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..0efef45 --- /dev/null +++ b/internal/mods/rbac/api/login.api.go @@ -0,0 +1,182 @@ +package api + +import ( + "github.com/guxuan/hailin_service/internal/mods/rbac/biz" + "github.com/guxuan/hailin_service/internal/mods/rbac/schema" + "github.com/guxuan/hailin_service/pkg/util" + "github.com/gin-gonic/gin" +) + +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/memorabilia.api.go b/internal/mods/rbac/api/memorabilia.api.go new file mode 100644 index 0000000..787c35c --- /dev/null +++ b/internal/mods/rbac/api/memorabilia.api.go @@ -0,0 +1,128 @@ +package api + +import ( + "github.com/gin-gonic/gin" + "github.com/guxuan/hailin_service/internal/mods/rbac/biz" + "github.com/guxuan/hailin_service/internal/mods/rbac/schema" + "github.com/guxuan/hailin_service/pkg/util" +) + +type Memorabilia struct { + MemorabiliaBIZ *biz.Memorabilia +} + +// @Tags 发展历程模块 +// @Security ApiKeyAuth +// @Summary Query Memorabilia list +// @Param current query int true "pagination index" default(1) +// @Param pageSize query int true "pagination size" default(10) +// @Param title query string false "title of Memorabilia" +// @Param month query string false "month of Memorabilia" +// @Param year query string false "year of Memorabilia" +// @Param status query string false "Status of banner (disabled,enabled)" +// @Success 200 {object} util.ResponseResult{data=[]schema.Memorabilia} +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/memorabilias [get] +func (a *Memorabilia) Query(c *gin.Context) { + ctx := c.Request.Context() + var params schema.MemorabiliaQueryParam + if err := util.ParseQuery(c, ¶ms); err != nil { + util.ResError(c, err) + return + } + + result, err := a.MemorabiliaBIZ.Query(ctx, params) + if err != nil { + util.ResError(c, err) + return + } + util.ResPage(c, result.Data, result.PageResult) +} + +// @Tags 发展历程模块 +// @Security ApiKeyAuth +// @Summary Get Memorabilia record by ID +// @Param id path string true "unique id" +// @Success 200 {object} util.ResponseResult{data=schema.Memorabilia} +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/memorabilias/{id} [get] +func (a *Memorabilia) Get(c *gin.Context) { + ctx := c.Request.Context() + item, err := a.MemorabiliaBIZ.Get(ctx, c.Param("id")) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, item) +} + +// @Tags 发展历程模块 +// @Security ApiKeyAuth +// @Summary Create Article record +// @Param body body schema.MemorabiliaForm true "Request body" +// @Success 200 {object} util.ResponseResult{data=schema.Memorabilia} +// @Failure 400 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/memorabilias [post] +func (a *Memorabilia) Create(c *gin.Context) { + ctx := c.Request.Context() + item := new(schema.MemorabiliaForm) + if err := util.ParseJSON(c, item); err != nil { + util.ResError(c, err) + return + } + + result, err := a.MemorabiliaBIZ.Create(ctx, item) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, result) +} + +// @Tags 发展历程模块 +// @Security ApiKeyAuth +// @Summary Update Article record by ID +// @Param id path string true "unique id" +// @Param body body schema.MemorabiliaForm 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/memorabilias/{id} [put] +func (a *Memorabilia) Update(c *gin.Context) { + ctx := c.Request.Context() + item := new(schema.MemorabiliaForm) + if err := util.ParseJSON(c, item); err != nil { + util.ResError(c, err) + return + } + + err := a.MemorabiliaBIZ.Update(ctx, c.Param("id"), item) + if err != nil { + util.ResError(c, err) + return + } + util.ResOK(c) +} + +// @Tags 发展历程模块 +// @Security ApiKeyAuth +// @Summary Delete Article 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/memorabilias/{id} [delete] +func (a *Memorabilia) Delete(c *gin.Context) { + ctx := c.Request.Context() + err := a.MemorabiliaBIZ.Delete(ctx, c.Param("id")) + 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..101f68e --- /dev/null +++ b/internal/mods/rbac/api/menu.api.go @@ -0,0 +1,132 @@ +package api + +import ( + "github.com/guxuan/hailin_service/internal/mods/rbac/biz" + "github.com/guxuan/hailin_service/internal/mods/rbac/schema" + "github.com/guxuan/hailin_service/pkg/util" + "github.com/gin-gonic/gin" +) + +// 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/product.api.go b/internal/mods/rbac/api/product.api.go new file mode 100644 index 0000000..ca44328 --- /dev/null +++ b/internal/mods/rbac/api/product.api.go @@ -0,0 +1,178 @@ +package api + +import ( + "github.com/gin-gonic/gin" + "github.com/guxuan/hailin_service/internal/mods/rbac/biz" + "github.com/guxuan/hailin_service/internal/mods/rbac/schema" + "github.com/guxuan/hailin_service/pkg/util" +) + +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) +// @Param title query string false "title of Product" +// @Param categoryId query int false "categoryId of Product" +// @Param code query string false "code of Product" +// @Param status query string false "Status of Product (disabled,enabled)" +// @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) +} + +type Option struct { + Value interface{} `json:"value"` + Label string `json:"label"` + Children []Option `json:"children"` +} + +func ToOptions(categories *[]schema.ProductCategory) []Option { + if categories == nil { + return []Option{} + } + optMap := make(map[uint]*Option, len(*categories)) + for _, cat := range *categories { + optMap[cat.ID] = &Option{ + Value: cat.ID, + Label: cat.Label, + Children: []Option{}, + } + } + var roots []Option + for _, cat := range *categories { + node := optMap[cat.ID] + if cat.ParentID == 0 { + roots = append(roots, *node) + } else if parent, ok := optMap[cat.ParentID]; ok { + parent.Children = append(parent.Children, *node) + } + } + + return roots +} + +// @Tags 产品模块 +// @Security ApiKeyAuth +// @Summary Query ProductCategory list +// @Success 200 {object} util.ResponseResult{data=[]schema.ProductCategory} +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/products/categorys [get] +func (a *Product) QueryCategory(c *gin.Context) { + ctx := c.Request.Context() + + result, err := a.ProductBIZ.QueryProductCategory(ctx) + if err != nil { + util.ResError(c, err) + return + } + list := ToOptions(result) + util.ResSuccess(c, list) +} + +// @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() + item, err := a.ProductBIZ.Get(ctx, c.Param("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 + } + + 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 + } + + err := a.ProductBIZ.Update(ctx, c.Param("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() + err := a.ProductBIZ.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..37ae999 --- /dev/null +++ b/internal/mods/rbac/api/role.api.go @@ -0,0 +1,133 @@ +package api + +import ( + "github.com/guxuan/hailin_service/internal/mods/rbac/biz" + "github.com/guxuan/hailin_service/internal/mods/rbac/schema" + "github.com/guxuan/hailin_service/pkg/util" + "github.com/gin-gonic/gin" +) + +// 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/team.api.go b/internal/mods/rbac/api/team.api.go new file mode 100644 index 0000000..8b9a210 --- /dev/null +++ b/internal/mods/rbac/api/team.api.go @@ -0,0 +1,126 @@ +package api + +import ( + "github.com/gin-gonic/gin" + "github.com/guxuan/hailin_service/internal/mods/rbac/biz" + "github.com/guxuan/hailin_service/internal/mods/rbac/schema" + "github.com/guxuan/hailin_service/pkg/util" +) + +type Team struct { + TeamBIZ *biz.Team +} + +// @Tags 团队模块 +// @Security ApiKeyAuth +// @Summary Query Team list +// @Param current query int true "pagination index" default(1) +// @Param pageSize query int true "pagination size" default(10) +// @Param name query string false "name of Team" +// @Param status query string false "Status of banner (disabled,enabled)" +// @Success 200 {object} util.ResponseResult{data=[]schema.Team} +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/teams [get] +func (a *Team) Query(c *gin.Context) { + ctx := c.Request.Context() + var params schema.TeamQueryParam + if err := util.ParseQuery(c, ¶ms); err != nil { + util.ResError(c, err) + return + } + + result, err := a.TeamBIZ.Query(ctx, params) + if err != nil { + util.ResError(c, err) + return + } + util.ResPage(c, result.Data, result.PageResult) +} + +// @Tags 团队模块 +// @Security ApiKeyAuth +// @Summary Get Team record by ID +// @Param id path string true "unique id" +// @Success 200 {object} util.ResponseResult{data=schema.Team} +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/teams/{id} [get] +func (a *Team) Get(c *gin.Context) { + ctx := c.Request.Context() + item, err := a.TeamBIZ.Get(ctx, c.Param("id")) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, item) +} + +// @Tags 团队模块 +// @Security ApiKeyAuth +// @Summary Create Article record +// @Param body body schema.TeamForm true "Request body" +// @Success 200 {object} util.ResponseResult{data=schema.Team} +// @Failure 400 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/teams [post] +func (a *Team) Create(c *gin.Context) { + ctx := c.Request.Context() + item := new(schema.TeamForm) + if err := util.ParseJSON(c, item); err != nil { + util.ResError(c, err) + return + } + + result, err := a.TeamBIZ.Create(ctx, item) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, result) +} + +// @Tags 团队模块 +// @Security ApiKeyAuth +// @Summary Update Article record by ID +// @Param id path string true "unique id" +// @Param body body schema.TeamForm 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/teams/{id} [put] +func (a *Team) Update(c *gin.Context) { + ctx := c.Request.Context() + item := new(schema.TeamForm) + if err := util.ParseJSON(c, item); err != nil { + util.ResError(c, err) + return + } + + err := a.TeamBIZ.Update(ctx, c.Param("id"), item) + if err != nil { + util.ResError(c, err) + return + } + util.ResOK(c) +} + +// @Tags 团队模块 +// @Security ApiKeyAuth +// @Summary Delete Article 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/teams/{id} [delete] +func (a *Team) Delete(c *gin.Context) { + ctx := c.Request.Context() + err := a.TeamBIZ.Delete(ctx, c.Param("id")) + if err != nil { + util.ResError(c, err) + return + } + util.ResOK(c) +} diff --git a/internal/mods/rbac/api/upload.api.go b/internal/mods/rbac/api/upload.api.go new file mode 100644 index 0000000..16d1254 --- /dev/null +++ b/internal/mods/rbac/api/upload.api.go @@ -0,0 +1,24 @@ +package api + +import ( + "fmt" + "github.com/gin-gonic/gin" + "github.com/guxuan/hailin_service/internal/config" + "github.com/guxuan/hailin_service/internal/mods/rbac/biz" + "github.com/guxuan/hailin_service/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/rbac/api/user.api.go b/internal/mods/rbac/api/user.api.go new file mode 100644 index 0000000..c07c1f5 --- /dev/null +++ b/internal/mods/rbac/api/user.api.go @@ -0,0 +1,152 @@ +package api + +import ( + "github.com/guxuan/hailin_service/internal/mods/rbac/biz" + "github.com/guxuan/hailin_service/internal/mods/rbac/schema" + "github.com/guxuan/hailin_service/pkg/util" + "github.com/gin-gonic/gin" +) + +// 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/api/video.api.go b/internal/mods/rbac/api/video.api.go new file mode 100644 index 0000000..4b11e32 --- /dev/null +++ b/internal/mods/rbac/api/video.api.go @@ -0,0 +1,127 @@ +package api + +import ( + "github.com/gin-gonic/gin" + "github.com/guxuan/hailin_service/internal/mods/rbac/biz" + "github.com/guxuan/hailin_service/internal/mods/rbac/schema" + "github.com/guxuan/hailin_service/pkg/util" +) + +// Banner management for RBAC +type Video struct { + VideoBIZ *biz.Video +} + +// @Tags VideoAPI +// @Security ApiKeyAuth +// @Summary Query Video list +// @Param current query int true "pagination index" default(1) +// @Param pageSize query int true "pagination size" default(10) +// @Param title query string false "title of Video" +// @Param status query string false "Status of banner (disabled,enabled)" +// @Success 200 {object} util.ResponseResult{data=[]schema.Video} +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/videos [get] +func (a *Video) Query(c *gin.Context) { + ctx := c.Request.Context() + var params schema.VideoQueryParam + if err := util.ParseQuery(c, ¶ms); err != nil { + util.ResError(c, err) + return + } + + result, err := a.VideoBIZ.Query(ctx, params) + if err != nil { + util.ResError(c, err) + return + } + util.ResPage(c, result.Data, result.PageResult) +} + +// @Tags VideoAPI +// @Security ApiKeyAuth +// @Summary Get Video record by ID +// @Param id path string true "unique id" +// @Success 200 {object} util.ResponseResult{data=schema.Video} +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/videos/{id} [get] +func (a *Video) Get(c *gin.Context) { + ctx := c.Request.Context() + item, err := a.VideoBIZ.Get(ctx, c.Param("id")) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, item) +} + +// @Tags VideoAPI +// @Security ApiKeyAuth +// @Summary Create banner record +// @Param body body schema.VideoForm true "Request body" +// @Success 200 {object} util.ResponseResult{data=schema.Video} +// @Failure 400 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/videos [post] +func (a *Video) Create(c *gin.Context) { + ctx := c.Request.Context() + item := new(schema.VideoForm) + if err := util.ParseJSON(c, item); err != nil { + util.ResError(c, err) + return + } + + result, err := a.VideoBIZ.Create(ctx, item) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, result) +} + +// @Tags VideoAPI +// @Security ApiKeyAuth +// @Summary Update Video 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/videos/{id} [put] +func (a *Video) Update(c *gin.Context) { + ctx := c.Request.Context() + item := new(schema.VideoForm) + if err := util.ParseJSON(c, item); err != nil { + util.ResError(c, err) + return + } + + err := a.VideoBIZ.Update(ctx, c.Param("id"), item) + if err != nil { + util.ResError(c, err) + return + } + util.ResOK(c) +} + +// @Tags VideoAPI +// @Security ApiKeyAuth +// @Summary Delete Video 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/videos/{id} [delete] +func (a *Video) Delete(c *gin.Context) { + ctx := c.Request.Context() + err := a.VideoBIZ.Delete(ctx, c.Param("id")) + if err != nil { + util.ResError(c, err) + return + } + util.ResOK(c) +} diff --git a/internal/mods/rbac/api/web.go b/internal/mods/rbac/api/web.go new file mode 100644 index 0000000..07790a8 --- /dev/null +++ b/internal/mods/rbac/api/web.go @@ -0,0 +1,56 @@ +package api + +import ( + "github.com/gin-gonic/gin" + "github.com/guxuan/hailin_service/internal/mods/rbac/biz" + "github.com/guxuan/hailin_service/internal/mods/rbac/schema" + "github.com/guxuan/hailin_service/pkg/util" +) + +// Banner management for RBAC +type Web struct { + ArticleBIZ *biz.Article + JobBIZ *biz.Job +} + +// @Tags 网页模块 +// @Security ApiKeyAuth +// @Summary 获取职位列表 +// @Success 200 {object} util.ResponseResult{data=[]schema.WebJobData} +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/web/jobs [get] +func (a *Web) QueryJob(c *gin.Context) { + ctx := c.Request.Context() + list, err := a.JobBIZ.QueryWebJobList(ctx) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, list) +} + +// @Tags 网页模块 +// @Security ApiKeyAuth +// @Summary 获取公司动态 +// @Param current query int true "pagination index" default(1) +// @Param pageSize query int true "pagination size" default(10) +// @Param jobAreaId query string false "jobAreaId of Job" +// @Success 200 {object} util.ResponseResult{data=[]schema.Article} +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/web/articles [get] +func (a *Web) QueryArticle(c *gin.Context) { + ctx := c.Request.Context() + var params schema.ArticleQueryParam + if err := util.ParseQuery(c, ¶ms); err != nil { + util.ResError(c, err) + return + } + result, err := a.ArticleBIZ.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/webSite.api.go b/internal/mods/rbac/api/webSite.api.go new file mode 100644 index 0000000..28acfef --- /dev/null +++ b/internal/mods/rbac/api/webSite.api.go @@ -0,0 +1,82 @@ +package api + +import ( + "github.com/gin-gonic/gin" + "github.com/guxuan/hailin_service/internal/mods/rbac/biz" + "github.com/guxuan/hailin_service/internal/mods/rbac/schema" + "github.com/guxuan/hailin_service/pkg/util" +) + +// Banner management for RBAC +type WebSite struct { + WebSiteBIZ *biz.WebSite +} + +// @Tags 网页设置 +// @Security ApiKeyAuth +// @Summary Query WebSite info +// @Success 200 {object} util.ResponseResult{data=[]schema.WebSite} +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/web_site [get] +func (a *WebSite) Query(c *gin.Context) { + ctx := c.Request.Context() + + result, err := a.WebSiteBIZ.Query(ctx) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, result) +} + +// @Tags ArticleAPI +// @Security ApiKeyAuth +// @Summary Create WebSite record +// @Param body body schema.WebSiteForm true "Request body" +// @Success 200 {object} util.ResponseResult{data=schema.WebSite} +// @Failure 400 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/web_site [post] +func (a *WebSite) Create(c *gin.Context) { + ctx := c.Request.Context() + item := new(schema.WebSiteForm) + if err := util.ParseJSON(c, item); err != nil { + util.ResError(c, err) + return + } + + result, err := a.WebSiteBIZ.Create(ctx, item) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, result) +} + +// @Tags 网页设置 +// @Security ApiKeyAuth +// @Summary Update WebSite record by ID +// @Param id path string true "unique id" +// @Param body body schema.WebSiteForm 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/web_site/{id} [put] +func (a *WebSite) Update(c *gin.Context) { + ctx := c.Request.Context() + item := new(schema.WebSiteForm) + if err := util.ParseJSON(c, item); err != nil { + util.ResError(c, err) + return + } + + err := a.WebSiteBIZ.Update(ctx, c.Param("id"), item) + if err != nil { + util.ResError(c, err) + return + } + util.ResOK(c) +} diff --git a/internal/mods/rbac/biz/article.biz.go b/internal/mods/rbac/biz/article.biz.go new file mode 100644 index 0000000..ef4df8c --- /dev/null +++ b/internal/mods/rbac/biz/article.biz.go @@ -0,0 +1,100 @@ +package biz + +import ( + "context" + "time" + + "github.com/guxuan/hailin_service/internal/mods/rbac/dal" + "github.com/guxuan/hailin_service/internal/mods/rbac/schema" + "github.com/guxuan/hailin_service/pkg/cachex" + "github.com/guxuan/hailin_service/pkg/errors" + "github.com/guxuan/hailin_service/pkg/util" +) + +// Role management for RBAC +type Article struct { + Cache cachex.Cacher + Trans *util.Trans + ArticleDAL *dal.Article +} + +// Query roles from the data access object based on the provided parameters and options. +func (a *Article) Query(ctx context.Context, params schema.ArticleQueryParam) (*schema.ArticleQueryResult, error) { + params.Pagination = true + result, err := a.ArticleDAL.Query(ctx, params, schema.ArticleQueryOptions{ + QueryOptions: util.QueryOptions{ + OrderFields: []util.OrderByParam{ + {Field: "created_at", Direction: util.DESC}, + {Field: "sequence", Direction: util.ASC}, + }, + }, + }) + if err != nil { + return nil, err + } + return result, nil +} + +// Get the specified role from the data access object. +func (a *Article) Get(ctx context.Context, id string) (*schema.Article, error) { + article, err := a.ArticleDAL.Get(ctx, id) + if err != nil { + return nil, err + } else if article == nil { + return nil, errors.NotFound("", "Banner not found") + } + + return article, nil +} + +// Create a new role in the data access object. +func (a *Article) Create(ctx context.Context, formItem *schema.ArticleForm) (*schema.Article, error) { + + article := &schema.Article{ + ID: util.NewXID(), + CreatedAt: time.Now(), + } + if err := formItem.FillTo(article); err != nil { + return nil, err + } + + if err := a.ArticleDAL.Create(ctx, article); err != nil { + return nil, err + } + + return article, nil +} + +// Update the specified role in the data access object. +func (a *Article) Update(ctx context.Context, id string, formItem *schema.ArticleForm) error { + article, err := a.ArticleDAL.Get(ctx, id) + if err != nil { + return err + } + + if err := formItem.FillTo(article); err != nil { + return err + } + article.UpdatedAt = time.Now() + + if err := a.ArticleDAL.Update(ctx, article); err != nil { + return err + } + return nil +} + +// Delete the specified role from the data access object. +func (a *Article) Delete(ctx context.Context, id string) error { + exists, err := a.ArticleDAL.Exists(ctx, id) + if err != nil { + return err + } else if !exists { + return errors.NotFound("", "Role not found") + } + + if err := a.ArticleDAL.Delete(ctx, id); err != nil { + return err + } + return nil + +} diff --git a/internal/mods/rbac/biz/banner.biz.go b/internal/mods/rbac/biz/banner.biz.go new file mode 100644 index 0000000..6b7fb98 --- /dev/null +++ b/internal/mods/rbac/biz/banner.biz.go @@ -0,0 +1,99 @@ +package biz + +import ( + "context" + "time" + + "github.com/guxuan/hailin_service/internal/mods/rbac/dal" + "github.com/guxuan/hailin_service/internal/mods/rbac/schema" + "github.com/guxuan/hailin_service/pkg/cachex" + "github.com/guxuan/hailin_service/pkg/errors" + "github.com/guxuan/hailin_service/pkg/util" +) + +// Role management for RBAC +type Banner struct { + Cache cachex.Cacher + Trans *util.Trans + BannerDAL *dal.Banner +} + +// Query roles 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: "sequence", Direction: util.DESC}, + }, + }, + }) + if err != nil { + return nil, err + } + return result, nil +} + +// Get the specified role from the data access object. +func (a *Banner) Get(ctx context.Context, id string) (*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 role in the data access object. +func (a *Banner) Create(ctx context.Context, formItem *schema.BannerForm) (*schema.Banner, error) { + + banner := &schema.Banner{ + ID: util.NewXID(), + CreatedAt: time.Now(), + } + if err := formItem.FillTo(banner); err != nil { + return nil, err + } + + if err := a.BannerDAL.Create(ctx, banner); err != nil { + return nil, err + } + + return banner, nil +} + +// Update the specified role in the data access object. +func (a *Banner) Update(ctx context.Context, id string, formItem *schema.BannerForm) error { + banner, err := a.BannerDAL.Get(ctx, id) + if err != nil { + return err + } + + if err := formItem.FillTo(banner); err != nil { + return err + } + banner.UpdatedAt = time.Now() + + if err := a.BannerDAL.Update(ctx, banner); err != nil { + return err + } + return nil +} + +// Delete the specified role from the data access object. +func (a *Banner) Delete(ctx context.Context, id string) error { + exists, err := a.BannerDAL.Exists(ctx, id) + if err != nil { + return err + } else if !exists { + return errors.NotFound("", "Role not found") + } + + if err := a.BannerDAL.Delete(ctx, id); err != nil { + return err + } + return nil + +} diff --git a/internal/mods/rbac/biz/job.biz.go b/internal/mods/rbac/biz/job.biz.go new file mode 100644 index 0000000..235d53e --- /dev/null +++ b/internal/mods/rbac/biz/job.biz.go @@ -0,0 +1,178 @@ +package biz + +import ( + "context" + "time" + + "github.com/guxuan/hailin_service/internal/mods/rbac/dal" + "github.com/guxuan/hailin_service/internal/mods/rbac/schema" + "github.com/guxuan/hailin_service/pkg/cachex" + "github.com/guxuan/hailin_service/pkg/errors" + "github.com/guxuan/hailin_service/pkg/util" +) + +// Role management for RBAC +type Job struct { + Cache cachex.Cacher + Trans *util.Trans + JobDAL *dal.Job + JobAreaDAL *dal.JobArea +} + +// Query roles from the data access object based on the provided parameters and options. +func (a *Job) QueryJobArea(ctx context.Context) (*[]schema.JobArea, error) { + return a.JobAreaDAL.Query(ctx) +} + +// Create a new role in the data access object. +func (a *Job) CreateJobArea(ctx context.Context, formItem *schema.JobAreaForm) (*schema.JobArea, error) { + area := &schema.JobArea{ + ID: util.NewXID(), + CreatedAt: time.Now(), + } + area.Name = formItem.Name + area.Status = formItem.Status + if err := a.JobAreaDAL.Create(ctx, area); err != nil { + return nil, err + } + + return area, nil +} + +func (a *Job) QueryWebJobList(ctx context.Context) ([]*schema.WebJobData, error) { + var result []*schema.WebJobData + + query, err := a.JobAreaDAL.Query(ctx) + if err != nil { + return nil, err + } + for _, area := range *query { + if area.Status == "enabled" { + queryResult, err := a.JobDAL.Query(ctx, schema.JobQueryParam{JobAreaID: area.ID, Status: "enabled"}, schema.JobQueryOptions{ + QueryOptions: util.QueryOptions{ + OrderFields: []util.OrderByParam{ + {Field: "sequence", Direction: util.ASC}, + }, + }}) + if err == nil { + var info schema.WebJobData + info.JobAreaTitle = area.Name + info.JobList = queryResult.Data + result = append(result, &info) + } + } + } + return result, nil +} + +// Update the specified role in the data access object. +func (a *Job) UpdateJobArea(ctx context.Context, id string, formItem *schema.JobAreaForm) error { + area, err := a.JobAreaDAL.Get(ctx, id) + if err != nil { + return err + } + + area.UpdatedAt = time.Now() + area.Name = formItem.Name + area.Status = formItem.Status + if err := a.JobAreaDAL.Update(ctx, area); err != nil { + return err + } + return nil +} + +// Delete the specified role from the data access object. +func (a *Job) DeleteJobArea(ctx context.Context, id string) error { + exists, err := a.JobAreaDAL.Exists(ctx, id) + if err != nil { + return err + } else if !exists { + return errors.NotFound("", "Role not found") + } + + if err := a.JobAreaDAL.Delete(ctx, id); err != nil { + return err + } + return nil +} + +// Query roles from the data access object based on the provided parameters and options. +func (a *Job) Query(ctx context.Context, params schema.JobQueryParam) (*schema.JobQueryResult, error) { + params.Pagination = true + result, err := a.JobDAL.Query(ctx, params, schema.JobQueryOptions{ + QueryOptions: util.QueryOptions{ + OrderFields: []util.OrderByParam{ + {Field: "sequence", Direction: util.ASC}, + }, + }, + }) + if err != nil { + return nil, err + } + + return result, nil +} + +// Get the specified role from the data access object. +func (a *Job) Get(ctx context.Context, id string) (*schema.Job, error) { + job, err := a.JobDAL.Get(ctx, id) + if err != nil { + return nil, err + } else if job == nil { + return nil, errors.NotFound("", "Banner not found") + } + + return job, nil +} + +// Create a new role in the data access object. +func (a *Job) Create(ctx context.Context, formItem *schema.JobForm) (*schema.Job, error) { + + job := &schema.Job{ + ID: util.NewXID(), + CreatedAt: time.Now(), + } + if err := formItem.FillTo(job); err != nil { + return nil, err + } + + if err := a.JobDAL.Create(ctx, job); err != nil { + return nil, err + } + + return job, nil +} + +// Update the specified role in the data access object. +func (a *Job) Update(ctx context.Context, id string, formItem *schema.JobForm) error { + job, err := a.JobDAL.Get(ctx, id) + if err != nil { + return err + } + + if err := formItem.FillTo(job); err != nil { + return err + } + job.UpdatedAt = time.Now() + + if err := a.JobDAL.Update(ctx, job); err != nil { + return err + } + return nil +} + +// Delete the specified role from the data access object. +func (a *Job) Delete(ctx context.Context, id string) error { + exists, err := a.JobDAL.Exists(ctx, id) + if err != nil { + return err + } else if !exists { + return errors.NotFound("", "Role not found") + } + + if err := a.JobDAL.Delete(ctx, id); err != nil { + return err + } + return nil + +} diff --git a/internal/mods/rbac/biz/logger.biz.go b/internal/mods/rbac/biz/logger.biz.go new file mode 100644 index 0000000..eec676d --- /dev/null +++ b/internal/mods/rbac/biz/logger.biz.go @@ -0,0 +1,31 @@ +package biz + +import ( + "context" + + "github.com/guxuan/hailin_service/internal/mods/rbac/dal" + "github.com/guxuan/hailin_service/internal/mods/rbac/schema" + "github.com/guxuan/hailin_service/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..1c86a75 --- /dev/null +++ b/internal/mods/rbac/biz/login.biz.go @@ -0,0 +1,374 @@ +package biz + +import ( + "context" + "net/http" + "sort" + "time" + + "github.com/LyricTian/captcha" + "github.com/gin-gonic/gin" + "github.com/guxuan/hailin_service/internal/config" + "github.com/guxuan/hailin_service/internal/mods/rbac/dal" + "github.com/guxuan/hailin_service/internal/mods/rbac/schema" + "github.com/guxuan/hailin_service/pkg/cachex" + "github.com/guxuan/hailin_service/pkg/crypto/hash" + "github.com/guxuan/hailin_service/pkg/errors" + "github.com/guxuan/hailin_service/pkg/jwtx" + "github.com/guxuan/hailin_service/pkg/logging" + "github.com/guxuan/hailin_service/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 + MenuDAL *dal.Menu + UserBIZ *User +} + +func (a *Login) ParseUserID(c *gin.Context) (string, error) { + rootID := config.C.General.Root.ID + if config.C.Middleware.Auth.Disable { + return rootID, 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) + + userID, err := a.Auth.ParseSubject(ctx, token) + if err != nil { + if err == jwtx.ErrInvalidToken { + return "", invalidToken + } + return "", err + } else if userID == rootID { + c.Request = c.Request.WithContext(util.NewIsRootUser(ctx)) + return userID, 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 userID, nil + } + + // 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)) + return userID, 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) { + token, err := a.Auth.GenerateToken(ctx, userID) + 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, "账号密码错误!") + } + + userID := config.C.General.Root.ID + ctx = logging.NewUserID(ctx, userID) + logging.Context(ctx).Info("Login by root") + 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, 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) +} + +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) + 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, 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, + }, 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, 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, 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) + } + 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) + 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/memorabilia.biz.go b/internal/mods/rbac/biz/memorabilia.biz.go new file mode 100644 index 0000000..4519591 --- /dev/null +++ b/internal/mods/rbac/biz/memorabilia.biz.go @@ -0,0 +1,99 @@ +package biz + +import ( + "context" + "time" + + "github.com/guxuan/hailin_service/internal/mods/rbac/dal" + "github.com/guxuan/hailin_service/internal/mods/rbac/schema" + "github.com/guxuan/hailin_service/pkg/cachex" + "github.com/guxuan/hailin_service/pkg/errors" + "github.com/guxuan/hailin_service/pkg/util" +) + +// Role management for RBAC +type Memorabilia struct { + Cache cachex.Cacher + Trans *util.Trans + MemorabiliaDAL *dal.Memorabilia +} + +// Query roles from the data access object based on the provided parameters and options. +func (a *Memorabilia) Query(ctx context.Context, params schema.MemorabiliaQueryParam) (*schema.MemorabiliaQueryResult, error) { + params.Pagination = true + result, err := a.MemorabiliaDAL.Query(ctx, params, schema.MemorabiliaQueryOptions{ + QueryOptions: util.QueryOptions{ + OrderFields: []util.OrderByParam{ + {Field: "sequence", Direction: util.DESC}, + }, + }, + }) + if err != nil { + return nil, err + } + return result, nil +} + +// Get the specified role from the data access object. +func (a *Memorabilia) Get(ctx context.Context, id string) (*schema.Memorabilia, error) { + memorabilia, err := a.MemorabiliaDAL.Get(ctx, id) + if err != nil { + return nil, err + } else if memorabilia == nil { + return nil, errors.NotFound("", "Banner not found") + } + + return memorabilia, nil +} + +// Create a new role in the data access object. +func (a *Memorabilia) Create(ctx context.Context, formItem *schema.MemorabiliaForm) (*schema.Memorabilia, error) { + + memorabilia := &schema.Memorabilia{ + ID: util.NewXID(), + CreatedAt: time.Now(), + } + if err := formItem.FillTo(memorabilia); err != nil { + return nil, err + } + + if err := a.MemorabiliaDAL.Create(ctx, memorabilia); err != nil { + return nil, err + } + + return memorabilia, nil +} + +// Update the specified role in the data access object. +func (a *Memorabilia) Update(ctx context.Context, id string, formItem *schema.MemorabiliaForm) error { + memorabilia, err := a.MemorabiliaDAL.Get(ctx, id) + if err != nil { + return err + } + + if err := formItem.FillTo(memorabilia); err != nil { + return err + } + memorabilia.UpdatedAt = time.Now() + + if err := a.MemorabiliaDAL.Update(ctx, memorabilia); err != nil { + return err + } + return nil +} + +// Delete the specified role from the data access object. +func (a *Memorabilia) Delete(ctx context.Context, id string) error { + exists, err := a.MemorabiliaDAL.Exists(ctx, id) + if err != nil { + return err + } else if !exists { + return errors.NotFound("", "Role not found") + } + + if err := a.MemorabiliaDAL.Delete(ctx, id); err != nil { + return err + } + return nil + +} diff --git a/internal/mods/rbac/biz/menu.biz.go b/internal/mods/rbac/biz/menu.biz.go new file mode 100644 index 0000000..0d9ab08 --- /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.com/guxuan/hailin_service/internal/config" + "github.com/guxuan/hailin_service/internal/mods/rbac/dal" + "github.com/guxuan/hailin_service/internal/mods/rbac/schema" + "github.com/guxuan/hailin_service/pkg/cachex" + "github.com/guxuan/hailin_service/pkg/encoding/json" + "github.com/guxuan/hailin_service/pkg/encoding/yaml" + "github.com/guxuan/hailin_service/pkg/errors" + "github.com/guxuan/hailin_service/pkg/logging" + "github.com/guxuan/hailin_service/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/product.biz.go b/internal/mods/rbac/biz/product.biz.go new file mode 100644 index 0000000..4dd4c4c --- /dev/null +++ b/internal/mods/rbac/biz/product.biz.go @@ -0,0 +1,102 @@ +package biz + +import ( + "context" + "time" + + "github.com/guxuan/hailin_service/internal/mods/rbac/dal" + "github.com/guxuan/hailin_service/internal/mods/rbac/schema" + "github.com/guxuan/hailin_service/pkg/cachex" + "github.com/guxuan/hailin_service/pkg/errors" + "github.com/guxuan/hailin_service/pkg/util" +) + +type Product struct { + Cache cachex.Cacher + Trans *util.Trans + ProductDAL *dal.Product + ProductCategoryDAL *dal.ProductCategory +} + +func (a *Product) QueryProductCategory(ctx context.Context) (*[]schema.ProductCategory, error) { + query, err := a.ProductCategoryDAL.Query(ctx) + if err != nil { + return nil, err + } + return query, nil +} + +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: "sequence", Direction: util.DESC}, + }, + }, + }) + if err != nil { + return nil, err + } + return result, nil +} + +func (a *Product) Get(ctx context.Context, id string) (*schema.Product, error) { + product, err := a.ProductDAL.Get(ctx, id) + if err != nil { + return nil, err + } else if product == nil { + return nil, errors.NotFound("", "Banner not found") + } + + return product, nil +} + +func (a *Product) Create(ctx context.Context, formItem *schema.ProductForm) (*schema.Product, error) { + + product := &schema.Product{ + ID: util.NewXID(), + CreatedAt: time.Now(), + } + if err := formItem.FillTo(product); err != nil { + return nil, err + } + + if err := a.ProductDAL.Create(ctx, product); err != nil { + return nil, err + } + + return product, nil +} + +func (a *Product) Update(ctx context.Context, id string, formItem *schema.ProductForm) error { + product, err := a.ProductDAL.Get(ctx, id) + if err != nil { + return err + } + + if err := formItem.FillTo(product); err != nil { + return err + } + product.UpdatedAt = time.Now() + + if err := a.ProductDAL.Update(ctx, product); err != nil { + return err + } + return nil +} + +func (a *Product) Delete(ctx context.Context, id string) error { + exists, err := a.ProductDAL.Exists(ctx, id) + if err != nil { + return err + } else if !exists { + return errors.NotFound("", "Role not found") + } + + if err := a.ProductDAL.Delete(ctx, id); err != nil { + return err + } + return nil + +} diff --git a/internal/mods/rbac/biz/role.biz.go b/internal/mods/rbac/biz/role.biz.go new file mode 100644 index 0000000..5e0e091 --- /dev/null +++ b/internal/mods/rbac/biz/role.biz.go @@ -0,0 +1,179 @@ +package biz + +import ( + "context" + "fmt" + "time" + + "github.com/guxuan/hailin_service/internal/config" + "github.com/guxuan/hailin_service/internal/mods/rbac/dal" + "github.com/guxuan/hailin_service/internal/mods/rbac/schema" + "github.com/guxuan/hailin_service/pkg/cachex" + "github.com/guxuan/hailin_service/pkg/errors" + "github.com/guxuan/hailin_service/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/team.biz.go b/internal/mods/rbac/biz/team.biz.go new file mode 100644 index 0000000..bb3a9c0 --- /dev/null +++ b/internal/mods/rbac/biz/team.biz.go @@ -0,0 +1,94 @@ +package biz + +import ( + "context" + "time" + + "github.com/guxuan/hailin_service/internal/mods/rbac/dal" + "github.com/guxuan/hailin_service/internal/mods/rbac/schema" + "github.com/guxuan/hailin_service/pkg/cachex" + "github.com/guxuan/hailin_service/pkg/errors" + "github.com/guxuan/hailin_service/pkg/util" +) + +type Team struct { + Cache cachex.Cacher + Trans *util.Trans + TeamDAL *dal.Team +} + +func (a *Team) Query(ctx context.Context, params schema.TeamQueryParam) (*schema.TeamQueryResult, error) { + params.Pagination = true + result, err := a.TeamDAL.Query(ctx, params, schema.TeamQueryOptions{ + QueryOptions: util.QueryOptions{ + OrderFields: []util.OrderByParam{ + {Field: "sequence", Direction: util.DESC}, + }, + }, + }) + if err != nil { + return nil, err + } + return result, nil +} + +// Get the specified role from the data access object. +func (a *Team) Get(ctx context.Context, id string) (*schema.Team, error) { + item, err := a.TeamDAL.Get(ctx, id) + if err != nil { + return nil, err + } else if item == nil { + return nil, errors.NotFound("", "Banner not found") + } + + return item, nil +} + +func (a *Team) Create(ctx context.Context, formItem *schema.TeamForm) (*schema.Team, error) { + + team := &schema.Team{ + ID: util.NewXID(), + CreatedAt: time.Now(), + } + if err := formItem.FillTo(team); err != nil { + return nil, err + } + + if err := a.TeamDAL.Create(ctx, team); err != nil { + return nil, err + } + + return team, nil +} + +func (a *Team) Update(ctx context.Context, id string, formItem *schema.TeamForm) error { + team, err := a.TeamDAL.Get(ctx, id) + if err != nil { + return err + } + + if err := formItem.FillTo(team); err != nil { + return err + } + team.UpdatedAt = time.Now() + + if err := a.TeamDAL.Update(ctx, team); err != nil { + return err + } + return nil +} + +func (a *Team) Delete(ctx context.Context, id string) error { + exists, err := a.TeamDAL.Exists(ctx, id) + if err != nil { + return err + } else if !exists { + return errors.NotFound("", "Role not found") + } + + if err := a.TeamDAL.Delete(ctx, id); err != nil { + return err + } + return nil + +} diff --git a/internal/mods/rbac/biz/upload.biz.go b/internal/mods/rbac/biz/upload.biz.go new file mode 100644 index 0000000..9b5e84b --- /dev/null +++ b/internal/mods/rbac/biz/upload.biz.go @@ -0,0 +1,54 @@ +package biz + +import ( + "context" + "fmt" + "github.com/guxuan/hailin_service/internal/config" + "github.com/guxuan/hailin_service/pkg/cachex" + "github.com/guxuan/hailin_service/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/rbac/biz/user.biz.go b/internal/mods/rbac/biz/user.biz.go new file mode 100644 index 0000000..3f5e3f5 --- /dev/null +++ b/internal/mods/rbac/biz/user.biz.go @@ -0,0 +1,227 @@ +package biz + +import ( + "context" + "time" + + "github.com/guxuan/hailin_service/internal/config" + "github.com/guxuan/hailin_service/internal/mods/rbac/dal" + "github.com/guxuan/hailin_service/internal/mods/rbac/schema" + "github.com/guxuan/hailin_service/pkg/cachex" + "github.com/guxuan/hailin_service/pkg/crypto/hash" + "github.com/guxuan/hailin_service/pkg/errors" + "github.com/guxuan/hailin_service/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/biz/video.biz.go b/internal/mods/rbac/biz/video.biz.go new file mode 100644 index 0000000..d9e7bd7 --- /dev/null +++ b/internal/mods/rbac/biz/video.biz.go @@ -0,0 +1,99 @@ +package biz + +import ( + "context" + "time" + + "github.com/guxuan/hailin_service/internal/mods/rbac/dal" + "github.com/guxuan/hailin_service/internal/mods/rbac/schema" + "github.com/guxuan/hailin_service/pkg/cachex" + "github.com/guxuan/hailin_service/pkg/errors" + "github.com/guxuan/hailin_service/pkg/util" +) + +// Role management for RBAC +type Video struct { + Cache cachex.Cacher + Trans *util.Trans + VideoDAL *dal.Video +} + +// Query roles from the data access object based on the provided parameters and options. +func (a *Video) Query(ctx context.Context, params schema.VideoQueryParam) (*schema.VideoQueryResult, error) { + params.Pagination = true + result, err := a.VideoDAL.Query(ctx, params, schema.VideoQueryOptions{ + QueryOptions: util.QueryOptions{ + OrderFields: []util.OrderByParam{ + {Field: "sequence", Direction: util.DESC}, + }, + }, + }) + if err != nil { + return nil, err + } + return result, nil +} + +// Get the specified role from the data access object. +func (a *Video) Get(ctx context.Context, id string) (*schema.Video, error) { + video, err := a.VideoDAL.Get(ctx, id) + if err != nil { + return nil, err + } else if video == nil { + return nil, errors.NotFound("", "Banner not found") + } + + return video, nil +} + +// Create a new role in the data access object. +func (a *Video) Create(ctx context.Context, formItem *schema.VideoForm) (*schema.Video, error) { + + video := &schema.Video{ + ID: util.NewXID(), + CreatedAt: time.Now(), + } + if err := formItem.FillTo(video); err != nil { + return nil, err + } + + if err := a.VideoDAL.Create(ctx, video); err != nil { + return nil, err + } + + return video, nil +} + +// Update the specified role in the data access object. +func (a *Video) Update(ctx context.Context, id string, formItem *schema.VideoForm) error { + video, err := a.VideoDAL.Get(ctx, id) + if err != nil { + return err + } + + if err := formItem.FillTo(video); err != nil { + return err + } + video.UpdatedAt = time.Now() + + if err := a.VideoDAL.Update(ctx, video); err != nil { + return err + } + return nil +} + +// Delete the specified role from the data access object. +func (a *Video) Delete(ctx context.Context, id string) error { + exists, err := a.VideoDAL.Exists(ctx, id) + if err != nil { + return err + } else if !exists { + return errors.NotFound("", "Role not found") + } + + if err := a.VideoDAL.Delete(ctx, id); err != nil { + return err + } + return nil + +} diff --git a/internal/mods/rbac/biz/webSite.biz.go b/internal/mods/rbac/biz/webSite.biz.go new file mode 100644 index 0000000..43471c0 --- /dev/null +++ b/internal/mods/rbac/biz/webSite.biz.go @@ -0,0 +1,63 @@ +package biz + +import ( + "context" + "time" + + "github.com/guxuan/hailin_service/internal/mods/rbac/dal" + "github.com/guxuan/hailin_service/internal/mods/rbac/schema" + "github.com/guxuan/hailin_service/pkg/cachex" + "github.com/guxuan/hailin_service/pkg/util" +) + +// Role management for RBAC +type WebSite struct { + Cache cachex.Cacher + Trans *util.Trans + WebSiteDAL *dal.WebSite +} + +// Query roles from the data access object based on the provided parameters and options. +func (a *WebSite) Query(ctx context.Context) (*schema.WebSite, error) { + result, err := a.WebSiteDAL.Query(ctx) + if err != nil { + return nil, err + } + return result, nil +} + +// Create a new role in the data access object. +func (a *WebSite) Create(ctx context.Context, formItem *schema.WebSiteForm) (*schema.WebSite, error) { + + item := &schema.WebSite{ + ID: util.NewXID(), + CreatedAt: time.Now(), + } + if err := formItem.FillTo(item); err != nil { + return nil, err + } + + if err := a.WebSiteDAL.Create(ctx, item); err != nil { + return nil, err + } + + return item, nil +} + +// Update the specified role in the data access object. +func (a *WebSite) Update(ctx context.Context, id string, formItem *schema.WebSiteForm) error { + article, err := a.WebSiteDAL.Get(ctx, id) + if err != nil { + return err + } + + if err := formItem.FillTo(article); err != nil { + return err + } + article.UpdatedAt = time.Now() + + if err := a.WebSiteDAL.Update(ctx, article); err != nil { + return err + } + return nil +} diff --git a/internal/mods/rbac/casbin.go b/internal/mods/rbac/casbin.go new file mode 100644 index 0000000..cb9e4d2 --- /dev/null +++ b/internal/mods/rbac/casbin.go @@ -0,0 +1,222 @@ +package rbac + +import ( + "bytes" + "context" + "fmt" + "os" + "path/filepath" + "strconv" + "strings" + "sync" + "sync/atomic" + "time" + + "github.com/guxuan/hailin_service/internal/config" + "github.com/guxuan/hailin_service/internal/mods/rbac/dal" + "github.com/guxuan/hailin_service/internal/mods/rbac/schema" + "github.com/guxuan/hailin_service/pkg/cachex" + "github.com/guxuan/hailin_service/pkg/logging" + "github.com/guxuan/hailin_service/pkg/util" + "github.com/casbin/casbin/v2" + "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/article.dal.go b/internal/mods/rbac/dal/article.dal.go new file mode 100644 index 0000000..6d53f05 --- /dev/null +++ b/internal/mods/rbac/dal/article.dal.go @@ -0,0 +1,89 @@ +package dal + +import ( + "context" + "github.com/guxuan/hailin_service/internal/mods/rbac/schema" + "github.com/guxuan/hailin_service/pkg/errors" + "github.com/guxuan/hailin_service/pkg/util" + "gorm.io/gorm" +) + +func GetArticleDB(ctx context.Context, defDB *gorm.DB) *gorm.DB { + return util.GetDB(ctx, defDB).Model(new(schema.Article)) +} + +type Article struct { + DB *gorm.DB +} + +// Query roles from the database based on the provided parameters and options. +func (a *Article) Query(ctx context.Context, params schema.ArticleQueryParam, opts ...schema.ArticleQueryOptions) (*schema.ArticleQueryResult, error) { + var opt schema.ArticleQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + db := GetArticleDB(ctx, a.DB) + + if v := params.LikeTitle; len(v) > 0 { + db = db.Where("title LIKE ?", "%"+v+"%") + } + if v := params.Status; len(v) > 0 { + db = db.Where("status = ?", v) + } + if v := params.Typer; len(v) > 0 { + db = db.Where("type = ?", v) + } + var list schema.Articles + pageResult, err := util.WrapPageQuery(ctx, db, params.PaginationParam, opt.QueryOptions, &list) + if err != nil { + return nil, errors.WithStack(err) + } + + queryResult := &schema.ArticleQueryResult{ + PageResult: pageResult, + Data: list, + } + return queryResult, nil +} + +// Get the specified role from the database. +func (a *Article) Get(ctx context.Context, id string, opts ...schema.ArticleQueryOptions) (*schema.Article, error) { + var opt schema.ArticleQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + item := new(schema.Article) + ok, err := util.FindOne(ctx, GetArticleDB(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 *Article) Exists(ctx context.Context, id string) (bool, error) { + ok, err := util.Exists(ctx, GetArticleDB(ctx, a.DB).Where("id=?", id)) + return ok, errors.WithStack(err) +} + +// Create a new role. +func (a *Article) Create(ctx context.Context, item *schema.Article) error { + result := GetArticleDB(ctx, a.DB).Create(item) + return errors.WithStack(result.Error) +} + +// Update the specified role in the database. +func (a *Article) Update(ctx context.Context, item *schema.Article) error { + result := GetArticleDB(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 *Article) Delete(ctx context.Context, id string) error { + result := GetArticleDB(ctx, a.DB).Where("id=?", id).Delete(new(schema.Article)) + return errors.WithStack(result.Error) +} diff --git a/internal/mods/rbac/dal/banner.dal.go b/internal/mods/rbac/dal/banner.dal.go new file mode 100644 index 0000000..da01569 --- /dev/null +++ b/internal/mods/rbac/dal/banner.dal.go @@ -0,0 +1,88 @@ +package dal + +import ( + "context" + + "github.com/guxuan/hailin_service/internal/mods/rbac/schema" + "github.com/guxuan/hailin_service/pkg/errors" + "github.com/guxuan/hailin_service/pkg/util" + "gorm.io/gorm" +) + +func GetBannerDB(ctx context.Context, defDB *gorm.DB) *gorm.DB { + return util.GetDB(ctx, defDB).Model(new(schema.Banner)) +} + +type Banner struct { + DB *gorm.DB +} + +// Query roles 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) + + 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.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 role from the database. +func (a *Banner) Get(ctx context.Context, id string, 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 +} + +// Exist checks if the specified role exists in the database. +func (a *Banner) Exists(ctx context.Context, id string) (bool, error) { + ok, err := util.Exists(ctx, GetBannerDB(ctx, a.DB).Where("id=?", id)) + return ok, errors.WithStack(err) +} + +// Create a new role. +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 role 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 role from the database. +func (a *Banner) Delete(ctx context.Context, id string) error { + result := GetBannerDB(ctx, a.DB).Where("id=?", id).Delete(new(schema.Banner)) + return errors.WithStack(result.Error) +} diff --git a/internal/mods/rbac/dal/job.dal.go b/internal/mods/rbac/dal/job.dal.go new file mode 100644 index 0000000..ce3c89b --- /dev/null +++ b/internal/mods/rbac/dal/job.dal.go @@ -0,0 +1,83 @@ +package dal + +import ( + "context" + "github.com/guxuan/hailin_service/internal/mods/rbac/schema" + "github.com/guxuan/hailin_service/pkg/errors" + "github.com/guxuan/hailin_service/pkg/util" + "gorm.io/gorm" +) + +func GetJobDB(ctx context.Context, defDB *gorm.DB) *gorm.DB { + return util.GetDB(ctx, defDB).Model(new(schema.Job)) +} + +type Job struct { + DB *gorm.DB +} + +func (a *Job) Query(ctx context.Context, params schema.JobQueryParam, opts ...schema.JobQueryOptions) (*schema.JobQueryResult, error) { + var opt schema.JobQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + db := GetJobDB(ctx, a.DB) + + if v := params.LikeTitle; len(v) > 0 { + db = db.Where("title LIKE ?", "%"+v+"%") + } + if v := params.Status; len(v) > 0 { + db = db.Where("status = ?", v) + } + if v := params.JobAreaID; len(v) > 0 { + db = db.Where("jobAreaId = ?", v) + } + var list schema.Jobs + pageResult, err := util.WrapPageQuery(ctx, db, params.PaginationParam, opt.QueryOptions, &list) + if err != nil { + return nil, errors.WithStack(err) + } + + queryResult := &schema.JobQueryResult{ + PageResult: pageResult, + Data: list, + } + return queryResult, nil +} + +func (a *Job) Get(ctx context.Context, id string, opts ...schema.JobQueryOptions) (*schema.Job, error) { + var opt schema.JobQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + item := new(schema.Job) + ok, err := util.FindOne(ctx, GetJobDB(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 *Job) Exists(ctx context.Context, id string) (bool, error) { + ok, err := util.Exists(ctx, GetJobDB(ctx, a.DB).Where("id=?", id)) + return ok, errors.WithStack(err) +} + +func (a *Job) Create(ctx context.Context, item *schema.Job) error { + result := GetJobDB(ctx, a.DB).Create(item) + return errors.WithStack(result.Error) +} + +func (a *Job) Update(ctx context.Context, item *schema.Job) error { + result := GetJobDB(ctx, a.DB).Where("id=?", item.ID).Select("*").Omit("created_at").Updates(item) + return errors.WithStack(result.Error) +} + +func (a *Job) Delete(ctx context.Context, id string) error { + result := GetJobDB(ctx, a.DB).Where("id=?", id).Delete(new(schema.Job)) + return errors.WithStack(result.Error) +} diff --git a/internal/mods/rbac/dal/jobArea.dal.go b/internal/mods/rbac/dal/jobArea.dal.go new file mode 100644 index 0000000..2b13446 --- /dev/null +++ b/internal/mods/rbac/dal/jobArea.dal.go @@ -0,0 +1,58 @@ +package dal + +import ( + "context" + "github.com/guxuan/hailin_service/internal/mods/rbac/schema" + "github.com/guxuan/hailin_service/pkg/errors" + "github.com/guxuan/hailin_service/pkg/util" + "gorm.io/gorm" +) + +func GetJobAreaDB(ctx context.Context, defDB *gorm.DB) *gorm.DB { + return util.GetDB(ctx, defDB).Model(new(schema.JobArea)) +} + +type JobArea struct { + DB *gorm.DB +} + +func (a *JobArea) Query(ctx context.Context) (*[]schema.JobArea, error) { + + var list []schema.JobArea + tx := GetJobAreaDB(ctx, a.DB).Find(&list) + if tx.Error != nil { + return nil, errors.WithStack(tx.Error) + + } + return &list, nil +} +func (a *JobArea) Get(ctx context.Context, id string) (*schema.JobArea, error) { + + item := new(schema.JobArea) + ok, err := util.FindOne(ctx, GetJobAreaDB(ctx, a.DB).Where("id=?", id), util.QueryOptions{}, item) + if err != nil { + return nil, errors.WithStack(err) + } else if !ok { + return nil, nil + } + return item, nil +} +func (a *JobArea) Exists(ctx context.Context, name string) (bool, error) { + ok, err := util.Exists(ctx, GetJobAreaDB(ctx, a.DB).Where("name =?", name)) + return ok, errors.WithStack(err) +} + +func (a *JobArea) Create(ctx context.Context, item *schema.JobArea) error { + result := GetJobAreaDB(ctx, a.DB).Create(item) + return errors.WithStack(result.Error) +} + +func (a *JobArea) Update(ctx context.Context, item *schema.JobArea) error { + result := GetJobAreaDB(ctx, a.DB).Where("id=?", item.ID).Select("*").Omit("created_at").Updates(item) + return errors.WithStack(result.Error) +} + +func (a *JobArea) Delete(ctx context.Context, id string) error { + result := GetJobAreaDB(ctx, a.DB).Where("id=?", id).Delete(new(schema.JobArea)) + return errors.WithStack(result.Error) +} diff --git a/internal/mods/rbac/dal/logger.dal.go b/internal/mods/rbac/dal/logger.dal.go new file mode 100644 index 0000000..6833d15 --- /dev/null +++ b/internal/mods/rbac/dal/logger.dal.go @@ -0,0 +1,64 @@ +package dal + +import ( + "context" + "fmt" + + "github.com/guxuan/hailin_service/internal/mods/rbac/schema" + "github.com/guxuan/hailin_service/pkg/errors" + "github.com/guxuan/hailin_service/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/memorabilia.dal.go b/internal/mods/rbac/dal/memorabilia.dal.go new file mode 100644 index 0000000..bf24750 --- /dev/null +++ b/internal/mods/rbac/dal/memorabilia.dal.go @@ -0,0 +1,86 @@ +package dal + +import ( + "context" + "github.com/guxuan/hailin_service/internal/mods/rbac/schema" + "github.com/guxuan/hailin_service/pkg/errors" + "github.com/guxuan/hailin_service/pkg/util" + "gorm.io/gorm" +) + +func GetMemorabiliaDB(ctx context.Context, defDB *gorm.DB) *gorm.DB { + return util.GetDB(ctx, defDB).Model(new(schema.Memorabilia)) +} + +type Memorabilia struct { + DB *gorm.DB +} + +func (a *Memorabilia) Query(ctx context.Context, params schema.MemorabiliaQueryParam, opts ...schema.MemorabiliaQueryOptions) (*schema.MemorabiliaQueryResult, error) { + var opt schema.MemorabiliaQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + db := GetMemorabiliaDB(ctx, a.DB) + + if v := params.LikeTitle; len(v) > 0 { + db = db.Where("title LIKE ?", "%"+v+"%") + } + if v := params.Status; len(v) > 0 { + db = db.Where("status = ?", v) + } + if v := params.Year; v > 0 { + db = db.Where("year = ?", v) + } + if v := params.Month; v > 0 { + db = db.Where("month = ?", v) + } + var list schema.Memorabilias + pageResult, err := util.WrapPageQuery(ctx, db, params.PaginationParam, opt.QueryOptions, &list) + if err != nil { + return nil, errors.WithStack(err) + } + + queryResult := &schema.MemorabiliaQueryResult{ + PageResult: pageResult, + Data: list, + } + return queryResult, nil +} + +func (a *Memorabilia) Get(ctx context.Context, id string, opts ...schema.MemorabiliaQueryOptions) (*schema.Memorabilia, error) { + var opt schema.MemorabiliaQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + item := new(schema.Memorabilia) + ok, err := util.FindOne(ctx, GetMemorabiliaDB(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 *Memorabilia) Exists(ctx context.Context, id string) (bool, error) { + ok, err := util.Exists(ctx, GetMemorabiliaDB(ctx, a.DB).Where("id=?", id)) + return ok, errors.WithStack(err) +} + +func (a *Memorabilia) Create(ctx context.Context, item *schema.Memorabilia) error { + result := GetMemorabiliaDB(ctx, a.DB).Create(item) + return errors.WithStack(result.Error) +} + +func (a *Memorabilia) Update(ctx context.Context, item *schema.Memorabilia) error { + result := GetMemorabiliaDB(ctx, a.DB).Where("id=?", item.ID).Select("*").Omit("created_at").Updates(item) + return errors.WithStack(result.Error) +} + +func (a *Memorabilia) Delete(ctx context.Context, id string) error { + result := GetMemorabiliaDB(ctx, a.DB).Where("id=?", id).Delete(new(schema.Memorabilia)) + return errors.WithStack(result.Error) +} diff --git a/internal/mods/rbac/dal/menu.dal.go b/internal/mods/rbac/dal/menu.dal.go new file mode 100644 index 0000000..d4bf6f3 --- /dev/null +++ b/internal/mods/rbac/dal/menu.dal.go @@ -0,0 +1,165 @@ +package dal + +import ( + "context" + + "github.com/guxuan/hailin_service/internal/mods/rbac/schema" + "github.com/guxuan/hailin_service/pkg/errors" + "github.com/guxuan/hailin_service/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..3a3cd85 --- /dev/null +++ b/internal/mods/rbac/dal/menu_resource.dal.go @@ -0,0 +1,101 @@ +package dal + +import ( + "context" + + "github.com/guxuan/hailin_service/internal/mods/rbac/schema" + "github.com/guxuan/hailin_service/pkg/errors" + "github.com/guxuan/hailin_service/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/product.dal.go b/internal/mods/rbac/dal/product.dal.go new file mode 100644 index 0000000..db00f3e --- /dev/null +++ b/internal/mods/rbac/dal/product.dal.go @@ -0,0 +1,83 @@ +package dal + +import ( + "context" + "github.com/guxuan/hailin_service/internal/mods/rbac/schema" + "github.com/guxuan/hailin_service/pkg/errors" + "github.com/guxuan/hailin_service/pkg/util" + "gorm.io/gorm" +) + +func GetProductDB(ctx context.Context, defDB *gorm.DB) *gorm.DB { + return util.GetDB(ctx, defDB).Model(new(schema.Product)) +} + +type Product struct { + DB *gorm.DB +} + +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) + + if v := params.LikeTitle; len(v) > 0 { + db = db.Where("title LIKE ?", "%"+v+"%") + } + if v := params.Status; len(v) > 0 { + db = db.Where("status = ?", v) + } + if v := params.CategoryID; v > 0 { + db = db.Where("categoryId = ?", v) + } + 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 +} + +func (a *Product) Get(ctx context.Context, id string, 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 +} + +func (a *Product) Exists(ctx context.Context, id string) (bool, error) { + ok, err := util.Exists(ctx, GetProductDB(ctx, a.DB).Where("id=?", id)) + return ok, errors.WithStack(err) +} + +func (a *Product) Create(ctx context.Context, item *schema.Product) error { + result := GetProductDB(ctx, a.DB).Create(item) + return errors.WithStack(result.Error) +} + +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) +} + +func (a *Product) Delete(ctx context.Context, id string) error { + result := GetProductDB(ctx, a.DB).Where("id=?", id).Delete(new(schema.Product)) + return errors.WithStack(result.Error) +} diff --git a/internal/mods/rbac/dal/productCategory.dal.go b/internal/mods/rbac/dal/productCategory.dal.go new file mode 100644 index 0000000..7094fef --- /dev/null +++ b/internal/mods/rbac/dal/productCategory.dal.go @@ -0,0 +1,59 @@ +package dal + +import ( + "context" + "github.com/guxuan/hailin_service/internal/mods/rbac/schema" + "github.com/guxuan/hailin_service/pkg/errors" + "github.com/guxuan/hailin_service/pkg/util" + "gorm.io/gorm" +) + +func GetProductCategoryDB(ctx context.Context, defDB *gorm.DB) *gorm.DB { + return util.GetDB(ctx, defDB).Model(new(schema.ProductCategory)) +} + +type ProductCategory struct { + DB *gorm.DB +} + +func (a *ProductCategory) Query(ctx context.Context) (*[]schema.ProductCategory, error) { + + db := GetProductCategoryDB(ctx, a.DB) + + var list []schema.ProductCategory + tx := db.Find(&list) + if tx.Error != nil { + return nil, errors.WithStack(tx.Error) + } + return &list, nil +} + +func (a *ProductCategory) Get(ctx context.Context, id uint) (*schema.ProductCategory, error) { + + item := new(schema.ProductCategory) + tx := GetProductCategoryDB(ctx, a.DB).Where("id=?", id).First(item) + if tx.Error != nil { // 没有找到 + return nil, errors.WithStack(tx.Error) + } + return item, nil +} + +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) +} + +func (a *ProductCategory) Create(ctx context.Context, item *schema.ProductCategory) error { + result := GetProductCategoryDB(ctx, a.DB).Create(item) + return errors.WithStack(result.Error) +} + +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) +} + +func (a *ProductCategory) Delete(ctx context.Context, id string) error { + result := GetProductCategoryDB(ctx, a.DB).Where("id=?", id).Delete(new(schema.ProductCategory)) + 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..61e8e6e --- /dev/null +++ b/internal/mods/rbac/dal/role.dal.go @@ -0,0 +1,100 @@ +package dal + +import ( + "context" + + "github.com/guxuan/hailin_service/internal/mods/rbac/schema" + "github.com/guxuan/hailin_service/pkg/errors" + "github.com/guxuan/hailin_service/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..05bcacb --- /dev/null +++ b/internal/mods/rbac/dal/role_menu.dal.go @@ -0,0 +1,98 @@ +package dal + +import ( + "context" + + "github.com/guxuan/hailin_service/internal/mods/rbac/schema" + "github.com/guxuan/hailin_service/pkg/errors" + "github.com/guxuan/hailin_service/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/team.go b/internal/mods/rbac/dal/team.go new file mode 100644 index 0000000..ab188f3 --- /dev/null +++ b/internal/mods/rbac/dal/team.go @@ -0,0 +1,81 @@ +package dal + +import ( + "context" + "github.com/guxuan/hailin_service/internal/mods/rbac/schema" + "github.com/guxuan/hailin_service/pkg/errors" + "github.com/guxuan/hailin_service/pkg/util" + "gorm.io/gorm" +) + +func GetTeamDB(ctx context.Context, defDB *gorm.DB) *gorm.DB { + return util.GetDB(ctx, defDB).Model(new(schema.Team)) +} + +type Team struct { + DB *gorm.DB +} + +func (a *Team) Query(ctx context.Context, params schema.TeamQueryParam, opts ...schema.TeamQueryOptions) (*schema.TeamQueryResult, error) { + var opt schema.TeamQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + db := GetTeamDB(ctx, a.DB) + + 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.Teams + pageResult, err := util.WrapPageQuery(ctx, db, params.PaginationParam, opt.QueryOptions, &list) + if err != nil { + return nil, errors.WithStack(err) + } + + queryResult := &schema.TeamQueryResult{ + PageResult: pageResult, + Data: list, + } + return queryResult, nil +} + +func (a *Team) Get(ctx context.Context, id string, opts ...schema.TeamQueryOptions) (*schema.Team, error) { + var opt schema.TeamQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + item := new(schema.Team) + ok, err := util.FindOne(ctx, GetTeamDB(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 *Team) Exists(ctx context.Context, id string) (bool, error) { + ok, err := util.Exists(ctx, GetTeamDB(ctx, a.DB).Where("id=?", id)) + return ok, errors.WithStack(err) +} + +func (a *Team) Create(ctx context.Context, item *schema.Team) error { + result := GetTeamDB(ctx, a.DB).Create(item) + return errors.WithStack(result.Error) +} + +func (a *Team) Update(ctx context.Context, item *schema.Team) error { + result := GetTeamDB(ctx, a.DB).Where("id=?", item.ID).Select("*").Omit("created_at").Updates(item) + return errors.WithStack(result.Error) +} + +func (a *Team) Delete(ctx context.Context, id string) error { + result := GetTeamDB(ctx, a.DB).Where("id=?", id).Delete(new(schema.Team)) + 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..f4d996a --- /dev/null +++ b/internal/mods/rbac/dal/user.dal.go @@ -0,0 +1,124 @@ +package dal + +import ( + "context" + + "github.com/guxuan/hailin_service/internal/mods/rbac/schema" + "github.com/guxuan/hailin_service/pkg/errors" + "github.com/guxuan/hailin_service/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..54d3489 --- /dev/null +++ b/internal/mods/rbac/dal/user_role.dal.go @@ -0,0 +1,108 @@ +package dal + +import ( + "context" + "fmt" + + "github.com/guxuan/hailin_service/internal/mods/rbac/schema" + "github.com/guxuan/hailin_service/pkg/errors" + "github.com/guxuan/hailin_service/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/dal/video.dal.go b/internal/mods/rbac/dal/video.dal.go new file mode 100644 index 0000000..aed8c74 --- /dev/null +++ b/internal/mods/rbac/dal/video.dal.go @@ -0,0 +1,88 @@ +package dal + +import ( + "context" + + "github.com/guxuan/hailin_service/internal/mods/rbac/schema" + "github.com/guxuan/hailin_service/pkg/errors" + "github.com/guxuan/hailin_service/pkg/util" + "gorm.io/gorm" +) + +func GetVideoDB(ctx context.Context, defDB *gorm.DB) *gorm.DB { + return util.GetDB(ctx, defDB).Model(new(schema.Video)) +} + +type Video struct { + DB *gorm.DB +} + +// Query roles from the database based on the provided parameters and options. +func (a *Video) Query(ctx context.Context, params schema.VideoQueryParam, opts ...schema.VideoQueryOptions) (*schema.VideoQueryResult, error) { + var opt schema.VideoQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + db := GetVideoDB(ctx, a.DB) + + if v := params.LikeTitle; len(v) > 0 { + db = db.Where("title LIKE ?", "%"+v+"%") + } + if v := params.Status; len(v) > 0 { + db = db.Where("status = ?", v) + } + + var list schema.Videos + pageResult, err := util.WrapPageQuery(ctx, db, params.PaginationParam, opt.QueryOptions, &list) + if err != nil { + return nil, errors.WithStack(err) + } + + queryResult := &schema.VideoQueryResult{ + PageResult: pageResult, + Data: list, + } + return queryResult, nil +} + +// Get the specified role from the database. +func (a *Video) Get(ctx context.Context, id string, opts ...schema.VideoQueryOptions) (*schema.Video, error) { + var opt schema.VideoQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + item := new(schema.Video) + ok, err := util.FindOne(ctx, GetVideoDB(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 *Video) Exists(ctx context.Context, id string) (bool, error) { + ok, err := util.Exists(ctx, GetVideoDB(ctx, a.DB).Where("id=?", id)) + return ok, errors.WithStack(err) +} + +// Create a new role. +func (a *Video) Create(ctx context.Context, item *schema.Video) error { + result := GetVideoDB(ctx, a.DB).Create(item) + return errors.WithStack(result.Error) +} + +// Update the specified role in the database. +func (a *Video) Update(ctx context.Context, item *schema.Video) error { + result := GetVideoDB(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 *Video) Delete(ctx context.Context, id string) error { + result := GetVideoDB(ctx, a.DB).Where("id=?", id).Delete(new(schema.Video)) + return errors.WithStack(result.Error) +} diff --git a/internal/mods/rbac/dal/website.dal.go b/internal/mods/rbac/dal/website.dal.go new file mode 100644 index 0000000..560dd3e --- /dev/null +++ b/internal/mods/rbac/dal/website.dal.go @@ -0,0 +1,46 @@ +package dal + +import ( + "context" + "github.com/guxuan/hailin_service/internal/mods/rbac/schema" + "github.com/guxuan/hailin_service/pkg/errors" + "github.com/guxuan/hailin_service/pkg/util" + "gorm.io/gorm" +) + +func GetWebSiteDB(ctx context.Context, defDB *gorm.DB) *gorm.DB { + return util.GetDB(ctx, defDB).Model(new(schema.WebSite)) +} + +type WebSite struct { + DB *gorm.DB +} + +func (a *WebSite) Get(ctx context.Context, id string) (*schema.WebSite, error) { + + item := new(schema.WebSite) + ok, err := util.FindOne(ctx, GetJobDB(ctx, a.DB).Where("id=?", id), util.QueryOptions{}, item) + if err != nil { + return nil, errors.WithStack(err) + } else if !ok { + return nil, nil + } + return item, nil +} + +func (a *WebSite) Query(ctx context.Context) (*schema.WebSite, error) { + db := GetWebSiteDB(ctx, a.DB) + var info schema.WebSite + db.First(&info) + return &info, nil +} + +func (a *WebSite) Create(ctx context.Context, item *schema.WebSite) error { + result := GetWebSiteDB(ctx, a.DB).Create(item) + return errors.WithStack(result.Error) +} + +func (a *WebSite) Update(ctx context.Context, item *schema.WebSite) error { + result := GetWebSiteDB(ctx, a.DB).Where("id=?", item.ID).Select("*").Omit("created_at").Updates(item) + 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..8cb2e8c --- /dev/null +++ b/internal/mods/rbac/main.go @@ -0,0 +1,217 @@ +package rbac + +import ( + "context" + "path/filepath" + + "github.com/gin-gonic/gin" + "github.com/guxuan/hailin_service/internal/config" + "github.com/guxuan/hailin_service/internal/mods/rbac/api" + "github.com/guxuan/hailin_service/internal/mods/rbac/schema" + "github.com/guxuan/hailin_service/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 + BannerAPI *api.Banner + ArticleAPI *api.Article + VideoAPI *api.Video + UploadAPI *api.Upload + JobAPI *api.Job + WebAPI *api.Web + WebSiteAPI *api.WebSite + TeamAPI *api.Team + MemorabiliaAPI *api.Memorabilia + ProductAPI *api.Product + + 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), + new(schema.Banner), + new(schema.Article), + new(schema.Video), + new(schema.Memorabilia), + new(schema.Job), + new(schema.JobArea), + new(schema.WebSite), + new(schema.Team), + new(schema.Product), + new(schema.ProductCategory), + ) +} + +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) + v1.POST("upload", a.UploadAPI.SaveFile) + + 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) + } + + 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) + } + + job := v1.Group("jobs") + { + job.GET("", a.JobAPI.Query) + job.GET("job_areas", a.JobAPI.QueryJobArea) + job.POST("job_areas", a.JobAPI.CreateJobArea) + job.PUT("job_areas/:id", a.JobAPI.UpdateJobArea) + job.DELETE("job_areas/:id", a.JobAPI.DeleteJobArea) + job.GET(":id", a.JobAPI.Get) + job.POST("", a.JobAPI.Create) + job.PUT(":id", a.JobAPI.Update) + job.DELETE(":id", a.JobAPI.Delete) + } + memorabilia := v1.Group("memorabilias") + { + memorabilia.GET("", a.MemorabiliaAPI.Query) + memorabilia.GET(":id", a.MemorabiliaAPI.Get) + memorabilia.POST("", a.MemorabiliaAPI.Create) + memorabilia.PUT(":id", a.MemorabiliaAPI.Update) + memorabilia.DELETE(":id", a.MemorabiliaAPI.Delete) + } + team := v1.Group("teams") + { + team.GET("", a.TeamAPI.Query) + team.GET(":id", a.TeamAPI.Get) + team.POST("", a.TeamAPI.Create) + team.PUT(":id", a.TeamAPI.Update) + team.DELETE(":id", a.TeamAPI.Delete) + } + webSite := v1.Group("web_site") + { + webSite.GET("", a.WebSiteAPI.Query) + webSite.POST("", a.WebSiteAPI.Create) + webSite.PUT(":id", a.WebSiteAPI.Update) + } + product := v1.Group("products") + { + product.GET("", a.ProductAPI.Query) + product.GET("categorys", a.ProductAPI.QueryCategory) + product.GET(":id", a.ProductAPI.Get) + product.POST("", a.ProductAPI.Create) + product.PUT(":id", a.ProductAPI.Update) + product.DELETE(":id", a.ProductAPI.Delete) + } + + article := v1.Group("articles") + { + article.GET("", a.ArticleAPI.Query) + article.GET(":id", a.ArticleAPI.Get) + article.POST("", a.ArticleAPI.Create) + article.PUT(":id", a.ArticleAPI.Update) + article.DELETE(":id", a.ArticleAPI.Delete) + } + video := v1.Group("videos") + { + video.GET("", a.VideoAPI.Query) + video.GET(":id", a.VideoAPI.Get) + video.POST("", a.VideoAPI.Create) + video.PUT(":id", a.VideoAPI.Update) + video.DELETE(":id", a.VideoAPI.Delete) + } + + 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) + } + + web := v1.Group("web") + { + web.GET("jobs", a.WebAPI.QueryJob) + web.GET("articles", a.WebAPI.QueryArticle) + + } + + 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/resp/job.go b/internal/mods/rbac/resp/job.go new file mode 100644 index 0000000..2ba6070 --- /dev/null +++ b/internal/mods/rbac/resp/job.go @@ -0,0 +1,17 @@ +package resp + +import "time" + +type JobAdminListResp struct { + AreaName string `json:"areaName"` + ID string `json:"id" ` + JobAreaID string `json:"jobAreaId"` + Title string `json:"title" ` + Introduce string `json:"introduce" ` + Duty string `json:"duty" ` + Salary string `json:"salary" ` + Sequence int `json:"sequence"` + Status string `json:"status" ` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at" ` +} diff --git a/internal/mods/rbac/resp/web.go b/internal/mods/rbac/resp/web.go new file mode 100644 index 0000000..29cd62c --- /dev/null +++ b/internal/mods/rbac/resp/web.go @@ -0,0 +1 @@ +package resp diff --git a/internal/mods/rbac/schema/article.go b/internal/mods/rbac/schema/article.go new file mode 100644 index 0000000..c7b76c0 --- /dev/null +++ b/internal/mods/rbac/schema/article.go @@ -0,0 +1,91 @@ +package schema + +import ( + "time" + + "github.com/guxuan/hailin_service/internal/config" + "github.com/guxuan/hailin_service/pkg/util" +) + +const ( + ArticleStatusDisabled = "disabled" + ArticleStatusEnabled = "enabled" +) + +var ( + ArticleOrderParams = []util.OrderByParam{ + {Field: "created_at", Direction: util.DESC}, + {Field: "sequence", Direction: util.DESC}, + } + ArticleOrderNoCreatedAtParams = []util.OrderByParam{ + {Field: "sequence", Direction: util.DESC}, + } +) + +// Article management for RBAC +type Article struct { + ID string `json:"id" gorm:"size:20;primarykey;"` // Unique ID + Title string `json:"title" gorm:"size:128;not null;index"` // Display name of banner + Img string `json:"img" gorm:"size:1024"` // Details about banner + Sequence int `json:"sequence" gorm:"index;"` // Sequence for sorting (Order by desc) + Link string `json:"link" gorm:"size:1024;"` + Content string `json:"content" gorm:"type:text;comment:详情"` + PushAt string `json:"pushAt" gorm:"size:50;index"` + Typer string `json:"type" gorm:"size:50;not null;index"` // Type of banner (banner, link) // Parent menu`` + Status string `json:"status" gorm:"size:20;index"` // Status of banner (enabled, disabled) // Child menus + CreatedAt time.Time `json:"created_at" gorm:"index;"` // Create time + UpdatedAt time.Time `json:"updated_at" gorm:"index;"` // Update time +} + +func (a *Article) TableName() string { + return config.C.FormatTableName("article") +} + +// Defining the query parameters for the `Menu` struct. +type ArticleQueryParam struct { + util.PaginationParam + LikeTitle string `form:"title"` // Display name of menu + Typer string `form:"type" bind:"required,max=50"` + Status string `form:"status"` // Status of menu (enabled, disabled) +} + +// Defining the query options for the `Menu` struct. +type ArticleQueryOptions struct { + util.QueryOptions +} + +// Defining the query result for the `Menu` struct. +type ArticleQueryResult struct { + Data Articles + PageResult *util.PaginationResult +} + +// Defining the slice of `Menu` struct. +type Articles []*Article + +func (a Articles) Len() int { + return len(a) +} + +type ArticleForm struct { + Title string `json:"title" binding:"required,max=128"` // Display name of menu + Sequence int `json:"sequence"` // Sequence for sorting (Order by desc) + Link string `json:"link" ` + Content string `json:"content"` + PushAt string `json:"pushAt"` + Typer string `json:"type" binding:"required,oneof=banner home achievement honor talent_center team news"` // Type of menu (banner, link) // Parent menu`` + Img string `json:"img" ` // Details about banner + Status string `json:"status" binding:"required,oneof=disabled enabled"` // Status of menu (enabled, disabled) +} + +func (a *ArticleForm) FillTo(article *Article) error { + article.Title = a.Title + article.PushAt = a.PushAt + article.Sequence = a.Sequence + article.Content = a.Content + article.Img = a.Img + article.Link = a.Link + article.Typer = a.Typer + article.Status = a.Status + return nil +} diff --git a/internal/mods/rbac/schema/banner.go b/internal/mods/rbac/schema/banner.go new file mode 100644 index 0000000..f2cac56 --- /dev/null +++ b/internal/mods/rbac/schema/banner.go @@ -0,0 +1,84 @@ +package schema + +import ( + "github.com/guxuan/hailin_service/internal/config" + "github.com/guxuan/hailin_service/pkg/util" + "time" +) + +const ( + BannerStatusDisabled = "disabled" + BannerStatusEnabled = "enabled" +) + +var ( + BannerOrderParams = []util.OrderByParam{ + {Field: "created_at", Direction: util.DESC}, + {Field: "sequence", Direction: util.DESC}, + } + BannerOrderNoCreatedAtParams = []util.OrderByParam{ + {Field: "sequence", Direction: util.DESC}, + } +) + +// Menu management for RBAC +type Banner struct { + ID string `json:"id" gorm:"size:20;primarykey;"` // Unique ID + Name string `json:"name" gorm:"size:128;not null;index"` // Display name of banner + Img string `json:"img" gorm:"size:1024"` // Details about banner + Sequence int `json:"sequence" gorm:"index;"` // Sequence for sorting (Order by desc) + Link string `json:"link" gorm:"size:1024;"` + Typer int `json:"type" gorm:"size:11;not null;index"` // Type of banner (banner, link) // Parent menu`` + Status string `json:"status" gorm:"size:20;index"` // Status of banner (enabled, disabled) // Child menus + CreatedAt time.Time `json:"created_at" gorm:"index;"` // Create time + UpdatedAt time.Time `json:"updated_at" gorm:"index;"` // Update time +} + +func (a *Banner) TableName() string { + return config.C.FormatTableName("banner") +} + +// Defining the query parameters for the `Menu` struct. +type BannerQueryParam struct { + util.PaginationParam + LikeName string `form:"name"` // Display name of menu + Typer int `form:"type" bind:"required,max=11"` + Status string `form:"status"` // Status of menu (enabled, disabled) +} + +// Defining the query options for the `Menu` struct. +type BannerQueryOptions struct { + util.QueryOptions +} + +// Defining the query result for the `Menu` struct. +type BannerQueryResult struct { + Data Banners + PageResult *util.PaginationResult +} + +// Defining the slice of `Menu` struct. +type Banners []*Banner + +func (a Banners) Len() int { + return len(a) +} + +type BannerForm struct { + Name string `json:"name" binding:"required,max=128"` // Display name of menu + Sequence int `json:"sequence"` // Sequence for sorting (Order by desc) + Link string `json:"link" ` + Typer int `json:"type" binding:"required"` // Type of menu (banner, link) // Parent menu`` + Img string `json:"img" gorm:"size:1024"` // Details about banner + Status string `json:"status" binding:"required,oneof=disabled enabled"` // Status of menu (enabled, disabled) +} + +func (a *BannerForm) FillTo(banner *Banner) error { + banner.Name = a.Name + banner.Sequence = a.Sequence + banner.Img = a.Img + banner.Link = a.Link + banner.Typer = a.Typer + banner.Status = a.Status + return nil +} diff --git a/internal/mods/rbac/schema/job.go b/internal/mods/rbac/schema/job.go new file mode 100644 index 0000000..893ea31 --- /dev/null +++ b/internal/mods/rbac/schema/job.go @@ -0,0 +1,108 @@ +package schema + +import ( + "time" + + "github.com/guxuan/hailin_service/internal/config" + "github.com/guxuan/hailin_service/pkg/util" +) + +const ( + JobStatusDisabled = "disabled" + JobStatusEnabled = "enabled" +) + +var ( + JobOrderParams = []util.OrderByParam{ + {Field: "created_at", Direction: util.DESC}, + {Field: "sequence", Direction: util.DESC}, + } + JobOrderNoCreatedAtParams = []util.OrderByParam{ + {Field: "sequence", Direction: util.DESC}, + } +) + +// Article management for RBAC +type Job struct { + ID string `json:"id" gorm:"size:20;primarykey;"` //主键 + JobAreaID string `json:"jobAreaId" gorm:"size:20;not null;index"` //职位地区id + Title string `json:"title" gorm:"size:128;not null;index"` //标题 + Introduce string `json:"introduce" gorm:"type:text"` //要求 + Duty string `json:"duty" gorm:"type:text"` //职责 + Salary string `json:"salary" gorm:"size:50"` //薪资 + Sequence int `json:"sequence" gorm:"index;"` //排序 + Status string `json:"status" gorm:"size:20;index"` //状态 + CreatedAt time.Time `json:"created_at" gorm:"index;"` + UpdatedAt time.Time `json:"updated_at" gorm:"index;"` +} +type JobArea struct { + ID string `json:"id" gorm:"size:20;primarykey;"` // Unique ID + Name string `json:"name" gorm:"size:128;not null;index"` // 区域名字 + Status string `json:"status" gorm:"size:20;index"` // Status of banner (enabled, disabled) // Child menus + CreatedAt time.Time `json:"created_at" gorm:"index;"` // Create time + UpdatedAt time.Time `json:"updated_at" gorm:"index;"` // Update time +} + +type WebJobData struct { + JobAreaTitle string `json:"jobAreaTitle"` + JobList Jobs `json:"jobList"` +} + +func (a *Job) TableName() string { + return config.C.FormatTableName("job") +} +func (a *JobArea) TableName() string { + return config.C.FormatTableName("job_area") +} + +// Defining the query parameters for the `Menu` struct. +type JobQueryParam struct { + util.PaginationParam + JobAreaID string `form:"jobAreaId"` // Display name of menu + LikeTitle string `form:"title"` // Display name of menu + Status string `form:"status"` // Status of menu (enabled, disabled) +} + +// Defining the query options for the `Menu` struct. +type JobQueryOptions struct { + util.QueryOptions +} + +// Defining the query result for the `Menu` struct. +type JobQueryResult struct { + Data Jobs + PageResult *util.PaginationResult +} + +// Defining the slice of `Menu` struct. +type Jobs []*Job + +func (a Jobs) Len() int { + return len(a) +} + +type JobForm struct { + Title string `json:"title" binding:"required,max=128"` // Display name of menu + Sequence int `json:"sequence"` // Sequence for sorting (Order by desc) + JobAreaId string `json:"jobAreaId" ` + Introduce string `json:"introduce"` + Duty string `json:"duty" ` + Salary string `json:"salary"` // Details about banner + Status string `json:"status" binding:"required,oneof=disabled enabled"` // Status of menu (enabled, disabled) +} + +type JobAreaForm struct { + Name string `json:"name" binding:"required,max=128"` // Display name of menu // Details about banner + Status string `json:"status" binding:"required,oneof=disabled enabled"` +} + +func (a *JobForm) FillTo(job *Job) error { + job.Title = a.Title + job.JobAreaID = a.JobAreaId + job.Sequence = a.Sequence + job.Introduce = a.Introduce + job.Duty = a.Duty + job.Salary = a.Salary + job.Status = a.Status + return nil +} diff --git a/internal/mods/rbac/schema/logger.go b/internal/mods/rbac/schema/logger.go new file mode 100644 index 0000000..c20d431 --- /dev/null +++ b/internal/mods/rbac/schema/logger.go @@ -0,0 +1,53 @@ +package schema + +import ( + "time" + + "github.com/guxuan/hailin_service/internal/config" + "github.com/guxuan/hailin_service/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..ed8d3dc --- /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" ` // Captcha verify id + CaptchaCode string `json:"captcha_code" ` // 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/memorabilia.go b/internal/mods/rbac/schema/memorabilia.go new file mode 100644 index 0000000..c99c274 --- /dev/null +++ b/internal/mods/rbac/schema/memorabilia.go @@ -0,0 +1,79 @@ +package schema + +import ( + "time" + + "github.com/guxuan/hailin_service/internal/config" + "github.com/guxuan/hailin_service/pkg/util" +) + +const ( + MemorabiliaStatusDisabled = "disabled" + MemorabiliaStatusEnabled = "enabled" +) + +var ( + MemorabiliaOrderParams = []util.OrderByParam{ + {Field: "sequence", Direction: util.DESC}, + } +) + +// Article management for RBAC +type Memorabilia struct { + ID string `json:"id" gorm:"size:20;primarykey;"` // Unique ID + Title string `json:"title" gorm:"size:128;not null;index"` // Display name of banner + Sequence int `json:"sequence" gorm:"index;default:0"` + Year int `json:"year" gorm:"size:10;not null;index"` // Type of banner (banner, link) // Parent menu`` + Month int `json:"month" gorm:"size:10;not null;index"` // Type of banner (banner, link) // Parent menu`` + Status string `json:"status" gorm:"size:20;index"` // Status of banner (enabled, disabled) // Child menus + CreatedAt time.Time `json:"created_at" gorm:"index;"` // Create time + UpdatedAt time.Time `json:"updated_at" gorm:"index;"` // Update time +} + +func (a *Memorabilia) TableName() string { + return config.C.FormatTableName("memorabilia") +} + +// Defining the query parameters for the `Menu` struct. +type MemorabiliaQueryParam struct { + util.PaginationParam + LikeTitle string `form:"title"` // Display name of menu + Month int `form:"month"` + Year int `form:"year"` + Status string `form:"status"` // Status of menu (enabled, disabled) +} + +// Defining the query options for the `Menu` struct. +type MemorabiliaQueryOptions struct { + util.QueryOptions +} + +// Defining the query result for the `Menu` struct. +type MemorabiliaQueryResult struct { + Data Memorabilias + PageResult *util.PaginationResult +} + +// Defining the slice of `Menu` struct. +type Memorabilias []*Memorabilia + +func (a Memorabilias) Len() int { + return len(a) +} + +type MemorabiliaForm struct { + Title string `json:"title" binding:"required,max=128"` // Display name of menu + Sequence int `json:"sequence"` // Sequence for sorting (Order by desc) + Year int `json:"year"` + Month int `json:"month"` + Status string `json:"status" binding:"required,oneof=disabled enabled"` // Status of menu (enabled, disabled) +} + +func (a *MemorabiliaForm) FillTo(memorabilia *Memorabilia) error { + memorabilia.Title = a.Title + memorabilia.Month = a.Month + memorabilia.Sequence = a.Sequence + memorabilia.Year = a.Year + memorabilia.Status = a.Status + return nil +} diff --git a/internal/mods/rbac/schema/menu.go b/internal/mods/rbac/schema/menu.go new file mode 100644 index 0000000..90c3e82 --- /dev/null +++ b/internal/mods/rbac/schema/menu.go @@ -0,0 +1,178 @@ +package schema + +import ( + "encoding/json" + "strings" + "time" + + "github.com/guxuan/hailin_service/internal/config" + "github.com/guxuan/hailin_service/pkg/errors" + "github.com/guxuan/hailin_service/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..0244fca --- /dev/null +++ b/internal/mods/rbac/schema/menu_resource.go @@ -0,0 +1,56 @@ +package schema + +import ( + "time" + + "github.com/guxuan/hailin_service/internal/config" + "github.com/guxuan/hailin_service/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/product.go b/internal/mods/rbac/schema/product.go new file mode 100644 index 0000000..49609b8 --- /dev/null +++ b/internal/mods/rbac/schema/product.go @@ -0,0 +1,114 @@ +package schema + +import ( + "time" + + "github.com/guxuan/hailin_service/internal/config" + "github.com/guxuan/hailin_service/pkg/util" +) + +const ( + ProductStatusDisabled = "disabled" + ProudctStatusEnabled = "enabled" +) + +var ( + ProductOrderParams = []util.OrderByParam{ + {Field: "created_at", Direction: util.DESC}, + {Field: "sequence", Direction: util.DESC}, + } + ProductOrderNoCreatedAtParams = []util.OrderByParam{ + {Field: "sequence", Direction: util.DESC}, + } +) + +// Article management for RBAC +type Product struct { + ID string `json:"id" gorm:"size:20;primarykey;"` + CategoryID uint `json:"categoryID" gorm:"index;"` + Title string `json:"title" gorm:"size:128;not null;index"` // Display name of banner + Images []string `json:"images" gorm:"serializer:json"` + Code string `json:"code" gorm:"size:50"` // Details about banner + Sequence int `json:"sequence" gorm:"index;"` // Sequence for sorting (Order by desc) + Compose string `json:"compose" gorm:"size:1024;"` + Target string `json:"target" gorm:"size:1024"` + Standard *[]ProductStandard `json:"standard" gorm:"serializer:json"` + Feature string `json:"feature" gorm:"size:2048"` // Type of banner (banner, link) // Parent menu`` + Status string `json:"status" gorm:"size:20;index"` // Status of banner (enabled, disabled) // Child menus + CreatedAt time.Time `json:"created_at" gorm:"index;"` // Create time + UpdatedAt time.Time `json:"updated_at" gorm:"index;"` // Update time +} +type ProductStandard struct { + Label string + Value string +} + +type ProductCategory struct { + ID uint `json:"id" gorm:"primaryKey"` + Label string `json:"label" gorm:"size:128;not null;index"` // Display name of banner + ParentID uint `json:"parentID" gorm:"size:50;not null;index"` // Type of banner (banner, link) // Parent menu`` + Status string `json:"status" gorm:"size:20;index"` // Status of banner (enabled, disabled) // Child menus + CreatedAt time.Time `json:"created_at" gorm:"index;"` // Create time + UpdatedAt time.Time `json:"updated_at" gorm:"index;"` // Update time +} + +func (a *Product) TableName() string { + return config.C.FormatTableName("product") +} +func (a *ProductCategory) TableName() string { + return config.C.FormatTableName("product_category") +} + +// Defining the query parameters for the `Menu` struct. +type ProductQueryParam struct { + util.PaginationParam + LikeTitle string `form:"title"` // Display name of menu + CategoryID uint `form:"categoryId"` + Code string `form:"code" ` + Status string `form:"status"` // Status of menu (enabled, disabled) +} + +// Defining the query options for the `Menu` struct. +type ProductQueryOptions struct { + util.QueryOptions +} + +// Defining the query result for the `Menu` struct. +type ProductQueryResult struct { + Data Products + PageResult *util.PaginationResult +} + +// Defining the slice of `Menu` struct. +type Products []*Product + +func (a Products) Len() int { + return len(a) +} + +type ProductForm struct { + CategoryID uint `json:"categoryID" binding:"required,max=11"` + Title string `json:"title" binding:"required,max=128"` // Display name of menu + Images []string `json:"images"` + Sequence int `json:"sequence"` // Sequence for sorting (Order by desc) + Code string `json:"code" ` + Compose string `json:"compose"` + Feature string `json:"feature"` + Target string `json:"target" ` // Type of menu (banner, link) // Parent menu`` + Standard *[]ProductStandard `json:"standard" ` // Details about banner + Status string `json:"status" binding:"required,oneof=disabled enabled"` +} + +func (a *ProductForm) FillTo(product *Product) error { + product.Title = a.Title + product.Compose = a.Compose + product.Sequence = a.Sequence + product.Code = a.Code + product.CategoryID = a.CategoryID + product.Standard = a.Standard + product.Images = a.Images + product.Feature = a.Feature + product.Target = a.Target + product.Status = a.Status + return nil +} diff --git a/internal/mods/rbac/schema/role.go b/internal/mods/rbac/schema/role.go new file mode 100644 index 0000000..4c1b020 --- /dev/null +++ b/internal/mods/rbac/schema/role.go @@ -0,0 +1,80 @@ +package schema + +import ( + "time" + + "github.com/guxuan/hailin_service/internal/config" + "github.com/guxuan/hailin_service/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..5d3318d --- /dev/null +++ b/internal/mods/rbac/schema/role_menu.go @@ -0,0 +1,54 @@ +package schema + +import ( + "time" + + "github.com/guxuan/hailin_service/internal/config" + "github.com/guxuan/hailin_service/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/team.go b/internal/mods/rbac/schema/team.go new file mode 100644 index 0000000..eead867 --- /dev/null +++ b/internal/mods/rbac/schema/team.go @@ -0,0 +1,84 @@ +package schema + +import ( + "time" + + "github.com/guxuan/hailin_service/internal/config" + "github.com/guxuan/hailin_service/pkg/util" +) + +const ( + TeamStatusDisabled = "disabled" + TeamStatusEnabled = "enabled" +) + +var ( + TeamOrderParams = []util.OrderByParam{ + {Field: "created_at", Direction: util.DESC}, + {Field: "sequence", Direction: util.DESC}, + } + TeamOrderNoCreatedAtParams = []util.OrderByParam{ + {Field: "sequence", Direction: util.DESC}, + } +) + +// Article management for RBAC +type Team struct { + ID string `json:"id" gorm:"size:20;primarykey;"` // Unique ID + Name string `json:"name" gorm:"size:128;not null;index"` // Display name of banner + Img string `json:"img" gorm:"size:1024"` // Details about banner + Sequence int `json:"sequence" gorm:"index;"` // Sequence for sorting (Order by desc) + Area string `json:"area" gorm:"size:1024"` + Rank *[]string `json:"rank" gorm:"serializer:json"` // Type of banner (banner, link) // Parent menu`` + Status string `json:"status" gorm:"size:20;index"` // Status of banner (enabled, disabled) // Child menus + CreatedAt time.Time `json:"created_at" gorm:"index;"` // Create time + UpdatedAt time.Time `json:"updated_at" gorm:"index;"` // Update time +} + +func (a *Team) TableName() string { + return config.C.FormatTableName("team") +} + +// Defining the query parameters for the `Menu` struct. +type TeamQueryParam struct { + util.PaginationParam + LikeName string `form:"name"` // Display name of menu + Status string `form:"status"` // Status of menu (enabled, disabled) +} + +// Defining the query options for the `Menu` struct. +type TeamQueryOptions struct { + util.QueryOptions +} + +// Defining the query result for the `Menu` struct. +type TeamQueryResult struct { + Data Teams + PageResult *util.PaginationResult +} + +// Defining the slice of `Menu` struct. +type Teams []*Team + +func (a Teams) Len() int { + return len(a) +} + +type TeamForm struct { + Name string `json:"name" binding:"required,max=128"` // Display name of menu + Sequence int `json:"sequence"` // Sequence for sorting (Order by desc) + Area string `json:"area" ` + Rank *[]string `json:"rank"` // Type of banner (ban + Img string `json:"img" ` // Details about banner + Status string `json:"status" binding:"required,oneof=disabled enabled"` // Status of menu (enabled, disabled) +} + +func (a *TeamForm) FillTo(team *Team) error { + team.Name = a.Name + team.Area = a.Area + team.Sequence = a.Sequence + team.Img = a.Img + team.Rank = a.Rank + team.Status = a.Status + return nil +} diff --git a/internal/mods/rbac/schema/user.go b/internal/mods/rbac/schema/user.go new file mode 100644 index 0000000..06b1abc --- /dev/null +++ b/internal/mods/rbac/schema/user.go @@ -0,0 +1,105 @@ +package schema + +import ( + "time" + + "github.com/guxuan/hailin_service/internal/config" + "github.com/guxuan/hailin_service/pkg/crypto/hash" + "github.com/guxuan/hailin_service/pkg/errors" + "github.com/guxuan/hailin_service/pkg/util" + "github.com/go-playground/validator/v10" +) + +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;"` // Email 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..bcae41f --- /dev/null +++ b/internal/mods/rbac/schema/user_role.go @@ -0,0 +1,74 @@ +package schema + +import ( + "time" + + "github.com/guxuan/hailin_service/internal/config" + "github.com/guxuan/hailin_service/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/schema/video.go b/internal/mods/rbac/schema/video.go new file mode 100644 index 0000000..fd316a7 --- /dev/null +++ b/internal/mods/rbac/schema/video.go @@ -0,0 +1,93 @@ +package schema + +import ( + "time" + + "github.com/guxuan/hailin_service/internal/config" + "github.com/guxuan/hailin_service/pkg/util" +) + +const ( + VideoStatusDisabled = "disabled" + VideoStatusEnabled = "enabled" +) + +var ( + VideoOrderParams = []util.OrderByParam{ + {Field: "created_at", Direction: util.DESC}, + {Field: "sequence", Direction: util.DESC}, + } + VideoOrderNoCreatedAtParams = []util.OrderByParam{ + {Field: "sequence", Direction: util.DESC}, + } +) + +// Article management for RBAC +type Video struct { + ID string `json:"id" gorm:"size:20;primarykey;"` // Unique ID + Title string `json:"title" gorm:"size:128;not null;index"` // Display name of banner + Subheading string `json:"subheading" gorm:"size:128;index"` // Display name of banner + SmallImg string `json:"smallImg" gorm:"size:1024"` + VideoUrl string `json:"videoUrl" gorm:"size:1024"` + + FullImg string `json:"fullImg" gorm:"size:1024"` // Details about banner + Sequence int `json:"sequence" gorm:"index;"` // Sequence for sorting (Order by desc) + Link string `json:"link" gorm:"size:1024;"` + PushAt string `json:"pushAt" gorm:"size:50;index"` + Status string `json:"status" gorm:"size:20;index"` // Status of banner (enabled, disabled) // Child menus + CreatedAt time.Time `json:"created_at" gorm:"index;"` // Create time + UpdatedAt time.Time `json:"updated_at" gorm:"index;"` // Update time +} + +func (a *Video) TableName() string { + return config.C.FormatTableName("video") +} + +// Defining the query parameters for the `Menu` struct. +type VideoQueryParam struct { + util.PaginationParam + LikeTitle string `form:"title"` // Display name of menu + Typer string `form:"type" bind:"required,max=50"` + Status string `form:"status"` // Status of menu (enabled, disabled) +} + +// Defining the query options for the `Menu` struct. +type VideoQueryOptions struct { + util.QueryOptions +} + +// Defining the query result for the `Menu` struct. +type VideoQueryResult struct { + Data Videos + PageResult *util.PaginationResult +} + +// Defining the slice of `Menu` struct. +type Videos []*Video + +func (a Videos) Len() int { + return len(a) +} + +type VideoForm struct { + Title string `json:"title" binding:"required,max=128"` // Display name of menu + Sequence int `json:"sequence"` // Sequence for sorting (Order by desc) + Link string `json:"link" ` + PushAt string `json:"pushAt"` + VideoUrl string `json:"videoUrl"` + SmallImg string `json:"smallImg" ` + FullImg string `json:"fullImg" ` + Status string `json:"status" binding:"required,oneof=disabled enabled"` // Status of menu (enabled, disabled) +} + +func (a *VideoForm) FillTo(video *Video) error { + video.Title = a.Title + video.PushAt = a.PushAt + video.VideoUrl = a.VideoUrl + video.Sequence = a.Sequence + video.SmallImg = a.SmallImg + video.FullImg = a.FullImg + video.Link = a.Link + video.Status = a.Status + return nil +} diff --git a/internal/mods/rbac/schema/website.go b/internal/mods/rbac/schema/website.go new file mode 100644 index 0000000..8d4573e --- /dev/null +++ b/internal/mods/rbac/schema/website.go @@ -0,0 +1,59 @@ +package schema + +import ( + "time" + + "github.com/guxuan/hailin_service/internal/config" +) + +type WebSite struct { + ID string `json:"id" gorm:"size:20;primarykey;"` + ReportNum string `json:"reportNum" gorm:"size:20"` + ReportContent string `json:"reportContent" gorm:"size:2048"` + ReportImage string `json:"reportImage" gorm:"size:1024"` + Address string `json:"address" gorm:"size:1024"` + Phone string `json:"phone" gorm:"size:50"` + Email string `json:"email" gorm:"size:50"` + SocialMedia *[]SocialMedia `json:"socialMedia" gorm:"serializer:json"` + Lon float64 `json:"lon" gorm:""` + Lat float64 `json:"lat" gorm:""` + Affirm string `json:"affirm" gorm:"type:text"` + CreatedAt time.Time `json:"created_at" gorm:"index;"` + UpdatedAt time.Time `json:"updated_at" gorm:"index;"` +} +type SocialMedia struct { + Name string `json:"name" gorm:"size:50"` + Image string `json:"image" gorm:"size:1024"` + Link string `json:"link" gorm:"size:1024"` +} + +func (a *WebSite) TableName() string { + return config.C.FormatTableName("web_site") +} + +type WebSiteForm struct { + ReportNum string `json:"reportNum"` + ReportContent string `json:"reportContent"` + ReportImage string `json:"reportImage"` + Address string `json:"address"` + Phone string `json:"phone" ` + Email string `json:"email" ` + SocialMedia *[]SocialMedia `json:"socialMedia" ` + Lon float64 `json:"lon" ` + Lat float64 `json:"lat" ` + Affirm string `json:"affirm"` +} + +func (a *WebSiteForm) FillTo(webSite *WebSite) error { + webSite.ReportNum = a.ReportNum + webSite.ReportContent = a.ReportContent + webSite.ReportImage = a.ReportImage + webSite.Address = a.Address + webSite.Phone = a.Phone + webSite.Email = a.Email + webSite.SocialMedia = a.SocialMedia + webSite.Lon = a.Lon + webSite.Lat = a.Lat + webSite.Affirm = a.Affirm + return nil +} diff --git a/internal/mods/rbac/wire.go b/internal/mods/rbac/wire.go new file mode 100644 index 0000000..0b47321 --- /dev/null +++ b/internal/mods/rbac/wire.go @@ -0,0 +1,66 @@ +package rbac + +import ( + "github.com/google/wire" + "github.com/guxuan/hailin_service/internal/mods/rbac/api" + "github.com/guxuan/hailin_service/internal/mods/rbac/biz" + "github.com/guxuan/hailin_service/internal/mods/rbac/dal" +) + +// Collection of wire providers +var Set = wire.NewSet( + wire.Struct(new(RBAC), "*"), + wire.Struct(new(Casbinx), "*"), + wire.Struct(new(api.Upload), "*"), + wire.Struct(new(biz.Upload), "*"), + wire.Struct(new(api.Web), "*"), + wire.Struct(new(dal.Article), "*"), + wire.Struct(new(biz.Article), "*"), + wire.Struct(new(api.Article), "*"), + wire.Struct(new(dal.Video), "*"), + wire.Struct(new(biz.Video), "*"), + wire.Struct(new(api.Video), "*"), + wire.Struct(new(dal.Menu), "*"), + wire.Struct(new(biz.Menu), "*"), + wire.Struct(new(api.Menu), "*"), + wire.Struct(new(dal.Banner), "*"), + wire.Struct(new(biz.Banner), "*"), + wire.Struct(new(api.Banner), "*"), + 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), "*"), + + wire.Struct(new(dal.Job), "*"), + wire.Struct(new(dal.JobArea), "*"), + wire.Struct(new(biz.Job), "*"), + wire.Struct(new(api.Job), "*"), + + wire.Struct(new(dal.Memorabilia), "*"), + wire.Struct(new(biz.Memorabilia), "*"), + wire.Struct(new(api.Memorabilia), "*"), + + wire.Struct(new(dal.Product), "*"), + wire.Struct(new(dal.ProductCategory), "*"), + + wire.Struct(new(biz.Product), "*"), + wire.Struct(new(api.Product), "*"), + + wire.Struct(new(dal.Team), "*"), + wire.Struct(new(biz.Team), "*"), + wire.Struct(new(api.Team), "*"), + + wire.Struct(new(dal.WebSite), "*"), + wire.Struct(new(biz.WebSite), "*"), + wire.Struct(new(api.WebSite), "*"), +) diff --git a/internal/swagger/docs.go b/internal/swagger/docs.go new file mode 100644 index 0000000..0aecb9e --- /dev/null +++ b/internal/swagger/docs.go @@ -0,0 +1,5436 @@ +// 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/articles": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "ArticleAPI" + ], + "summary": "Query Article 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": "name of Article", + "name": "name", + "in": "query" + }, + { + "type": "string", + "description": "Status of banner (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.Article" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "ArticleAPI" + ], + "summary": "Create Article record", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.ArticleForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.Article" + } + } + } + ] + } + }, + "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/articles/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "ArticleAPI" + ], + "summary": "Get Article 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.Article" + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "ArticleAPI" + ], + "summary": "Update Article 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.ArticleForm" + } + } + ], + "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": [ + "ArticleAPI" + ], + "summary": "Delete Article 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": [ + "BannerAPI" + ], + "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 + }, + { + "type": "string", + "description": "Name of banner", + "name": "name", + "in": "query" + }, + { + "type": "string", + "description": "Status of banner (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.Banner" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "BannerAPI" + ], + "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": [ + "BannerAPI" + ], + "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": [ + "BannerAPI" + ], + "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": [ + "BannerAPI" + ], + "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/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/jobs": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "职位模块" + ], + "summary": "Query Job 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": "title of Job", + "name": "title", + "in": "query" + }, + { + "type": "string", + "description": "jobAreaId of Job", + "name": "jobAreaId", + "in": "query" + }, + { + "type": "string", + "description": "Status of banner (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.Job" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "职位模块" + ], + "summary": "Create Job record", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.JobForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.Job" + } + } + } + ] + } + }, + "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/jobs/job_areas": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "职位模块" + ], + "summary": "Query JobArea 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": "Status of banner (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.JobArea" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "职位模块" + ], + "summary": "Create JobArea record", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.JobAreaForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.JobAreaForm" + } + } + } + ] + } + }, + "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/jobs/job_areas/{id}": { + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "职位模块" + ], + "summary": "Update JobArea 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.JobAreaForm" + } + } + ], + "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 JobArea 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/jobs/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "职位模块" + ], + "summary": "Get Job 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.Job" + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "职位模块" + ], + "summary": "Update Job 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.JobForm" + } + } + ], + "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 Job 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/memorabilias": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "发展历程模块" + ], + "summary": "Query Memorabilia 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": "title of Memorabilia", + "name": "title", + "in": "query" + }, + { + "type": "string", + "description": "month of Memorabilia", + "name": "month", + "in": "query" + }, + { + "type": "string", + "description": "year of Memorabilia", + "name": "year", + "in": "query" + }, + { + "type": "string", + "description": "Status of banner (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.Memorabilia" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "发展历程模块" + ], + "summary": "Create Article record", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.MemorabiliaForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.Memorabilia" + } + } + } + ] + } + }, + "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/memorabilias/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "发展历程模块" + ], + "summary": "Get Memorabilia 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.Memorabilia" + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "发展历程模块" + ], + "summary": "Update Article 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.MemorabiliaForm" + } + } + ], + "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 Article 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/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/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 + }, + { + "type": "string", + "description": "title of Product", + "name": "title", + "in": "query" + }, + { + "type": "integer", + "description": "categoryId of Product", + "name": "categoryId", + "in": "query" + }, + { + "type": "string", + "description": "code of Product", + "name": "code", + "in": "query" + }, + { + "type": "string", + "description": "Status of Product (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.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/categorys": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "产品模块" + ], + "summary": "Query ProductCategory list", + "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" + } + } + } + } + }, + "/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/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/teams": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "团队模块" + ], + "summary": "Query Team 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": "name of Team", + "name": "name", + "in": "query" + }, + { + "type": "string", + "description": "Status of banner (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.Team" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "团队模块" + ], + "summary": "Create Article record", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.TeamForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.Team" + } + } + } + ] + } + }, + "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/teams/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "团队模块" + ], + "summary": "Get Team 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.Team" + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "团队模块" + ], + "summary": "Update Article 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.TeamForm" + } + } + ], + "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 Article 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" + } + } + } + } + }, + "/api/v1/videos": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "VideoAPI" + ], + "summary": "Query Video 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": "title of Video", + "name": "title", + "in": "query" + }, + { + "type": "string", + "description": "Status of banner (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.Video" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "VideoAPI" + ], + "summary": "Create banner record", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.VideoForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.Video" + } + } + } + ] + } + }, + "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/videos/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "VideoAPI" + ], + "summary": "Get Video 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.Video" + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "VideoAPI" + ], + "summary": "Update Video 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": [ + "VideoAPI" + ], + "summary": "Delete Video 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/web/articles": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "网页模块" + ], + "summary": "获取公司动态", + "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": "jobAreaId of Job", + "name": "jobAreaId", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.Article" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/web/jobs": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "网页模块" + ], + "summary": "获取职位列表", + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.WebJobData" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/web_site": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "网页设置" + ], + "summary": "Query WebSite info", + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.WebSite" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "ArticleAPI" + ], + "summary": "Create WebSite record", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.WebSiteForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.WebSite" + } + } + } + ] + } + }, + "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/web_site/{id}": { + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "网页设置" + ], + "summary": "Update WebSite 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.WebSiteForm" + } + } + ], + "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" + } + } + } + } + } + }, + "definitions": { + "errors.Error": { + "type": "object", + "properties": { + "code": { + "type": "integer" + }, + "detail": { + "type": "string" + }, + "id": { + "type": "string" + }, + "status": { + "type": "string" + } + } + }, + "schema.Article": { + "type": "object", + "properties": { + "content": { + "type": "string" + }, + "created_at": { + "description": "Create time", + "type": "string" + }, + "id": { + "description": "Unique ID", + "type": "string" + }, + "img": { + "description": "Details about banner", + "type": "string" + }, + "link": { + "type": "string" + }, + "pushAt": { + "type": "string" + }, + "sequence": { + "description": "Sequence for sorting (Order by desc)", + "type": "integer" + }, + "status": { + "description": "Status of banner (enabled, disabled) // Child menus", + "type": "string" + }, + "title": { + "description": "Display name of banner", + "type": "string" + }, + "type": { + "description": "Type of banner (banner, link) // Parent menu` + "`" + `` + "`" + `", + "type": "string" + }, + "updated_at": { + "description": "Update time", + "type": "string" + } + } + }, + "schema.ArticleForm": { + "type": "object", + "required": [ + "status", + "title", + "type" + ], + "properties": { + "content": { + "type": "string" + }, + "img": { + "description": "Details about banner", + "type": "string" + }, + "link": { + "type": "string" + }, + "pushAt": { + "type": "string" + }, + "sequence": { + "description": "Sequence for sorting (Order by desc)", + "type": "integer" + }, + "status": { + "description": "Status of menu (enabled, disabled)", + "type": "string", + "enum": [ + "disabled", + "enabled" + ] + }, + "title": { + "description": "Display name of menu", + "type": "string", + "maxLength": 128 + }, + "type": { + "description": "Type of menu (banner, link) // Parent menu` + "`" + `` + "`" + `", + "type": "string", + "enum": [ + "banner", + "home", + "achievement", + "honor", + "talent_center", + "team", + "news" + ] + } + } + }, + "schema.Banner": { + "type": "object", + "properties": { + "created_at": { + "description": "Create time", + "type": "string" + }, + "id": { + "description": "Unique ID", + "type": "string" + }, + "img": { + "description": "Details about banner", + "type": "string" + }, + "link": { + "type": "string" + }, + "name": { + "description": "Display name of banner", + "type": "string" + }, + "sequence": { + "description": "Sequence for sorting (Order by desc)", + "type": "integer" + }, + "status": { + "description": "Status of banner (enabled, disabled) // Child menus", + "type": "string" + }, + "type": { + "description": "Type of banner (banner, link) // Parent menu` + "`" + `` + "`" + `", + "type": "integer" + }, + "updated_at": { + "description": "Update time", + "type": "string" + } + } + }, + "schema.BannerForm": { + "type": "object", + "required": [ + "name", + "status", + "type" + ], + "properties": { + "img": { + "description": "Details about banner", + "type": "string" + }, + "link": { + "type": "string" + }, + "name": { + "description": "Display name of menu", + "type": "string", + "maxLength": 128 + }, + "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 (banner, link) // Parent menu` + "`" + `` + "`" + `", + "type": "integer" + } + } + }, + "schema.Captcha": { + "type": "object", + "properties": { + "captcha_id": { + "description": "Captcha ID", + "type": "string" + } + } + }, + "schema.Job": { + "type": "object", + "properties": { + "created_at": { + "type": "string" + }, + "duty": { + "description": "职责", + "type": "array", + "items": { + "type": "string" + } + }, + "id": { + "description": "主键", + "type": "string" + }, + "introduce": { + "description": "要求", + "type": "string" + }, + "jobAreaId": { + "description": "职位地区id", + "type": "string" + }, + "salary": { + "description": "薪资", + "type": "string" + }, + "sequence": { + "description": "排序", + "type": "integer" + }, + "status": { + "description": "状态", + "type": "string" + }, + "title": { + "description": "标题", + "type": "string" + }, + "updated_at": { + "type": "string" + } + } + }, + "schema.JobArea": { + "type": "object", + "properties": { + "created_at": { + "description": "Create time", + "type": "string" + }, + "id": { + "description": "Unique ID", + "type": "string" + }, + "name": { + "description": "区域名字", + "type": "string" + }, + "status": { + "description": "Status of banner (enabled, disabled) // Child menus", + "type": "string" + }, + "updated_at": { + "description": "Update time", + "type": "string" + } + } + }, + "schema.JobAreaForm": { + "type": "object", + "required": [ + "name", + "status" + ], + "properties": { + "name": { + "description": "Display name of menu // Details about banner", + "type": "string", + "maxLength": 128 + }, + "status": { + "type": "string", + "enum": [ + "disabled", + "enabled" + ] + } + } + }, + "schema.JobForm": { + "type": "object", + "required": [ + "status", + "title" + ], + "properties": { + "duty": { + "type": "array", + "items": { + "type": "string" + } + }, + "introduce": { + "type": "string" + }, + "jobAreaId": { + "type": "string" + }, + "salary": { + "description": "Details about banner", + "type": "string" + }, + "sequence": { + "description": "Sequence for sorting (Order by desc)", + "type": "integer" + }, + "status": { + "description": "Status of menu (enabled, disabled)", + "type": "string", + "enum": [ + "disabled", + "enabled" + ] + }, + "title": { + "description": "Display name of menu", + "type": "string", + "maxLength": 128 + } + } + }, + "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": [ + "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.Memorabilia": { + "type": "object", + "properties": { + "created_at": { + "description": "Create time", + "type": "string" + }, + "id": { + "description": "Unique ID", + "type": "string" + }, + "month": { + "description": "Type of banner (banner, link) // Parent menu` + "`" + `` + "`" + `", + "type": "integer" + }, + "sequence": { + "type": "integer" + }, + "status": { + "description": "Status of banner (enabled, disabled) // Child menus", + "type": "string" + }, + "title": { + "description": "Display name of banner", + "type": "string" + }, + "updated_at": { + "description": "Update time", + "type": "string" + }, + "year": { + "description": "Type of banner (banner, link) // Parent menu` + "`" + `` + "`" + `", + "type": "integer" + } + } + }, + "schema.MemorabiliaForm": { + "type": "object", + "required": [ + "status", + "title" + ], + "properties": { + "month": { + "type": "integer" + }, + "sequence": { + "description": "Sequence for sorting (Order by desc)", + "type": "integer" + }, + "status": { + "description": "Status of menu (enabled, disabled)", + "type": "string", + "enum": [ + "disabled", + "enabled" + ] + }, + "title": { + "description": "Display name of menu", + "type": "string", + "maxLength": 128 + }, + "year": { + "type": "integer" + } + } + }, + "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.Product": { + "type": "object", + "properties": { + "categoryID": { + "type": "integer" + }, + "code": { + "description": "Details about banner", + "type": "string" + }, + "compose": { + "type": "string" + }, + "created_at": { + "description": "Create time", + "type": "string" + }, + "feature": { + "description": "Type of banner (banner, link) // Parent menu` + "`" + `` + "`" + `", + "type": "string" + }, + "id": { + "type": "string" + }, + "images": { + "type": "array", + "items": { + "type": "string" + } + }, + "sequence": { + "description": "Sequence for sorting (Order by desc)", + "type": "integer" + }, + "standard": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.ProductStandard" + } + }, + "status": { + "description": "Status of banner (enabled, disabled) // Child menus", + "type": "string" + }, + "target": { + "type": "string" + }, + "title": { + "description": "Display name of banner", + "type": "string" + }, + "updated_at": { + "description": "Update time", + "type": "string" + } + } + }, + "schema.ProductCategory": { + "type": "object", + "properties": { + "created_at": { + "description": "Create time", + "type": "string" + }, + "id": { + "type": "integer" + }, + "label": { + "description": "Display name of banner", + "type": "string" + }, + "parentID": { + "description": "Type of banner (banner, link) // Parent menu` + "`" + `` + "`" + `", + "type": "integer" + }, + "status": { + "description": "Status of banner (enabled, disabled) // Child menus", + "type": "string" + }, + "updated_at": { + "description": "Update time", + "type": "string" + } + } + }, + "schema.ProductForm": { + "type": "object", + "required": [ + "categoryID", + "status", + "title" + ], + "properties": { + "categoryID": { + "type": "integer", + "maximum": 11 + }, + "code": { + "type": "string" + }, + "compose": { + "type": "string" + }, + "feature": { + "type": "string" + }, + "images": { + "type": "array", + "items": { + "type": "string" + } + }, + "sequence": { + "description": "Sequence for sorting (Order by desc)", + "type": "integer" + }, + "standard": { + "description": "Details about banner", + "type": "array", + "items": { + "$ref": "#/definitions/schema.ProductStandard" + } + }, + "status": { + "type": "string", + "enum": [ + "disabled", + "enabled" + ] + }, + "target": { + "description": "Type of menu (banner, link) // Parent menu` + "`" + `` + "`" + `", + "type": "string" + }, + "title": { + "description": "Display name of menu", + "type": "string", + "maxLength": 128 + } + } + }, + "schema.ProductStandard": { + "type": "object", + "properties": { + "label": { + "type": "string" + }, + "value": { + "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.SocialMedia": { + "type": "object", + "properties": { + "image": { + "type": "string" + }, + "link": { + "type": "string" + }, + "name": { + "type": "string" + } + } + }, + "schema.Team": { + "type": "object", + "properties": { + "area": { + "type": "string" + }, + "created_at": { + "description": "Create time", + "type": "string" + }, + "id": { + "description": "Unique ID", + "type": "string" + }, + "img": { + "description": "Details about banner", + "type": "string" + }, + "name": { + "description": "Display name of banner", + "type": "string" + }, + "rank": { + "description": "Type of banner (banner, link) // Parent menu` + "`" + `` + "`" + `", + "type": "array", + "items": { + "type": "string" + } + }, + "sequence": { + "description": "Sequence for sorting (Order by desc)", + "type": "integer" + }, + "status": { + "description": "Status of banner (enabled, disabled) // Child menus", + "type": "string" + }, + "updated_at": { + "description": "Update time", + "type": "string" + } + } + }, + "schema.TeamForm": { + "type": "object", + "required": [ + "name", + "status" + ], + "properties": { + "area": { + "type": "string" + }, + "img": { + "description": "Details about banner", + "type": "string" + }, + "name": { + "description": "Display name of menu", + "type": "string", + "maxLength": 128 + }, + "rank": { + "description": "Type of banner (ban", + "type": "array", + "items": { + "type": "string" + } + }, + "sequence": { + "description": "Sequence for sorting (Order by desc)", + "type": "integer" + }, + "status": { + "description": "Status of menu (enabled, disabled)", + "type": "string", + "enum": [ + "disabled", + "enabled" + ] + } + } + }, + "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": { + "created_at": { + "description": "Create time", + "type": "string" + }, + "email": { + "description": "Email of user", + "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" + } + } + }, + "schema.Video": { + "type": "object", + "properties": { + "created_at": { + "description": "Create time", + "type": "string" + }, + "fullImg": { + "description": "Details about banner", + "type": "string" + }, + "id": { + "description": "Unique ID", + "type": "string" + }, + "link": { + "type": "string" + }, + "pushAt": { + "type": "string" + }, + "sequence": { + "description": "Sequence for sorting (Order by desc)", + "type": "integer" + }, + "smallImg": { + "type": "string" + }, + "status": { + "description": "Status of banner (enabled, disabled) // Child menus", + "type": "string" + }, + "subheading": { + "description": "Display name of banner", + "type": "string" + }, + "title": { + "description": "Display name of banner", + "type": "string" + }, + "updated_at": { + "description": "Update time", + "type": "string" + }, + "videoUrl": { + "type": "string" + } + } + }, + "schema.VideoForm": { + "type": "object", + "required": [ + "status", + "title" + ], + "properties": { + "fullImg": { + "type": "string" + }, + "link": { + "type": "string" + }, + "pushAt": { + "type": "string" + }, + "sequence": { + "description": "Sequence for sorting (Order by desc)", + "type": "integer" + }, + "smallImg": { + "type": "string" + }, + "status": { + "description": "Status of menu (enabled, disabled)", + "type": "string", + "enum": [ + "disabled", + "enabled" + ] + }, + "title": { + "description": "Display name of menu", + "type": "string", + "maxLength": 128 + }, + "videoUrl": { + "type": "string" + } + } + }, + "schema.WebJobData": { + "type": "object", + "properties": { + "jobAreaTitle": { + "type": "string" + }, + "jobList": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.Job" + } + } + } + }, + "schema.WebSite": { + "type": "object", + "properties": { + "address": { + "type": "string" + }, + "affirm": { + "type": "string" + }, + "created_at": { + "type": "string" + }, + "email": { + "type": "string" + }, + "id": { + "type": "string" + }, + "lat": { + "type": "number" + }, + "lon": { + "type": "number" + }, + "phone": { + "type": "string" + }, + "reportContent": { + "type": "string" + }, + "reportImage": { + "type": "string" + }, + "reportNum": { + "type": "string" + }, + "socialMedia": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.SocialMedia" + } + }, + "updated_at": { + "type": "string" + } + } + }, + "schema.WebSiteForm": { + "type": "object", + "properties": { + "address": { + "type": "string" + }, + "affirm": { + "type": "string" + }, + "email": { + "type": "string" + }, + "lat": { + "type": "number" + }, + "lon": { + "type": "number" + }, + "phone": { + "type": "string" + }, + "reportContent": { + "type": "string" + }, + "reportImage": { + "type": "string" + }, + "reportNum": { + "type": "string" + }, + "socialMedia": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.SocialMedia" + } + } + } + }, + "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: "hailinservice", + Description: "Hailinservice API service", + 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..93c2464 --- /dev/null +++ b/internal/swagger/swagger.json @@ -0,0 +1,5410 @@ +{ + "swagger": "2.0", + "info": { + "description": "Hailinservice API service", + "title": "hailinservice", + "contact": {}, + "version": "v1.0.0" + }, + "paths": { + "/api/v1/articles": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "ArticleAPI" + ], + "summary": "Query Article 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": "name of Article", + "name": "name", + "in": "query" + }, + { + "type": "string", + "description": "Status of banner (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.Article" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "ArticleAPI" + ], + "summary": "Create Article record", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.ArticleForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.Article" + } + } + } + ] + } + }, + "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/articles/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "ArticleAPI" + ], + "summary": "Get Article 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.Article" + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "ArticleAPI" + ], + "summary": "Update Article 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.ArticleForm" + } + } + ], + "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": [ + "ArticleAPI" + ], + "summary": "Delete Article 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": [ + "BannerAPI" + ], + "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 + }, + { + "type": "string", + "description": "Name of banner", + "name": "name", + "in": "query" + }, + { + "type": "string", + "description": "Status of banner (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.Banner" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "BannerAPI" + ], + "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": [ + "BannerAPI" + ], + "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": [ + "BannerAPI" + ], + "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": [ + "BannerAPI" + ], + "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/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/jobs": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "职位模块" + ], + "summary": "Query Job 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": "title of Job", + "name": "title", + "in": "query" + }, + { + "type": "string", + "description": "jobAreaId of Job", + "name": "jobAreaId", + "in": "query" + }, + { + "type": "string", + "description": "Status of banner (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.Job" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "职位模块" + ], + "summary": "Create Job record", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.JobForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.Job" + } + } + } + ] + } + }, + "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/jobs/job_areas": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "职位模块" + ], + "summary": "Query JobArea 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": "Status of banner (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.JobArea" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "职位模块" + ], + "summary": "Create JobArea record", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.JobAreaForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.JobAreaForm" + } + } + } + ] + } + }, + "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/jobs/job_areas/{id}": { + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "职位模块" + ], + "summary": "Update JobArea 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.JobAreaForm" + } + } + ], + "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 JobArea 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/jobs/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "职位模块" + ], + "summary": "Get Job 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.Job" + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "职位模块" + ], + "summary": "Update Job 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.JobForm" + } + } + ], + "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 Job 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/memorabilias": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "发展历程模块" + ], + "summary": "Query Memorabilia 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": "title of Memorabilia", + "name": "title", + "in": "query" + }, + { + "type": "string", + "description": "month of Memorabilia", + "name": "month", + "in": "query" + }, + { + "type": "string", + "description": "year of Memorabilia", + "name": "year", + "in": "query" + }, + { + "type": "string", + "description": "Status of banner (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.Memorabilia" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "发展历程模块" + ], + "summary": "Create Article record", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.MemorabiliaForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.Memorabilia" + } + } + } + ] + } + }, + "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/memorabilias/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "发展历程模块" + ], + "summary": "Get Memorabilia 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.Memorabilia" + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "发展历程模块" + ], + "summary": "Update Article 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.MemorabiliaForm" + } + } + ], + "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 Article 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/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/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 + }, + { + "type": "string", + "description": "title of Product", + "name": "title", + "in": "query" + }, + { + "type": "integer", + "description": "categoryId of Product", + "name": "categoryId", + "in": "query" + }, + { + "type": "string", + "description": "code of Product", + "name": "code", + "in": "query" + }, + { + "type": "string", + "description": "Status of Product (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.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/categorys": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "产品模块" + ], + "summary": "Query ProductCategory list", + "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" + } + } + } + } + }, + "/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/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/teams": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "团队模块" + ], + "summary": "Query Team 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": "name of Team", + "name": "name", + "in": "query" + }, + { + "type": "string", + "description": "Status of banner (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.Team" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "团队模块" + ], + "summary": "Create Article record", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.TeamForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.Team" + } + } + } + ] + } + }, + "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/teams/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "团队模块" + ], + "summary": "Get Team 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.Team" + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "团队模块" + ], + "summary": "Update Article 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.TeamForm" + } + } + ], + "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 Article 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" + } + } + } + } + }, + "/api/v1/videos": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "VideoAPI" + ], + "summary": "Query Video 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": "title of Video", + "name": "title", + "in": "query" + }, + { + "type": "string", + "description": "Status of banner (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.Video" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "VideoAPI" + ], + "summary": "Create banner record", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.VideoForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.Video" + } + } + } + ] + } + }, + "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/videos/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "VideoAPI" + ], + "summary": "Get Video 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.Video" + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "VideoAPI" + ], + "summary": "Update Video 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": [ + "VideoAPI" + ], + "summary": "Delete Video 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/web/articles": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "网页模块" + ], + "summary": "获取公司动态", + "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": "jobAreaId of Job", + "name": "jobAreaId", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.Article" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/web/jobs": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "网页模块" + ], + "summary": "获取职位列表", + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.WebJobData" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/web_site": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "网页设置" + ], + "summary": "Query WebSite info", + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.WebSite" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "ArticleAPI" + ], + "summary": "Create WebSite record", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.WebSiteForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.WebSite" + } + } + } + ] + } + }, + "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/web_site/{id}": { + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "网页设置" + ], + "summary": "Update WebSite 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.WebSiteForm" + } + } + ], + "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" + } + } + } + } + } + }, + "definitions": { + "errors.Error": { + "type": "object", + "properties": { + "code": { + "type": "integer" + }, + "detail": { + "type": "string" + }, + "id": { + "type": "string" + }, + "status": { + "type": "string" + } + } + }, + "schema.Article": { + "type": "object", + "properties": { + "content": { + "type": "string" + }, + "created_at": { + "description": "Create time", + "type": "string" + }, + "id": { + "description": "Unique ID", + "type": "string" + }, + "img": { + "description": "Details about banner", + "type": "string" + }, + "link": { + "type": "string" + }, + "pushAt": { + "type": "string" + }, + "sequence": { + "description": "Sequence for sorting (Order by desc)", + "type": "integer" + }, + "status": { + "description": "Status of banner (enabled, disabled) // Child menus", + "type": "string" + }, + "title": { + "description": "Display name of banner", + "type": "string" + }, + "type": { + "description": "Type of banner (banner, link) // Parent menu``", + "type": "string" + }, + "updated_at": { + "description": "Update time", + "type": "string" + } + } + }, + "schema.ArticleForm": { + "type": "object", + "required": [ + "status", + "title", + "type" + ], + "properties": { + "content": { + "type": "string" + }, + "img": { + "description": "Details about banner", + "type": "string" + }, + "link": { + "type": "string" + }, + "pushAt": { + "type": "string" + }, + "sequence": { + "description": "Sequence for sorting (Order by desc)", + "type": "integer" + }, + "status": { + "description": "Status of menu (enabled, disabled)", + "type": "string", + "enum": [ + "disabled", + "enabled" + ] + }, + "title": { + "description": "Display name of menu", + "type": "string", + "maxLength": 128 + }, + "type": { + "description": "Type of menu (banner, link) // Parent menu``", + "type": "string", + "enum": [ + "banner", + "home", + "achievement", + "honor", + "talent_center", + "team", + "news" + ] + } + } + }, + "schema.Banner": { + "type": "object", + "properties": { + "created_at": { + "description": "Create time", + "type": "string" + }, + "id": { + "description": "Unique ID", + "type": "string" + }, + "img": { + "description": "Details about banner", + "type": "string" + }, + "link": { + "type": "string" + }, + "name": { + "description": "Display name of banner", + "type": "string" + }, + "sequence": { + "description": "Sequence for sorting (Order by desc)", + "type": "integer" + }, + "status": { + "description": "Status of banner (enabled, disabled) // Child menus", + "type": "string" + }, + "type": { + "description": "Type of banner (banner, link) // Parent menu``", + "type": "integer" + }, + "updated_at": { + "description": "Update time", + "type": "string" + } + } + }, + "schema.BannerForm": { + "type": "object", + "required": [ + "name", + "status", + "type" + ], + "properties": { + "img": { + "description": "Details about banner", + "type": "string" + }, + "link": { + "type": "string" + }, + "name": { + "description": "Display name of menu", + "type": "string", + "maxLength": 128 + }, + "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 (banner, link) // Parent menu``", + "type": "integer" + } + } + }, + "schema.Captcha": { + "type": "object", + "properties": { + "captcha_id": { + "description": "Captcha ID", + "type": "string" + } + } + }, + "schema.Job": { + "type": "object", + "properties": { + "created_at": { + "type": "string" + }, + "duty": { + "description": "职责", + "type": "array", + "items": { + "type": "string" + } + }, + "id": { + "description": "主键", + "type": "string" + }, + "introduce": { + "description": "要求", + "type": "string" + }, + "jobAreaId": { + "description": "职位地区id", + "type": "string" + }, + "salary": { + "description": "薪资", + "type": "string" + }, + "sequence": { + "description": "排序", + "type": "integer" + }, + "status": { + "description": "状态", + "type": "string" + }, + "title": { + "description": "标题", + "type": "string" + }, + "updated_at": { + "type": "string" + } + } + }, + "schema.JobArea": { + "type": "object", + "properties": { + "created_at": { + "description": "Create time", + "type": "string" + }, + "id": { + "description": "Unique ID", + "type": "string" + }, + "name": { + "description": "区域名字", + "type": "string" + }, + "status": { + "description": "Status of banner (enabled, disabled) // Child menus", + "type": "string" + }, + "updated_at": { + "description": "Update time", + "type": "string" + } + } + }, + "schema.JobAreaForm": { + "type": "object", + "required": [ + "name", + "status" + ], + "properties": { + "name": { + "description": "Display name of menu // Details about banner", + "type": "string", + "maxLength": 128 + }, + "status": { + "type": "string", + "enum": [ + "disabled", + "enabled" + ] + } + } + }, + "schema.JobForm": { + "type": "object", + "required": [ + "status", + "title" + ], + "properties": { + "duty": { + "type": "array", + "items": { + "type": "string" + } + }, + "introduce": { + "type": "string" + }, + "jobAreaId": { + "type": "string" + }, + "salary": { + "description": "Details about banner", + "type": "string" + }, + "sequence": { + "description": "Sequence for sorting (Order by desc)", + "type": "integer" + }, + "status": { + "description": "Status of menu (enabled, disabled)", + "type": "string", + "enum": [ + "disabled", + "enabled" + ] + }, + "title": { + "description": "Display name of menu", + "type": "string", + "maxLength": 128 + } + } + }, + "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": [ + "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.Memorabilia": { + "type": "object", + "properties": { + "created_at": { + "description": "Create time", + "type": "string" + }, + "id": { + "description": "Unique ID", + "type": "string" + }, + "month": { + "description": "Type of banner (banner, link) // Parent menu``", + "type": "integer" + }, + "sequence": { + "type": "integer" + }, + "status": { + "description": "Status of banner (enabled, disabled) // Child menus", + "type": "string" + }, + "title": { + "description": "Display name of banner", + "type": "string" + }, + "updated_at": { + "description": "Update time", + "type": "string" + }, + "year": { + "description": "Type of banner (banner, link) // Parent menu``", + "type": "integer" + } + } + }, + "schema.MemorabiliaForm": { + "type": "object", + "required": [ + "status", + "title" + ], + "properties": { + "month": { + "type": "integer" + }, + "sequence": { + "description": "Sequence for sorting (Order by desc)", + "type": "integer" + }, + "status": { + "description": "Status of menu (enabled, disabled)", + "type": "string", + "enum": [ + "disabled", + "enabled" + ] + }, + "title": { + "description": "Display name of menu", + "type": "string", + "maxLength": 128 + }, + "year": { + "type": "integer" + } + } + }, + "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.Product": { + "type": "object", + "properties": { + "categoryID": { + "type": "integer" + }, + "code": { + "description": "Details about banner", + "type": "string" + }, + "compose": { + "type": "string" + }, + "created_at": { + "description": "Create time", + "type": "string" + }, + "feature": { + "description": "Type of banner (banner, link) // Parent menu``", + "type": "string" + }, + "id": { + "type": "string" + }, + "images": { + "type": "array", + "items": { + "type": "string" + } + }, + "sequence": { + "description": "Sequence for sorting (Order by desc)", + "type": "integer" + }, + "standard": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.ProductStandard" + } + }, + "status": { + "description": "Status of banner (enabled, disabled) // Child menus", + "type": "string" + }, + "target": { + "type": "string" + }, + "title": { + "description": "Display name of banner", + "type": "string" + }, + "updated_at": { + "description": "Update time", + "type": "string" + } + } + }, + "schema.ProductCategory": { + "type": "object", + "properties": { + "created_at": { + "description": "Create time", + "type": "string" + }, + "id": { + "type": "integer" + }, + "label": { + "description": "Display name of banner", + "type": "string" + }, + "parentID": { + "description": "Type of banner (banner, link) // Parent menu``", + "type": "integer" + }, + "status": { + "description": "Status of banner (enabled, disabled) // Child menus", + "type": "string" + }, + "updated_at": { + "description": "Update time", + "type": "string" + } + } + }, + "schema.ProductForm": { + "type": "object", + "required": [ + "categoryID", + "status", + "title" + ], + "properties": { + "categoryID": { + "type": "integer", + "maximum": 11 + }, + "code": { + "type": "string" + }, + "compose": { + "type": "string" + }, + "feature": { + "type": "string" + }, + "images": { + "type": "array", + "items": { + "type": "string" + } + }, + "sequence": { + "description": "Sequence for sorting (Order by desc)", + "type": "integer" + }, + "standard": { + "description": "Details about banner", + "type": "array", + "items": { + "$ref": "#/definitions/schema.ProductStandard" + } + }, + "status": { + "type": "string", + "enum": [ + "disabled", + "enabled" + ] + }, + "target": { + "description": "Type of menu (banner, link) // Parent menu``", + "type": "string" + }, + "title": { + "description": "Display name of menu", + "type": "string", + "maxLength": 128 + } + } + }, + "schema.ProductStandard": { + "type": "object", + "properties": { + "label": { + "type": "string" + }, + "value": { + "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.SocialMedia": { + "type": "object", + "properties": { + "image": { + "type": "string" + }, + "link": { + "type": "string" + }, + "name": { + "type": "string" + } + } + }, + "schema.Team": { + "type": "object", + "properties": { + "area": { + "type": "string" + }, + "created_at": { + "description": "Create time", + "type": "string" + }, + "id": { + "description": "Unique ID", + "type": "string" + }, + "img": { + "description": "Details about banner", + "type": "string" + }, + "name": { + "description": "Display name of banner", + "type": "string" + }, + "rank": { + "description": "Type of banner (banner, link) // Parent menu``", + "type": "array", + "items": { + "type": "string" + } + }, + "sequence": { + "description": "Sequence for sorting (Order by desc)", + "type": "integer" + }, + "status": { + "description": "Status of banner (enabled, disabled) // Child menus", + "type": "string" + }, + "updated_at": { + "description": "Update time", + "type": "string" + } + } + }, + "schema.TeamForm": { + "type": "object", + "required": [ + "name", + "status" + ], + "properties": { + "area": { + "type": "string" + }, + "img": { + "description": "Details about banner", + "type": "string" + }, + "name": { + "description": "Display name of menu", + "type": "string", + "maxLength": 128 + }, + "rank": { + "description": "Type of banner (ban", + "type": "array", + "items": { + "type": "string" + } + }, + "sequence": { + "description": "Sequence for sorting (Order by desc)", + "type": "integer" + }, + "status": { + "description": "Status of menu (enabled, disabled)", + "type": "string", + "enum": [ + "disabled", + "enabled" + ] + } + } + }, + "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": { + "created_at": { + "description": "Create time", + "type": "string" + }, + "email": { + "description": "Email of user", + "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" + } + } + }, + "schema.Video": { + "type": "object", + "properties": { + "created_at": { + "description": "Create time", + "type": "string" + }, + "fullImg": { + "description": "Details about banner", + "type": "string" + }, + "id": { + "description": "Unique ID", + "type": "string" + }, + "link": { + "type": "string" + }, + "pushAt": { + "type": "string" + }, + "sequence": { + "description": "Sequence for sorting (Order by desc)", + "type": "integer" + }, + "smallImg": { + "type": "string" + }, + "status": { + "description": "Status of banner (enabled, disabled) // Child menus", + "type": "string" + }, + "subheading": { + "description": "Display name of banner", + "type": "string" + }, + "title": { + "description": "Display name of banner", + "type": "string" + }, + "updated_at": { + "description": "Update time", + "type": "string" + }, + "videoUrl": { + "type": "string" + } + } + }, + "schema.VideoForm": { + "type": "object", + "required": [ + "status", + "title" + ], + "properties": { + "fullImg": { + "type": "string" + }, + "link": { + "type": "string" + }, + "pushAt": { + "type": "string" + }, + "sequence": { + "description": "Sequence for sorting (Order by desc)", + "type": "integer" + }, + "smallImg": { + "type": "string" + }, + "status": { + "description": "Status of menu (enabled, disabled)", + "type": "string", + "enum": [ + "disabled", + "enabled" + ] + }, + "title": { + "description": "Display name of menu", + "type": "string", + "maxLength": 128 + }, + "videoUrl": { + "type": "string" + } + } + }, + "schema.WebJobData": { + "type": "object", + "properties": { + "jobAreaTitle": { + "type": "string" + }, + "jobList": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.Job" + } + } + } + }, + "schema.WebSite": { + "type": "object", + "properties": { + "address": { + "type": "string" + }, + "affirm": { + "type": "string" + }, + "created_at": { + "type": "string" + }, + "email": { + "type": "string" + }, + "id": { + "type": "string" + }, + "lat": { + "type": "number" + }, + "lon": { + "type": "number" + }, + "phone": { + "type": "string" + }, + "reportContent": { + "type": "string" + }, + "reportImage": { + "type": "string" + }, + "reportNum": { + "type": "string" + }, + "socialMedia": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.SocialMedia" + } + }, + "updated_at": { + "type": "string" + } + } + }, + "schema.WebSiteForm": { + "type": "object", + "properties": { + "address": { + "type": "string" + }, + "affirm": { + "type": "string" + }, + "email": { + "type": "string" + }, + "lat": { + "type": "number" + }, + "lon": { + "type": "number" + }, + "phone": { + "type": "string" + }, + "reportContent": { + "type": "string" + }, + "reportImage": { + "type": "string" + }, + "reportNum": { + "type": "string" + }, + "socialMedia": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.SocialMedia" + } + } + } + }, + "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..71edfec --- /dev/null +++ b/internal/swagger/swagger.yaml @@ -0,0 +1,3430 @@ +definitions: + errors.Error: + properties: + code: + type: integer + detail: + type: string + id: + type: string + status: + type: string + type: object + schema.Article: + properties: + content: + type: string + created_at: + description: Create time + type: string + id: + description: Unique ID + type: string + img: + description: Details about banner + type: string + link: + type: string + pushAt: + type: string + sequence: + description: Sequence for sorting (Order by desc) + type: integer + status: + description: Status of banner (enabled, disabled) // Child menus + type: string + title: + description: Display name of banner + type: string + type: + description: Type of banner (banner, link) // Parent menu`` + type: string + updated_at: + description: Update time + type: string + type: object + schema.ArticleForm: + properties: + content: + type: string + img: + description: Details about banner + type: string + link: + type: string + pushAt: + type: string + sequence: + description: Sequence for sorting (Order by desc) + type: integer + status: + description: Status of menu (enabled, disabled) + enum: + - disabled + - enabled + type: string + title: + description: Display name of menu + maxLength: 128 + type: string + type: + description: Type of menu (banner, link) // Parent menu`` + enum: + - banner + - home + - achievement + - honor + - talent_center + - team + - news + type: string + required: + - status + - title + - type + type: object + schema.Banner: + properties: + created_at: + description: Create time + type: string + id: + description: Unique ID + type: string + img: + description: Details about banner + type: string + link: + type: string + name: + description: Display name of banner + type: string + sequence: + description: Sequence for sorting (Order by desc) + type: integer + status: + description: Status of banner (enabled, disabled) // Child menus + type: string + type: + description: Type of banner (banner, link) // Parent menu`` + type: integer + updated_at: + description: Update time + type: string + type: object + schema.BannerForm: + properties: + img: + description: Details about banner + type: string + link: + type: string + name: + description: Display name of menu + maxLength: 128 + type: string + 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 (banner, link) // Parent menu`` + type: integer + required: + - name + - status + - type + type: object + schema.Captcha: + properties: + captcha_id: + description: Captcha ID + type: string + type: object + schema.Job: + properties: + created_at: + type: string + duty: + description: 职责 + items: + type: string + type: array + id: + description: 主键 + type: string + introduce: + description: 要求 + type: string + jobAreaId: + description: 职位地区id + type: string + salary: + description: 薪资 + type: string + sequence: + description: 排序 + type: integer + status: + description: 状态 + type: string + title: + description: 标题 + type: string + updated_at: + type: string + type: object + schema.JobArea: + properties: + created_at: + description: Create time + type: string + id: + description: Unique ID + type: string + name: + description: 区域名字 + type: string + status: + description: Status of banner (enabled, disabled) // Child menus + type: string + updated_at: + description: Update time + type: string + type: object + schema.JobAreaForm: + properties: + name: + description: Display name of menu // Details about banner + maxLength: 128 + type: string + status: + enum: + - disabled + - enabled + type: string + required: + - name + - status + type: object + schema.JobForm: + properties: + duty: + items: + type: string + type: array + introduce: + type: string + jobAreaId: + type: string + salary: + description: Details about banner + type: string + sequence: + description: Sequence for sorting (Order by desc) + type: integer + status: + description: Status of menu (enabled, disabled) + enum: + - disabled + - enabled + type: string + title: + description: Display name of menu + maxLength: 128 + type: string + required: + - status + - title + 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: + - 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.Memorabilia: + properties: + created_at: + description: Create time + type: string + id: + description: Unique ID + type: string + month: + description: Type of banner (banner, link) // Parent menu`` + type: integer + sequence: + type: integer + status: + description: Status of banner (enabled, disabled) // Child menus + type: string + title: + description: Display name of banner + type: string + updated_at: + description: Update time + type: string + year: + description: Type of banner (banner, link) // Parent menu`` + type: integer + type: object + schema.MemorabiliaForm: + properties: + month: + type: integer + sequence: + description: Sequence for sorting (Order by desc) + type: integer + status: + description: Status of menu (enabled, disabled) + enum: + - disabled + - enabled + type: string + title: + description: Display name of menu + maxLength: 128 + type: string + year: + type: integer + required: + - status + - title + 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.Product: + properties: + categoryID: + type: integer + code: + description: Details about banner + type: string + compose: + type: string + created_at: + description: Create time + type: string + feature: + description: Type of banner (banner, link) // Parent menu`` + type: string + id: + type: string + images: + items: + type: string + type: array + sequence: + description: Sequence for sorting (Order by desc) + type: integer + standard: + items: + $ref: '#/definitions/schema.ProductStandard' + type: array + status: + description: Status of banner (enabled, disabled) // Child menus + type: string + target: + type: string + title: + description: Display name of banner + type: string + updated_at: + description: Update time + type: string + type: object + schema.ProductCategory: + properties: + created_at: + description: Create time + type: string + id: + type: integer + label: + description: Display name of banner + type: string + parentID: + description: Type of banner (banner, link) // Parent menu`` + type: integer + status: + description: Status of banner (enabled, disabled) // Child menus + type: string + updated_at: + description: Update time + type: string + type: object + schema.ProductForm: + properties: + categoryID: + maximum: 11 + type: integer + code: + type: string + compose: + type: string + feature: + type: string + images: + items: + type: string + type: array + sequence: + description: Sequence for sorting (Order by desc) + type: integer + standard: + description: Details about banner + items: + $ref: '#/definitions/schema.ProductStandard' + type: array + status: + enum: + - disabled + - enabled + type: string + target: + description: Type of menu (banner, link) // Parent menu`` + type: string + title: + description: Display name of menu + maxLength: 128 + type: string + required: + - categoryID + - status + - title + type: object + schema.ProductStandard: + properties: + label: + type: string + value: + 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.SocialMedia: + properties: + image: + type: string + link: + type: string + name: + type: string + type: object + schema.Team: + properties: + area: + type: string + created_at: + description: Create time + type: string + id: + description: Unique ID + type: string + img: + description: Details about banner + type: string + name: + description: Display name of banner + type: string + rank: + description: Type of banner (banner, link) // Parent menu`` + items: + type: string + type: array + sequence: + description: Sequence for sorting (Order by desc) + type: integer + status: + description: Status of banner (enabled, disabled) // Child menus + type: string + updated_at: + description: Update time + type: string + type: object + schema.TeamForm: + properties: + area: + type: string + img: + description: Details about banner + type: string + name: + description: Display name of menu + maxLength: 128 + type: string + rank: + description: Type of banner (ban + items: + type: string + type: array + sequence: + description: Sequence for sorting (Order by desc) + type: integer + status: + description: Status of menu (enabled, disabled) + enum: + - disabled + - enabled + type: string + required: + - name + - status + 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: + created_at: + description: Create time + type: string + email: + description: Email of user + 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 + schema.Video: + properties: + created_at: + description: Create time + type: string + fullImg: + description: Details about banner + type: string + id: + description: Unique ID + type: string + link: + type: string + pushAt: + type: string + sequence: + description: Sequence for sorting (Order by desc) + type: integer + smallImg: + type: string + status: + description: Status of banner (enabled, disabled) // Child menus + type: string + subheading: + description: Display name of banner + type: string + title: + description: Display name of banner + type: string + updated_at: + description: Update time + type: string + videoUrl: + type: string + type: object + schema.VideoForm: + properties: + fullImg: + type: string + link: + type: string + pushAt: + type: string + sequence: + description: Sequence for sorting (Order by desc) + type: integer + smallImg: + type: string + status: + description: Status of menu (enabled, disabled) + enum: + - disabled + - enabled + type: string + title: + description: Display name of menu + maxLength: 128 + type: string + videoUrl: + type: string + required: + - status + - title + type: object + schema.WebJobData: + properties: + jobAreaTitle: + type: string + jobList: + items: + $ref: '#/definitions/schema.Job' + type: array + type: object + schema.WebSite: + properties: + address: + type: string + affirm: + type: string + created_at: + type: string + email: + type: string + id: + type: string + lat: + type: number + lon: + type: number + phone: + type: string + reportContent: + type: string + reportImage: + type: string + reportNum: + type: string + socialMedia: + items: + $ref: '#/definitions/schema.SocialMedia' + type: array + updated_at: + type: string + type: object + schema.WebSiteForm: + properties: + address: + type: string + affirm: + type: string + email: + type: string + lat: + type: number + lon: + type: number + phone: + type: string + reportContent: + type: string + reportImage: + type: string + reportNum: + type: string + socialMedia: + items: + $ref: '#/definitions/schema.SocialMedia' + type: array + type: object + util.ResponseResult: + properties: + data: {} + error: + $ref: '#/definitions/errors.Error' + success: + type: boolean + total: + type: integer + type: object +info: + contact: {} + description: Hailinservice API service + title: hailinservice + version: v1.0.0 +paths: + /api/v1/articles: + 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: name of Article + in: query + name: name + type: string + - description: Status of banner (disabled,enabled) + in: query + name: status + type: string + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + items: + $ref: '#/definitions/schema.Article' + 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 Article list + tags: + - ArticleAPI + post: + parameters: + - description: Request body + in: body + name: body + required: true + schema: + $ref: '#/definitions/schema.ArticleForm' + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + $ref: '#/definitions/schema.Article' + 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 Article record + tags: + - ArticleAPI + /api/v1/articles/{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 Article record by ID + tags: + - ArticleAPI + 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.Article' + type: object + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Get Article record by ID + tags: + - ArticleAPI + 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.ArticleForm' + 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 Article record by ID + tags: + - ArticleAPI + /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 + - description: Name of banner + in: query + name: name + type: string + - description: Status of banner (disabled,enabled) + in: query + name: status + type: string + 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: + - BannerAPI + 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: + - BannerAPI + /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: + - BannerAPI + 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: + - BannerAPI + 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: + - BannerAPI + /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/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/jobs: + 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: title of Job + in: query + name: title + type: string + - description: jobAreaId of Job + in: query + name: jobAreaId + type: string + - description: Status of banner (disabled,enabled) + in: query + name: status + type: string + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + items: + $ref: '#/definitions/schema.Job' + 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 Job list + tags: + - 职位模块 + post: + parameters: + - description: Request body + in: body + name: body + required: true + schema: + $ref: '#/definitions/schema.JobForm' + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + $ref: '#/definitions/schema.Job' + 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 Job record + tags: + - 职位模块 + /api/v1/jobs/{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 Job 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.Job' + type: object + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Get Job 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.JobForm' + 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 Job record by ID + tags: + - 职位模块 + /api/v1/jobs/job_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 + - description: Status of banner (disabled,enabled) + in: query + name: status + type: string + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + items: + $ref: '#/definitions/schema.JobArea' + 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 JobArea list + tags: + - 职位模块 + post: + parameters: + - description: Request body + in: body + name: body + required: true + schema: + $ref: '#/definitions/schema.JobAreaForm' + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + $ref: '#/definitions/schema.JobAreaForm' + 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 JobArea record + tags: + - 职位模块 + /api/v1/jobs/job_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 JobArea 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.JobAreaForm' + 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 JobArea 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/memorabilias: + 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: title of Memorabilia + in: query + name: title + type: string + - description: month of Memorabilia + in: query + name: month + type: string + - description: year of Memorabilia + in: query + name: year + type: string + - description: Status of banner (disabled,enabled) + in: query + name: status + type: string + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + items: + $ref: '#/definitions/schema.Memorabilia' + 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 Memorabilia list + tags: + - 发展历程模块 + post: + parameters: + - description: Request body + in: body + name: body + required: true + schema: + $ref: '#/definitions/schema.MemorabiliaForm' + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + $ref: '#/definitions/schema.Memorabilia' + 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 Article record + tags: + - 发展历程模块 + /api/v1/memorabilias/{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 Article 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.Memorabilia' + type: object + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Get Memorabilia 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.MemorabiliaForm' + 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 Article record by ID + tags: + - 发展历程模块 + /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/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 + - description: title of Product + in: query + name: title + type: string + - description: categoryId of Product + in: query + name: categoryId + type: integer + - description: code of Product + in: query + name: code + type: string + - description: Status of Product (disabled,enabled) + in: query + name: status + type: string + 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/products/categorys: + get: + 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 ProductCategory list + 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/teams: + 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: name of Team + in: query + name: name + type: string + - description: Status of banner (disabled,enabled) + in: query + name: status + type: string + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + items: + $ref: '#/definitions/schema.Team' + 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 Team list + tags: + - 团队模块 + post: + parameters: + - description: Request body + in: body + name: body + required: true + schema: + $ref: '#/definitions/schema.TeamForm' + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + $ref: '#/definitions/schema.Team' + 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 Article record + tags: + - 团队模块 + /api/v1/teams/{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 Article 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.Team' + type: object + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Get Team 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.TeamForm' + 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 Article record by ID + tags: + - 团队模块 + /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 + /api/v1/videos: + 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: title of Video + in: query + name: title + type: string + - description: Status of banner (disabled,enabled) + in: query + name: status + type: string + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + items: + $ref: '#/definitions/schema.Video' + 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 Video list + tags: + - VideoAPI + post: + parameters: + - description: Request body + in: body + name: body + required: true + schema: + $ref: '#/definitions/schema.VideoForm' + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + $ref: '#/definitions/schema.Video' + 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: + - VideoAPI + /api/v1/videos/{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 Video record by ID + tags: + - VideoAPI + 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.Video' + type: object + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Get Video record by ID + tags: + - VideoAPI + 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 Video record by ID + tags: + - VideoAPI + /api/v1/web/articles: + 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: jobAreaId of Job + in: query + name: jobAreaId + type: string + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + items: + $ref: '#/definitions/schema.Article' + 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: 获取公司动态 + tags: + - 网页模块 + /api/v1/web/jobs: + get: + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + items: + $ref: '#/definitions/schema.WebJobData' + 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: 获取职位列表 + tags: + - 网页模块 + /api/v1/web_site: + get: + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + items: + $ref: '#/definitions/schema.WebSite' + 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 WebSite info + tags: + - 网页设置 + post: + parameters: + - description: Request body + in: body + name: body + required: true + schema: + $ref: '#/definitions/schema.WebSiteForm' + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + $ref: '#/definitions/schema.WebSite' + 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 WebSite record + tags: + - ArticleAPI + /api/v1/web_site/{id}: + 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.WebSiteForm' + 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 WebSite record by ID + tags: + - 网页设置 +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..0cae7f5 --- /dev/null +++ b/internal/utility/prom/prom.go @@ -0,0 +1,36 @@ +package prom + +import ( + "github.com/guxuan/hailin_service/internal/config" + "github.com/guxuan/hailin_service/pkg/promx" + "github.com/guxuan/hailin_service/pkg/util" + "github.com/gin-gonic/gin" +) + +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..053e601 --- /dev/null +++ b/internal/wirex/injector.go @@ -0,0 +1,129 @@ +package wirex + +import ( + "context" + "time" + + "github.com/guxuan/hailin_service/internal/config" + "github.com/guxuan/hailin_service/internal/mods" + "github.com/guxuan/hailin_service/pkg/cachex" + "github.com/guxuan/hailin_service/pkg/gormx" + "github.com/guxuan/hailin_service/pkg/jwtx" + "github.com/golang-jwt/jwt" + "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..41b4c08 --- /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.com/guxuan/hailin_service/internal/mods" + "github.com/guxuan/hailin_service/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..a8ed658 --- /dev/null +++ b/internal/wirex/wire_gen.go @@ -0,0 +1,253 @@ +// 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.com/guxuan/hailin_service/internal/mods" + "github.com/guxuan/hailin_service/internal/mods/rbac" + "github.com/guxuan/hailin_service/internal/mods/rbac/api" + "github.com/guxuan/hailin_service/internal/mods/rbac/biz" + "github.com/guxuan/hailin_service/internal/mods/rbac/dal" + "github.com/guxuan/hailin_service/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, + } + banner := &dal.Banner{ + DB: db, + } + bizBanner := &biz.Banner{ + Cache: cacher, + Trans: trans, + BannerDAL: banner, + } + apiBanner := &api.Banner{ + BannerBIZ: bizBanner, + } + article := &dal.Article{ + DB: db, + } + bizArticle := &biz.Article{ + Cache: cacher, + Trans: trans, + ArticleDAL: article, + } + apiArticle := &api.Article{ + ArticleBIZ: bizArticle, + } + video := &dal.Video{ + DB: db, + } + bizVideo := &biz.Video{ + Cache: cacher, + Trans: trans, + VideoDAL: video, + } + apiVideo := &api.Video{ + VideoBIZ: bizVideo, + } + upload := &biz.Upload{ + Cache: cacher, + Trans: trans, + } + apiUpload := &api.Upload{ + UploadBIZ: upload, + } + job := &dal.Job{ + DB: db, + } + jobArea := &dal.JobArea{ + DB: db, + } + bizJob := &biz.Job{ + Cache: cacher, + Trans: trans, + JobDAL: job, + JobAreaDAL: jobArea, + } + apiJob := &api.Job{ + JobBIZ: bizJob, + } + web := &api.Web{ + ArticleBIZ: bizArticle, + JobBIZ: bizJob, + } + webSite := &dal.WebSite{ + DB: db, + } + bizWebSite := &biz.WebSite{ + Cache: cacher, + Trans: trans, + WebSiteDAL: webSite, + } + apiWebSite := &api.WebSite{ + WebSiteBIZ: bizWebSite, + } + team := &dal.Team{ + DB: db, + } + bizTeam := &biz.Team{ + Cache: cacher, + Trans: trans, + TeamDAL: team, + } + apiTeam := &api.Team{ + TeamBIZ: bizTeam, + } + memorabilia := &dal.Memorabilia{ + DB: db, + } + bizMemorabilia := &biz.Memorabilia{ + Cache: cacher, + Trans: trans, + MemorabiliaDAL: memorabilia, + } + apiMemorabilia := &api.Memorabilia{ + MemorabiliaBIZ: bizMemorabilia, + } + product := &dal.Product{ + DB: db, + } + productCategory := &dal.ProductCategory{ + DB: db, + } + bizProduct := &biz.Product{ + Cache: cacher, + Trans: trans, + ProductDAL: product, + ProductCategoryDAL: productCategory, + } + apiProduct := &api.Product{ + ProductBIZ: bizProduct, + } + 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, + BannerAPI: apiBanner, + ArticleAPI: apiArticle, + VideoAPI: apiVideo, + UploadAPI: apiUpload, + JobAPI: apiJob, + WebAPI: web, + WebSiteAPI: apiWebSite, + TeamAPI: apiTeam, + MemorabiliaAPI: apiMemorabilia, + ProductAPI: apiProduct, + Casbinx: casbinx, + } + modsMods := &mods.Mods{ + RBAC: rbacRBAC, + } + 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..18dcc26 --- /dev/null +++ b/main.go @@ -0,0 +1,35 @@ +package main + +import ( + "os" + + "github.com/guxuan/hailin_service/cmd" + "github.com/urfave/cli/v2" +) + +// Usage: go build -ldflags "-X main.VERSION=x.x.x" +var VERSION = "v1.0.0" + +// @title hailinservice +// @version v1.0.0 +// @description Hailinservice API service +// @securityDefinitions.apikey ApiKeyAuth +// @in header +// @name Authorization +// @schemes http https +// @basePath / +func main() { + app := cli.NewApp() + app.Name = "hailinservice" + app.Version = VERSION + app.Usage = "Hailinservice API service" + 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..9660053 --- /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..72cb5fe --- /dev/null +++ b/pkg/jwtx/jwt.go @@ -0,0 +1,190 @@ +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 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..bc659f3 --- /dev/null +++ b/pkg/logging/logger.go @@ -0,0 +1,119 @@ +package logging + +import ( + "context" + "fmt" + + "go.uber.org/zap" +) + +const ( + TagKeyMain = "main" + TagKeyRecovery = "recovery" + TagKeyRequest = "request" + TagKeyLogin = "login" + 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 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..820dda1 --- /dev/null +++ b/pkg/middleware/auth.go @@ -0,0 +1,40 @@ +package middleware + +import ( + "github.com/guxuan/hailin_service/pkg/logging" + "github.com/guxuan/hailin_service/pkg/util" + "github.com/gin-gonic/gin" +) + +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, err) + return + } + + ctx := util.NewUserID(c.Request.Context(), userID) + ctx = logging.NewUserID(ctx, userID) + 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..aee2411 --- /dev/null +++ b/pkg/middleware/casbin.go @@ -0,0 +1,46 @@ +package middleware + +import ( + "github.com/guxuan/hailin_service/pkg/errors" + "github.com/guxuan/hailin_service/pkg/util" + "github.com/casbin/casbin/v2" + "github.com/gin-gonic/gin" +) + +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..7fae6bf --- /dev/null +++ b/pkg/middleware/copybody.go @@ -0,0 +1,66 @@ +package middleware + +import ( + "bytes" + "compress/gzip" + "io" + "net/http" + + "github.com/guxuan/hailin_service/pkg/errors" + "github.com/guxuan/hailin_service/pkg/util" + "github.com/gin-gonic/gin" +) + +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..6221c86 --- /dev/null +++ b/pkg/middleware/logger.go @@ -0,0 +1,88 @@ +package middleware + +import ( + "fmt" + "mime" + "net/http" + "time" + + "github.com/guxuan/hailin_service/pkg/logging" + "github.com/guxuan/hailin_service/pkg/util" + "github.com/gin-gonic/gin" + "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..005c5a0 --- /dev/null +++ b/pkg/middleware/ratelimiter.go @@ -0,0 +1,144 @@ +package middleware + +import ( + "context" + "time" + + "github.com/guxuan/hailin_service/pkg/errors" + "github.com/guxuan/hailin_service/pkg/logging" + "github.com/guxuan/hailin_service/pkg/util" + "github.com/gin-gonic/gin" + "github.com/go-redis/redis/v8" + "github.com/go-redis/redis_rate/v9" + "github.com/patrickmn/go-cache" + "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, 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..dd25344 --- /dev/null +++ b/pkg/middleware/recover.go @@ -0,0 +1,59 @@ +package middleware + +import ( + "fmt" + "net/http/httputil" + "strings" + "time" + + "github.com/guxuan/hailin_service/pkg/errors" + "github.com/guxuan/hailin_service/pkg/logging" + "github.com/guxuan/hailin_service/pkg/util" + "github.com/gin-gonic/gin" + "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..4a5bb72 --- /dev/null +++ b/pkg/middleware/trace.go @@ -0,0 +1,48 @@ +package middleware + +import ( + "fmt" + "strings" + + "github.com/guxuan/hailin_service/pkg/logging" + "github.com/guxuan/hailin_service/pkg/util" + "github.com/gin-gonic/gin" + "github.com/rs/xid" +) + +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/util/command.go b/pkg/util/command.go new file mode 100644 index 0000000..1d9df07 --- /dev/null +++ b/pkg/util/command.go @@ -0,0 +1,45 @@ +package util + +import ( + "context" + "os" + "os/signal" + "syscall" + "time" + + "github.com/guxuan/hailin_service/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..3c40e68 --- /dev/null +++ b/pkg/util/context.go @@ -0,0 +1,115 @@ +package util + +import ( + "context" + + "github.com/guxuan/hailin_service/pkg/encoding/json" + "gorm.io/gorm" +) + +type ( + traceIDCtx struct{} + transCtx struct{} + rowLockCtx struct{} + userIDCtx 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 string) context.Context { + return context.WithValue(ctx, userIDCtx{}, userID) +} + +func FromUserID(ctx context.Context) string { + v := ctx.Value(userIDCtx{}) + 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 { + v := ctx.Value(isRootUserCtx{}) + return v != nil && v.(bool) +} + +// 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..0db4f96 --- /dev/null +++ b/pkg/util/gin.go @@ -0,0 +1,136 @@ +package util + +import ( + "fmt" + "net/http" + "reflect" + "strings" + + "github.com/guxuan/hailin_service/pkg/encoding/json" + "github.com/guxuan/hailin_service/pkg/errors" + "github.com/guxuan/hailin_service/pkg/logging" + "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/binding" + "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/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..d8b4c61 --- /dev/null +++ b/pkg/util/schema.go @@ -0,0 +1,61 @@ +package util + +import "github.com/guxuan/hailin_service/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