From ee50f56b262cdd91e9dcaa0db4105f6e0e38cb29 Mon Sep 17 00:00:00 2001 From: Daniel Date: Thu, 19 Jun 2025 10:35:26 +0800 Subject: [PATCH] first commit --- .devcontainer/.env | 5 + .devcontainer/Dockerfile | 13 + .devcontainer/devcontainer.json | 9 + .devcontainer/docker-compose.yml | 42 + .gitignore | 32 + 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 + demo.png | Bin 0 -> 325393 bytes go.mod | 144 + go.sum | 626 ++ internal/bootstrap/bootstrap.go | 106 + internal/bootstrap/http.go | 192 + internal/bootstrap/logger.go | 43 + internal/config/config.go | 166 + internal/config/consts.go | 17 + internal/config/middleware.go | 90 + internal/config/parse.go | 84 + internal/mods/activity/api/activity.api.go | 131 + .../activity/api/activity_category.api.go | 131 + internal/mods/activity/api/reciprocity.api.go | 131 + internal/mods/activity/biz/activity.biz.go | 104 + .../activity/biz/activity_category.biz.go | 104 + internal/mods/activity/biz/reciprocity.biz.go | 104 + internal/mods/activity/dal/activity.dal.go | 83 + .../activity/dal/activity_category.dal.go | 83 + internal/mods/activity/dal/reciprocity.dal.go | 83 + internal/mods/activity/main.go | 64 + internal/mods/activity/schema/activity.go | 92 + .../mods/activity/schema/activity_category.go | 55 + internal/mods/activity/schema/reciprocity.go | 68 + internal/mods/activity/wire.go | 21 + internal/mods/ai/api/ai_request.api.go | 171 + internal/mods/ai/biz/ai_request.biz.go | 104 + internal/mods/ai/dal/ai_request.dal.go | 83 + internal/mods/ai/main.go | 46 + internal/mods/ai/schema/ai_request.go | 53 + internal/mods/ai/wire.go | 15 + internal/mods/app/api/app.api.go | 131 + internal/mods/app/biz/app.biz.go | 104 + internal/mods/app/dal/app.dal.go | 83 + internal/mods/app/main.go | 46 + internal/mods/app/schema/app.go | 47 + internal/mods/app/wire.go | 15 + internal/mods/common/api/banner.api.go | 131 + internal/mods/common/api/help.api.go | 131 + internal/mods/common/api/metting_room.api.go | 131 + .../mods/common/api/metting_room_order.api.go | 131 + internal/mods/common/api/notice.api.go | 131 + .../common/api/surrounding_service.api.go | 131 + .../api/surrounding_service_type.api.go | 131 + internal/mods/common/api/web_site.api.go | 131 + internal/mods/common/api/work_order.api.go | 131 + .../mods/common/api/work_order_type.api.go | 131 + internal/mods/common/biz/banner.biz.go | 104 + internal/mods/common/biz/help.biz.go | 104 + internal/mods/common/biz/metting_room.biz.go | 104 + .../mods/common/biz/metting_room_order.biz.go | 104 + internal/mods/common/biz/notice.biz.go | 104 + .../common/biz/surrounding_service.biz.go | 104 + .../biz/surrounding_service_type.biz.go | 104 + internal/mods/common/biz/web_site.biz.go | 104 + internal/mods/common/biz/work_order.biz.go | 104 + .../mods/common/biz/work_order_type.biz.go | 104 + internal/mods/common/dal/banner.dal.go | 83 + internal/mods/common/dal/help.dal.go | 83 + internal/mods/common/dal/metting_room.dal.go | 83 + .../mods/common/dal/metting_room_order.dal.go | 83 + internal/mods/common/dal/notice.dal.go | 83 + .../common/dal/surrounding_service.dal.go | 83 + .../dal/surrounding_service_type.dal.go | 83 + internal/mods/common/dal/web_site.dal.go | 83 + internal/mods/common/dal/work_order.dal.go | 83 + .../mods/common/dal/work_order_type.dal.go | 83 + internal/mods/common/main.go | 127 + internal/mods/common/schema/banner.go | 74 + internal/mods/common/schema/help.go | 59 + internal/mods/common/schema/metting_room.go | 70 + .../mods/common/schema/metting_room_order.go | 74 + internal/mods/common/schema/notice.go | 67 + .../mods/common/schema/surrounding_service.go | 76 + .../common/schema/surrounding_service_type.go | 62 + internal/mods/common/schema/web_site.go | 55 + internal/mods/common/schema/work_order.go | 77 + .../mods/common/schema/work_order_type.go | 61 + internal/mods/common/wire.go | 42 + internal/mods/customer/api/balance.api.go | 131 + internal/mods/customer/api/customer.api.go | 131 + internal/mods/customer/biz/balance.biz.go | 104 + internal/mods/customer/biz/customer.biz.go | 104 + internal/mods/customer/dal/balance.dal.go | 83 + internal/mods/customer/dal/customer.dal.go | 83 + internal/mods/customer/main.go | 55 + internal/mods/customer/schema/balance.go | 74 + internal/mods/customer/schema/customer.go | 76 + internal/mods/customer/wire.go | 18 + internal/mods/mods.go | 135 + internal/mods/product/api/order.api.go | 131 + internal/mods/product/api/product.api.go | 131 + .../mods/product/api/product_category.api.go | 131 + internal/mods/product/api/shop.api.go | 131 + internal/mods/product/biz/order.biz.go | 104 + internal/mods/product/biz/product.biz.go | 104 + .../mods/product/biz/product_category.biz.go | 104 + internal/mods/product/biz/shop.biz.go | 104 + internal/mods/product/dal/order.dal.go | 83 + internal/mods/product/dal/product.dal.go | 83 + .../mods/product/dal/product_category.dal.go | 83 + internal/mods/product/dal/shop.dal.go | 83 + internal/mods/product/main.go | 73 + internal/mods/product/schema/order.go | 57 + internal/mods/product/schema/product.go | 83 + .../mods/product/schema/product_category.go | 61 + internal/mods/product/schema/shop.go | 76 + internal/mods/product/wire.go | 24 + internal/mods/rbac/api/logger.api.go | 45 + internal/mods/rbac/api/login.api.go | 182 + internal/mods/rbac/api/menu.api.go | 132 + internal/mods/rbac/api/role.api.go | 133 + internal/mods/rbac/api/user.api.go | 152 + internal/mods/rbac/biz/logger.biz.go | 31 + internal/mods/rbac/biz/login.biz.go | 388 + internal/mods/rbac/biz/menu.biz.go | 515 + internal/mods/rbac/biz/role.biz.go | 179 + internal/mods/rbac/biz/user.biz.go | 227 + internal/mods/rbac/casbin.go | 222 + internal/mods/rbac/dal/logger.dal.go | 64 + internal/mods/rbac/dal/menu.dal.go | 165 + internal/mods/rbac/dal/menu_resource.dal.go | 101 + internal/mods/rbac/dal/role.dal.go | 100 + internal/mods/rbac/dal/role_menu.dal.go | 98 + internal/mods/rbac/dal/user.dal.go | 124 + internal/mods/rbac/dal/user_role.dal.go | 108 + internal/mods/rbac/main.go | 118 + internal/mods/rbac/schema/logger.go | 53 + internal/mods/rbac/schema/login.go | 38 + internal/mods/rbac/schema/menu.go | 178 + internal/mods/rbac/schema/menu_resource.go | 56 + internal/mods/rbac/schema/role.go | 80 + internal/mods/rbac/schema/role_menu.go | 54 + internal/mods/rbac/schema/user.go | 105 + internal/mods/rbac/schema/user_role.go | 74 + internal/mods/rbac/wire.go | 31 + internal/swagger/docs.go | 9074 +++++++++++++++++ internal/swagger/swagger.json | 9048 ++++++++++++++++ internal/swagger/swagger.yaml | 5604 ++++++++++ internal/utility/prom/prom.go | 36 + internal/wirex/injector.go | 129 + internal/wirex/wire.go | 27 + internal/wirex/wire_gen.go | 415 + main.go | 35 + pkg/ai/alBaiLian.go | 167 + 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 | 135 + 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/baseModel.go | 22 + 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 + qqgroup.jpg | Bin 0 -> 106873 bytes scripts/restart.sh | 2 + scripts/start.sh | 1 + scripts/stop.sh | 1 + swagger.png | Bin 0 -> 326795 bytes test/menu_test.go | 58 + test/role_test.go | 82 + test/test.go | 55 + test/user_test.go | 108 + wechat.jpg | Bin 0 -> 14766 bytes 223 files changed, 44475 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 demo.png 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/activity/api/activity.api.go create mode 100644 internal/mods/activity/api/activity_category.api.go create mode 100644 internal/mods/activity/api/reciprocity.api.go create mode 100644 internal/mods/activity/biz/activity.biz.go create mode 100644 internal/mods/activity/biz/activity_category.biz.go create mode 100644 internal/mods/activity/biz/reciprocity.biz.go create mode 100644 internal/mods/activity/dal/activity.dal.go create mode 100644 internal/mods/activity/dal/activity_category.dal.go create mode 100644 internal/mods/activity/dal/reciprocity.dal.go create mode 100644 internal/mods/activity/main.go create mode 100644 internal/mods/activity/schema/activity.go create mode 100644 internal/mods/activity/schema/activity_category.go create mode 100644 internal/mods/activity/schema/reciprocity.go create mode 100644 internal/mods/activity/wire.go create mode 100644 internal/mods/ai/api/ai_request.api.go create mode 100644 internal/mods/ai/biz/ai_request.biz.go create mode 100644 internal/mods/ai/dal/ai_request.dal.go create mode 100644 internal/mods/ai/main.go create mode 100644 internal/mods/ai/schema/ai_request.go create mode 100644 internal/mods/ai/wire.go create mode 100644 internal/mods/app/api/app.api.go create mode 100644 internal/mods/app/biz/app.biz.go create mode 100644 internal/mods/app/dal/app.dal.go create mode 100644 internal/mods/app/main.go create mode 100644 internal/mods/app/schema/app.go create mode 100644 internal/mods/app/wire.go create mode 100644 internal/mods/common/api/banner.api.go create mode 100644 internal/mods/common/api/help.api.go create mode 100644 internal/mods/common/api/metting_room.api.go create mode 100644 internal/mods/common/api/metting_room_order.api.go create mode 100644 internal/mods/common/api/notice.api.go create mode 100644 internal/mods/common/api/surrounding_service.api.go create mode 100644 internal/mods/common/api/surrounding_service_type.api.go create mode 100644 internal/mods/common/api/web_site.api.go create mode 100644 internal/mods/common/api/work_order.api.go create mode 100644 internal/mods/common/api/work_order_type.api.go create mode 100644 internal/mods/common/biz/banner.biz.go create mode 100644 internal/mods/common/biz/help.biz.go create mode 100644 internal/mods/common/biz/metting_room.biz.go create mode 100644 internal/mods/common/biz/metting_room_order.biz.go create mode 100644 internal/mods/common/biz/notice.biz.go create mode 100644 internal/mods/common/biz/surrounding_service.biz.go create mode 100644 internal/mods/common/biz/surrounding_service_type.biz.go create mode 100644 internal/mods/common/biz/web_site.biz.go create mode 100644 internal/mods/common/biz/work_order.biz.go create mode 100644 internal/mods/common/biz/work_order_type.biz.go create mode 100644 internal/mods/common/dal/banner.dal.go create mode 100644 internal/mods/common/dal/help.dal.go create mode 100644 internal/mods/common/dal/metting_room.dal.go create mode 100644 internal/mods/common/dal/metting_room_order.dal.go create mode 100644 internal/mods/common/dal/notice.dal.go create mode 100644 internal/mods/common/dal/surrounding_service.dal.go create mode 100644 internal/mods/common/dal/surrounding_service_type.dal.go create mode 100644 internal/mods/common/dal/web_site.dal.go create mode 100644 internal/mods/common/dal/work_order.dal.go create mode 100644 internal/mods/common/dal/work_order_type.dal.go create mode 100644 internal/mods/common/main.go create mode 100644 internal/mods/common/schema/banner.go create mode 100644 internal/mods/common/schema/help.go create mode 100644 internal/mods/common/schema/metting_room.go create mode 100644 internal/mods/common/schema/metting_room_order.go create mode 100644 internal/mods/common/schema/notice.go create mode 100644 internal/mods/common/schema/surrounding_service.go create mode 100644 internal/mods/common/schema/surrounding_service_type.go create mode 100644 internal/mods/common/schema/web_site.go create mode 100644 internal/mods/common/schema/work_order.go create mode 100644 internal/mods/common/schema/work_order_type.go create mode 100644 internal/mods/common/wire.go create mode 100644 internal/mods/customer/api/balance.api.go create mode 100644 internal/mods/customer/api/customer.api.go create mode 100644 internal/mods/customer/biz/balance.biz.go create mode 100644 internal/mods/customer/biz/customer.biz.go create mode 100644 internal/mods/customer/dal/balance.dal.go create mode 100644 internal/mods/customer/dal/customer.dal.go create mode 100644 internal/mods/customer/main.go create mode 100644 internal/mods/customer/schema/balance.go create mode 100644 internal/mods/customer/schema/customer.go create mode 100644 internal/mods/customer/wire.go create mode 100644 internal/mods/mods.go create mode 100644 internal/mods/product/api/order.api.go create mode 100644 internal/mods/product/api/product.api.go create mode 100644 internal/mods/product/api/product_category.api.go create mode 100644 internal/mods/product/api/shop.api.go create mode 100644 internal/mods/product/biz/order.biz.go create mode 100644 internal/mods/product/biz/product.biz.go create mode 100644 internal/mods/product/biz/product_category.biz.go create mode 100644 internal/mods/product/biz/shop.biz.go create mode 100644 internal/mods/product/dal/order.dal.go create mode 100644 internal/mods/product/dal/product.dal.go create mode 100644 internal/mods/product/dal/product_category.dal.go create mode 100644 internal/mods/product/dal/shop.dal.go create mode 100644 internal/mods/product/main.go create mode 100644 internal/mods/product/schema/order.go create mode 100644 internal/mods/product/schema/product.go create mode 100644 internal/mods/product/schema/product_category.go create mode 100644 internal/mods/product/schema/shop.go create mode 100644 internal/mods/product/wire.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/menu.api.go create mode 100644 internal/mods/rbac/api/role.api.go create mode 100644 internal/mods/rbac/api/user.api.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/menu.biz.go create mode 100644 internal/mods/rbac/biz/role.biz.go create mode 100644 internal/mods/rbac/biz/user.biz.go create mode 100644 internal/mods/rbac/casbin.go create mode 100644 internal/mods/rbac/dal/logger.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/role.dal.go create mode 100644 internal/mods/rbac/dal/role_menu.dal.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/main.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/menu.go create mode 100644 internal/mods/rbac/schema/menu_resource.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/user.go create mode 100644 internal/mods/rbac/schema/user_role.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/ai/alBaiLian.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/baseModel.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 qqgroup.jpg create mode 100644 scripts/restart.sh create mode 100644 scripts/start.sh create mode 100644 scripts/stop.sh create mode 100644 swagger.png create mode 100644 test/menu_test.go create mode 100644 test/role_test.go create mode 100644 test/test.go create mode 100644 test/user_test.go create mode 100644 wechat.jpg 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..27b250a --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,13 @@ +FROM mcr.microsoft.com/devcontainers/go:1.22-bookworm + +ARG APP=jinshan_community + +# 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..1bf380c --- /dev/null +++ b/.gitignore @@ -0,0 +1,32 @@ +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, build with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +*.DS_Store +/jinshan_community +/jinshan_community_linux_amd64 +/jinshan_community.lock +/release +/data +/internal/test/data +tmp +/vendor +/configs/gen_rbac_policy.csv +/configs/gen_rbac_policy.csv.bak +/configs/rbac_policy.csv.bak +/test/data +/internal/swagger/v3/.openapi-generator +/internal/swagger/v3/.openapi-generator-ignore + +# IDE configs +.idea +.vscode \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..8f2c939 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,28 @@ +FROM golang:alpine as builder + +ARG APP=jinshan_community +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=jinshan_community +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 ["jinshan_community", "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..e172c03 --- /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 = jinshan_community +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) --daemon + +stop: + ./$(SERVER_BIN) stop diff --git a/README.md b/README.md new file mode 100644 index 0000000..f772a10 --- /dev/null +++ b/README.md @@ -0,0 +1,28 @@ +# jinshan_community + +> 近山社区小程序平台 + +## 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..d992f20 --- /dev/null +++ b/cmd/start.go @@ -0,0 +1,105 @@ +package cmd + +import ( + "context" + "fmt" + "os" + "os/exec" + "path/filepath" + "strings" + + "gitlab.guxuan.icu/jinshan_community/internal/bootstrap" + "gitlab.guxuan.icu/jinshan_community/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..a76715b --- /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/jinshan_community.log" +MaxBackups = 20 # Files +MaxSize = 64 # MB + +[[Logger.Hooks]] +Enable = true +Level = "info" +Type = "gorm" # gorm +MaxBuffer = 1024 +MaxThread = 2 + +[Logger.Hooks.Options] +Debug = "false" +DBType = "mysql" # sqlite3/mysql/postgres +DSN = "jinshan:jinshan@tcp(115.239.217.220:3306)/jinshan?charset=utf8mb4&parseTime=True&loc=Local" +MaxOpenConns = "16" +MaxIdleConns = "4" +MaxLifetime = "86400" +MaxIdleTime = "7200" diff --git a/configs/dev/middleware.toml b/configs/dev/middleware.toml new file mode 100644 index 0000000..833d32a --- /dev/null +++ b/configs/dev/middleware.toml @@ -0,0 +1,76 @@ +[Middleware] + +[Middleware.Recovery] +Skip = 3 + +[Middleware.CORS] +Enable = false +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"] +SigningMethod = "HS512" # HS256/HS384/HS512 +SigningKey = "XnEsT0S@" # Secret key +OldSigningKey = "" # Old secret key (For change secret key) +Expired = 86400 # seconds + +[Middleware.Auth.Store] +Type = "redis" # memory/badger/redis +Delimiter = ":" + +[Middleware.Auth.Store.Memory] +CleanupInterval = 60 # seconds + +[Middleware.Auth.Store.Badger] +Path = "data/auth" + +[Middleware.Auth.Store.Redis] +Addr = "115.239.217.220:6379" # If empty, then use the same configuration as Storage.Cache.Redis +Username = "" +Password = "123456" +DB = 5 + +[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 = "115.239.217.220:6379" # If empty, then use the same configuration as Storage.Cache.Redis +Username = "" +Password = "123456" +DB = 4 + +[Middleware.Casbin] +Disable = true +SkippedPathPrefixes = ["/api/v1/captcha/", "/api/v1/login", "/api/v1/current/"] +LoadThread = 2 +AutoLoadInterval = 3 # seconds +ModelFile = "rbac_model.conf" +GenPolicyFile = "gen_rbac_policy.csv" diff --git a/configs/dev/server.toml b/configs/dev/server.toml new file mode 100644 index 0000000..7f58496 --- /dev/null +++ b/configs/dev/server.toml @@ -0,0 +1,89 @@ +[General] +AppName = "jinshan_community" +Version = "v10.1.0" +Debug = true +PprofAddr = "" # Pprof monitor address, "localhost:6060" +DisableSwagger = false +DisablePrintConfig = false +DefaultLoginPwd = "6351623c8cef86fefabfa7da046fc619" # MD5("abc-123") +MenuFile = "menu_cn.json" # Or use "menu_cn.json" +DenyOperateMenu = false + +[General.HTTP] +Addr = ":8071" +ShutdownTimeout = 10 +ReadTimeout = 60 +WriteTimeout = 60 +IdleTimeout = 10 +CertFile = "" +KeyFile = "" + +[General.Root] # Super Administrator Account +ID = "root" +Username = "admin" +Password = "6351623c8cef86fefabfa7da046fc619" # MD5("abc-123") +Name = "Admin" + +[Storage] + +[Storage.Cache] +Type = "redis" # memory/badger/redis +Delimiter = ":" + +[Storage.Cache.Memory] +CleanupInterval = 60 + +[Storage.Cache.Badger] +Path = "data/cache" + +[Storage.Cache.Redis] +Addr = "115.239.217.220:6379" +Username = "" +Password = "123456" +DB = 6 + +[Storage.DB] +Debug = true +Type = "mysql" # sqlite3/mysql/postgres +# SQLite3 DSN +#DSN = "data/jinshan_community.db" +# MySQL DSN +DSN = "jinshan:jinshan@tcp(115.239.217.220:3306)/jinshan?charset=utf8mb4&parseTime=True&loc=Local" +# PostgreSQL DSN +# DSN = "host=db user=postgres password=123456 dbname=jinshan_community port=5432 sslmode=disable TimeZone=Asia/Shanghai" +MaxLifetime = 86400 +MaxIdleTime = 3600 +MaxOpenConns = 100 +MaxIdleConns = 50 +TablePrefix = "" +AutoMigrate = true + +[Util] + +[Util.Captcha] +Length = 4 +Width = 400 +Height = 160 +CacheType = "memory" # memory/redis + +[Util.Captcha.Redis] +Addr = "115.239.217.220:6379" # If empty, then use the same configuration as Storage.Cache.Redis +Username = "" +Password = "123456" +DB = 1 +KeyPrefix = "captcha:" + +[Util.Prometheus] +Enable = false +Port = 9100 +BasicUsername = "admin" +BasicPassword = "admin" +LogApis = [] # Log APIs, e.g. ["/api/v1/users"] +LogMethods = [] # Log HTTP methods, e.g. ["GET"] +DefaultCollect = true + +[Dictionary] +UserCacheExp = 4 # hours +[FileConfig] +UploadDir = "./uploads" +StaticPrefix = "/static" diff --git a/configs/menu.json b/configs/menu.json new file mode 100644 index 0000000..1e458c2 --- /dev/null +++ b/configs/menu.json @@ -0,0 +1,240 @@ +[ + { + "code": "home", + "name": "Home", + "sequence": 90, + "type": "page", + "path": "/home", + "status": "enabled" + }, + { + "code": "system", + "name": "System", + "sequence": 10, + "type": "page", + "path": "/system", + "status": "enabled", + "children": [ + { + "code": "menu", + "name": "Menu", + "sequence": 90, + "type": "page", + "path": "/system/menu", + "status": "enabled", + "children": [ + { + "code": "add", + "name": "Add", + "sequence": 9, + "type": "button", + "status": "enabled", + "resources": [ + { + "method": "POST", + "path": "/api/v1/menus" + } + ] + }, + { + "code": "edit", + "name": "Edit", + "sequence": 8, + "type": "button", + "status": "enabled", + "resources": [ + { + "method": "PUT", + "path": "/api/v1/menus/{id}" + } + ] + }, + { + "code": "delete", + "name": "Delete", + "sequence": 7, + "type": "button", + "status": "enabled", + "resources": [ + { + "method": "DELETE", + "path": "/api/v1/menus/{id}" + } + ] + }, + { + "code": "search", + "name": "Search", + "sequence": 6, + "type": "button", + "status": "enabled" + } + ], + "resources": [ + { + "method": "GET", + "path": "/api/v1/menus" + }, + { + "method": "GET", + "path": "/api/v1/menus/{id}" + } + ] + }, + { + "code": "role", + "name": "Role", + "sequence": 80, + "type": "page", + "path": "/system/role", + "status": "enabled", + "children": [ + { + "code": "add", + "name": "Add", + "sequence": 9, + "type": "button", + "status": "enabled", + "resources": [ + { + "method": "POST", + "path": "/api/v1/roles" + } + ] + }, + { + "code": "edit", + "name": "Edit", + "sequence": 8, + "type": "button", + "status": "enabled", + "resources": [ + { + "method": "PUT", + "path": "/api/v1/roles/{id}" + } + ] + }, + { + "code": "delete", + "name": "Delete", + "sequence": 7, + "type": "button", + "status": "enabled", + "resources": [ + { + "method": "DELETE", + "path": "/api/v1/roles/{id}" + } + ] + }, + { + "code": "search", + "name": "Search", + "sequence": 6, + "type": "button", + "status": "enabled" + } + ], + "resources": [ + { + "method": "GET", + "path": "/api/v1/menus" + }, + { + "method": "GET", + "path": "/api/v1/roles" + }, + { + "method": "GET", + "path": "/api/v1/roles/{id}" + } + ] + }, + { + "code": "user", + "name": "User", + "sequence": 70, + "type": "page", + "path": "/system/user", + "status": "enabled", + "children": [ + { + "code": "add", + "name": "Add", + "sequence": 9, + "type": "button", + "status": "enabled", + "resources": [ + { + "method": "POST", + "path": "/api/v1/users" + } + ] + }, + { + "code": "edit", + "name": "Edit", + "sequence": 8, + "type": "button", + "status": "enabled", + "resources": [ + { + "method": "PUT", + "path": "/api/v1/users/{id}" + } + ] + }, + { + "code": "delete", + "name": "Delete", + "sequence": 7, + "type": "button", + "status": "enabled", + "resources": [ + { + "method": "DELETE", + "path": "/api/v1/users/{id}" + } + ] + }, + { + "code": "search", + "name": "Search", + "sequence": 6, + "type": "button", + "status": "enabled" + } + ], + "resources": [ + { + "method": "GET", + "path": "/api/v1/roles" + }, + { + "method": "GET", + "path": "/api/v1/users" + }, + { + "method": "GET", + "path": "/api/v1/users/{id}" + } + ] + }, + { + "code": "logger", + "name": "Logger", + "sequence": 10, + "type": "page", + "path": "/system/logger", + "status": "enabled", + "resources": [ + { + "method": "GET", + "path": "/api/v1/loggers" + } + ] + } + ] + } +] \ No newline at end of file diff --git a/configs/menu_cn.json b/configs/menu_cn.json new file mode 100644 index 0000000..2b3fad4 --- /dev/null +++ b/configs/menu_cn.json @@ -0,0 +1,240 @@ +[ + { + "code": "home", + "name": "首页", + "sequence": 90, + "type": "page", + "path": "/home", + "status": "enabled" + }, + { + "code": "system", + "name": "系统管理", + "sequence": 10, + "type": "page", + "path": "/system", + "status": "enabled", + "children": [ + { + "code": "menu", + "name": "菜单管理", + "sequence": 90, + "type": "page", + "path": "/system/menu", + "status": "enabled", + "children": [ + { + "code": "add", + "name": "增加", + "sequence": 9, + "type": "button", + "status": "enabled", + "resources": [ + { + "method": "POST", + "path": "/api/v1/menus" + } + ] + }, + { + "code": "edit", + "name": "编辑", + "sequence": 8, + "type": "button", + "status": "enabled", + "resources": [ + { + "method": "PUT", + "path": "/api/v1/menus/{id}" + } + ] + }, + { + "code": "delete", + "name": "删除", + "sequence": 7, + "type": "button", + "status": "enabled", + "resources": [ + { + "method": "DELETE", + "path": "/api/v1/menus/{id}" + } + ] + }, + { + "code": "search", + "name": "查询", + "sequence": 6, + "type": "button", + "status": "enabled" + } + ], + "resources": [ + { + "method": "GET", + "path": "/api/v1/menus" + }, + { + "method": "GET", + "path": "/api/v1/menus/{id}" + } + ] + }, + { + "code": "role", + "name": "角色管理", + "sequence": 80, + "type": "page", + "path": "/system/role", + "status": "enabled", + "children": [ + { + "code": "add", + "name": "增加", + "sequence": 9, + "type": "button", + "status": "enabled", + "resources": [ + { + "method": "POST", + "path": "/api/v1/roles" + } + ] + }, + { + "code": "edit", + "name": "编辑", + "sequence": 8, + "type": "button", + "status": "enabled", + "resources": [ + { + "method": "PUT", + "path": "/api/v1/roles/{id}" + } + ] + }, + { + "code": "delete", + "name": "删除", + "sequence": 7, + "type": "button", + "status": "enabled", + "resources": [ + { + "method": "DELETE", + "path": "/api/v1/roles/{id}" + } + ] + }, + { + "code": "search", + "name": "查询", + "sequence": 6, + "type": "button", + "status": "enabled" + } + ], + "resources": [ + { + "method": "GET", + "path": "/api/v1/menus" + }, + { + "method": "GET", + "path": "/api/v1/roles" + }, + { + "method": "GET", + "path": "/api/v1/roles/{id}" + } + ] + }, + { + "code": "user", + "name": "用户管理", + "sequence": 70, + "type": "page", + "path": "/system/user", + "status": "enabled", + "children": [ + { + "code": "add", + "name": "增加", + "sequence": 9, + "type": "button", + "status": "enabled", + "resources": [ + { + "method": "POST", + "path": "/api/v1/users" + } + ] + }, + { + "code": "edit", + "name": "编辑", + "sequence": 8, + "type": "button", + "status": "enabled", + "resources": [ + { + "method": "PUT", + "path": "/api/v1/users/{id}" + } + ] + }, + { + "code": "delete", + "name": "删除", + "sequence": 7, + "type": "button", + "status": "enabled", + "resources": [ + { + "method": "DELETE", + "path": "/api/v1/users/{id}" + } + ] + }, + { + "code": "search", + "name": "查询", + "sequence": 6, + "type": "button", + "status": "enabled" + } + ], + "resources": [ + { + "method": "GET", + "path": "/api/v1/roles" + }, + { + "method": "GET", + "path": "/api/v1/users" + }, + { + "method": "GET", + "path": "/api/v1/users/{id}" + } + ] + }, + { + "code": "logger", + "name": "日志查询", + "sequence": 10, + "type": "page", + "path": "/system/logger", + "status": "enabled", + "resources": [ + { + "method": "GET", + "path": "/api/v1/loggers" + } + ] + } + ] + } +] \ No newline at end of file diff --git a/configs/rbac_model.conf b/configs/rbac_model.conf new file mode 100644 index 0000000..6ede185 --- /dev/null +++ b/configs/rbac_model.conf @@ -0,0 +1,14 @@ +[request_definition] +r = sub, obj, act + +[policy_definition] +p = sub, obj, act + +[policy_effect] +e = some(where (p.eft == allow)) # Passes auth if any of the policies allows + +[role_definition] +g = _, _ + +[matchers] +m = g(r.sub, p.sub) && r.sub == p.sub && (keyMatch2(r.obj, p.obj) || keyMatch3(r.obj, p.obj)) && r.act == p.act diff --git a/demo.png b/demo.png new file mode 100644 index 0000000000000000000000000000000000000000..d6a9666382b529598e40004febb189f7136709e7 GIT binary patch literal 325393 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�X%`smb2#lo1zaCvU*!LTOZeOR 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

rx7I4Hg3eZ*oJc4Pv&crd% z7vpVdodauH*ff9HfP#TPS$syYiZ7H=;GMtco37hEY2FjY%Up1|&&4EV=$A0wB5Bx> z@YmZezNgfdKfj{CY8pGr9_;L%z0>U`4_+b~gEI-^CY-CGDeZKipEP;RwPehE0JRkY04%=@U@I?SEA?c^HghV?{gL`_%`6QDc$qhS=-I@d|2Ihc6qEK^_NBZI&P}))hAza}c zIyB9BJy^4~H^Mu3Vt7w~LfF*(T&tMut8RvC{B=n6xmKWOMwYL6T%UHg+3ktPoY#j= zJi=YJfcvJmz``}_I9WM)4)pLJ(Sp!hLMQU&%8-QhKrm1KSxru*(j)RgFl~f*b&FAkcv8D=S z6HPgiTHW(c4ZH)$MBg6}%8cs&JOW#wK9w>4y8RbP0tq#AE>tpG(EdZWuWIsX{0H6_ z*K$sv$JzqD!ludQIJ1(Q(t#_YznJXd+^qwGuKDRp!qPN=Xk)+PPR< zhMx<{eL*57p{Qu5_z?B@qQHK|*U4E85$FT%w8md_aJ)O?zDrEB%|1Gu6PkTceV+=t zFI<78qTGSy{SFku=G1w5#HO+@eylx!Z^54aEm3?ofv{?~Z6w<~9pH~0$!#I-Cd2qt z5rE0kK4dDm^0%MEV)aUYjO3Uit^^-8U`||;mPO#aFW|AGebSIE-P_1yFqC=a;CNO= zGF_jnhR?VRtjWvt0uKi$$xfv#hn3HiT-nUW=RL4q2W+Q)uMj5yk3R6h(tTm$-L!y@ zD7aKr#Sp^*Hh9R@n)I+d8JGB81u#v^vECq+GM2UcwIqX5NS6*fCB6NBam|zvec$CN zi$Pvk!r)53v>=}1GG7*6d=tD!wEqp1IIf>L^)Te{j>Lx*B^jVOombhlie$nYrXOzi zT=xDa(Mx*xKVKRZ+?kNU(|6~iQESs6^z>6^s8`s_D%sPSqk1|5w&>1KqML-&1w8_7 zZsHqXRSaL|I!3f*P^o!tNaq<#kU9LWm#PdN^0#L=XqJWv(E`uAJ@*!~6K1F<4h_=bsYFgiW4ROiS} zko#tha;j~|+>`eO>i_IVjrW51{8nzHz!9+=N?0;Eo1PzBk5f9$=v{Mbu9P)x!IH|Y z=Q?!8Dx9bWZ&s)21<4N~_{&E(XQQ_0>wO7D*=hKrD>Y-WLa`%i0i`*{wsg(8Qx_0A z*fzVU+f7=z6kLjH*RbE_RNw&SgI63PSkU*pB9$La)vvvYQ&C`6UA)R3)v}VbbM!1X zyxStxNo+XRvZa3h1FuP>q?(kH`9tXd8!-m21j~2ua}>|IML8T%(Sc_39ZE`-J)fg+ zGNEsme(wAFvRVsal%w%`^vO@(*X@q0ul834FZ!hk7NRWCOFvOHv$s@a_5T}&p%kP`NolDe1SAJg zLQoJD=}u|sW=K&Qq$NZ`r9naj1}Tviq#Ft8?s)FOea^GbIp4j{KEJJh@B(LM&02R} z_Z6SFB>r)}m(96D?NMv6Lk@XEs799Ec&s61g~gr*!m_Y`wLbK$hc*ac08d6^&9Q4A1Cw(k@WPn6|F2t|9Y{} zN3ckDaF%`Z!IJ%Ryp)vZSsHN3oy#^1f@8mxBfpw<-mD`>%3c5F~R4W zaFaR+NeLG(bDsCX*U7bcNT2AKt@{`^dl`(E@6}Y>g-z%t?MNr&e0fsmvHNyEoJ)4? z!OY8@wA!^7?DmMg@^6=%-_&89%l>I9(_@+mx_^H^49}IhuP3?H&Og&=1r69t?{1cc zV@aVYy;aLEqGN}Z3@?34efOT{TO7@$uAPA@%C|<$MSFesmBUVMLR}+YhA)S`v0H8J zl=T^~BY@m(d9cQcU_ZV?n0Hf9htE1E-2-BJo=F8t2PBhT!?WF&u6!QA8Jwj}5>)Zq zr98W7=Y1{I>*~|AkdnukhlH*-C;9ifQ5axX;N_b!er7@Q@vbkyO1Uv6%J}|Fp-+qu z&`s~j%y+V8WoTmvrY3H=44XohS@cf(?>xCPb!35p2U9H%_IkZ}PZvfj$ImS^ik|@U zS5V#OgiS$rLV?OuUJM8m#)p&0*no2cFT89kkm&H@fGK1?v5ztWxWWXm8i)xMMQ@O} zt8}H0Hp{@MBPy6cNlP`loFO-oMxm?T&t-VR4Je~(_9yQ@m&1bqw) z{TQf+eN~|?7*PXSA=lvj6EXwKb?|)7;@0H2Ry0?_~;oxaWuR-gJ1Tc&QQJMblDJ5sF-o?!=p8PY<2mUH2AftFn_s z8LVm)lbVg+@pXj#9%UYCDQZBPy+eOI;p8RV4HHh`uz=qoX z;cK4Yic*PhBrPXbIbn~TYYykk3NOWr+uP)KsCjvBizx&7nV@NRE%O zChnrG&aYLk)h0Yl2)Tm$g@N)TRYX_NyWHyW+_8l@vJ-<_=RAe<)aRn4m;* zHShQ$H1U|`dh!CTho7?C>_Sj6!M7yw7`W#xvWKtHu_ofGc_t$5Ev#n%%%-aZ3fGO z#rT-$LM1lmCm~IZ$(8B6_4++d%4RR~j!yc?ClOf_6;_nh$fQ~2hbJd*S$UFX4g%*- z<@dKgrQyzyt9yhGd`Y7x+4#DqSKxqby+(KV!1Z{?Cs1{lZs_U3Ued(^y;Jg^`3RK8 z=zhGe>Y9&y(Tkx*>qYJ4^chGybFJ6nFZ3_d)^Kf`5X0LWQ2Wq!j_(A|$(tLV15MXo z;MI$9mSW0=T_%guFip&!*Uxj(TYBnAKzLjY6v&$7dg;nUbA2Iq*kSx_9+Kj zlx!+|_5HxaHBfs&jW=Q_J89stIBNg%ME%p>e%?jD{XonSrOOmmP$90|xL`_~D&q8b z*^yevMw`*r!ko&~K2^|Kvz<*!5W<&$Z_LZM4qyCW17<(zf~Bl}|DNp8QU=!mxh&b- zs`g1)Md~Hxnp1Oin^R>|#HWjn5_(JVtvusY!<*IySp)?VEmxu2b0ud%9}bYS{E%Q`uVYBDblog-5D?A3q+s5noPC5v8Ym3E|yjRE2 zH2l)`^tkBysb3blkFsN|cd21u7$Y%zA=PeebA<8hKep`tw#xb z*GZLyi@V+}YD-C)L9^FVywAMb{6?!XPhTO|$tiKE5C`0rZM8i?dx>^G>$3i{=}?Z& z^dFcDR@zSA9v*o~{wSjncX#vzx?VaNC=&<*{ro{ogWPM)5FwXoG77Q$rd>wm=WDr| z!t-+qozJ6x{{hcIN^b?&^{83%zFVuhZ^rr}g_l2oWNmV9=!^yFvUAI9*qacsLk0&hR zmoS5Z;XcMtT3Wf`y&3^Vy9s<9|3CO}w-v+~lebbIbaCn2{B%Z+Fj>KY`WWLi(|@==a1BDUDon9x&3_IU{XTp z-QZN_`beM27&<4)S$*?28tm`K^lu*M|GURf0jJ-;8B75Bt!2$j{KwQ!`S~Vt3Bz^j zB29L=0vvK2p?(E9$2p{`kp z(la>15)nP8-lYH1c>nqwzNZjTtRs{QK}mZ5==V4_=D<+Iu|qM#a!> zAu*&&Cc$}icj9&T|nj-hvojZhtk_Y_{6 zndt>rTfY4#(%<^7xRGR)`S~5~NV1%;yZ5!%&$Dm+*^7|c&zm~LPv^uLFsm=k?FKiL z+tmk0e++8yu8#P}^Z))%{;{XJLfBQ}CWm5gxvZCupJ`o_40p6TsUsTzn@Pvj$S$)P zx6Ol3Pxm2~q^2l@W`=4LwzrEW17{U%0!o)dDF4|8ig8WlT3{s4xy#}|)|LOM-~ah$b8dtu_xB{r zh|w#*sGl^o>Eqi1JwKoRL&oU;y~lrBL=khupDCU<%?&f|$^Pw&onHV!PLc4EsIrMj z>{nqWlkWH$BM(eR*!O-7rVr%_HRm8LflsQw`9>q={fjy$>2|t*D6&es;EnT_mw#%N z{tx2|5s3+z-xB!+E64dnfydx}7Zn*!wu&F6`9+VdsYRdG73e9UXKs!8_jmB0lLx^< z!sbjWQ~TiB^&y)o(Z4gVfA`t4a(Jx@&H*7F)Mh0DA@^$tqQF1VWXM}ag*{50GyCn4 zo^?2lJn>al-(y8dhT_s=g%lBYKXwEN9F zWs0lMenJ17cW~_OrbvQch&@k)KB?0F4BApDr_t*dt$g$Ws^riV*2}*RC*Hvb7^PrI z5$vL^V*UFg{pVC6eAaXoWtRRG9^YS^Ujkvj8KW5%&Yx+1QES2xgz{J^CeS$P|8e~N z;YyaIke->z_}!sOn|~$d=+FFQ|3g#5+b(|JI*7Cd|J-oU|M0v2w-?M=P;yeA{SX~E zqe`qzziNP{t6w9=(N_6&4Jx1h-~xl*msh`#p{or!(EfaVfVN8Lf9Qli8>VHIaPIhv z2azXTzo^GiMKqB-O~R%0_pAhT58iMswOP_)1cI5rgm%*gvIv7+aBG{53j87@LQ=biq!A^mT2 z?>{~UEW6Jxj%7ka+`rPSlSsRG;91)}e1Z1Y(7{3Ql|^NlEy(lDgU2OfPh9@yL;qvV zHMd51W)N;NfVAUbI*earET<0v<|-#6;AD3=YyK*WOO4|5{FVm29rwWECEB`QH0bXz zt30DZBtMQHJuk!mN2~nD82KMw5I9*X{)ZL-n+fzFg$i-P^h;(a@QOZw1fb2!(9`}x z6XdE2XPh)}0(LwR8726y#&P;PWa#dXsUD#-DqYW`A1a(qj$S=Jfu03~&0$r z_*?I}UO)Y>{R4Xi9Ui`rk)NrKcjL)l!gVLf`qBf&XiY!bq=;F-yb`*o(li1kAl+R?Ks+O-!bZbl3V^@1_5$#?9LU^8JVyl$bqNCx}D)eNg@!L8H5-^ zSh{x_9C*m$lFVjdkG%Wz*Q9%G*H)(6*Y}h60_M;5T-#H`oNMnSd8ds@{r9H42fm7O zvz~HNwnqEzAKpEJhzm|==iyN;g(NdeM4JCICbvF4qTJF!1=ZGiIce_6*7|PuYwdsE z0`YjzSqi#DjZIu%3hbdWs{Ze>jzAF8PkvL?f~O4&qOOX;2tsSm5`CABSkk^f^s)F0 zoZp}bw5|RMBK&2%-G=$sP8B)EObIs}Oxu;Z$4X8%+_ki{D6c<$Fq!j*u{Qnv3*C?O z(=AW7f|WB=a_b@s|GUew;ikT+AnT6+*4tAb-;}blT6fKb*LrBOm(9wkd-Z4^R}ITO zF?c{OSa$Y!_r;}{-bEY07RDj@EbS|!_2&EXDdX%%V(2j_n8I6}D!M}pQW%vJEDNfz zoJMSv{;;wB(hKe^uz%)wa4YhI>Qq9{dHzzp>XSD^l3fYvc)e_m#KtoTIOWicg$x%DtvTh(-Ob4zY_ zl4Om|G@Of;P;6;M<-_)-%7??@C#lWeU52%rE#4!(lg(w?x6v`jiP%VorL`E6@1$oM zb@JH02uedsbYllV$jfCf&9tD99BYE%ps~pRx?ud-6OG|Kz^YsIl95xy!gyqgeiu=< zWnv(Il&>Z(oa8$hqG38&C$0lTW0GimWZ~;?T*w<m z@^QL227L7=2!su2GgLG5u9U`_pacCr>BX=!cCBzXqvN?F?_&yG>};nnvuD*N4Wf?kK)m0dsvOutZ4QKone z<9&x=F{LyGmI*>osff8BqL&lNW5CmX{+GxRJSiu>P{0@~hgh z4Q(gZ_O}{9HZCq)#h}}VgNxN1EDJ1o2&akVifWxNd{}Dfd^ib{12k%%QCa6&*C<^N zudHFA$6&hpGe5b`k2WC6m|2pYlPRHtgPng5r&@1>cD0iaQn%rZR@kP>N&74{dcP#FBT0$9YtLAJ{p<88ZK-OyF#%G zN)T;?0E*I;{a)l(sz=`14TMh;%jcoV=}IyZPHJGDo6lIMF)klg5eU zUJL5IE6RlxS`>D@U!8FoD6CEaj2GP*b)p{& zMd0&vt@>!S8e=|4N&`h96eG|(`10rzw&Z?2I$c^IU~%r2!s+?Y1yNKfwuBxOJWqr~+Ti&iU`HF%6&wl>!G#7bJGh81>!}0>Z2xpWtn-c3!XPoS4gs*S!o9 zkPG&!hC%hp^n`_;5=x9H7^Ri5wZ>@uZdkcl%?nhjgh@Hf7D}!oF{Yvwxp8~5?{N5h zSX`stx+}UuO0HoX^|tWnHA*fF5lrjOHVVjZQz(gseH53LRr}<#oCL9&j?=fI4#ANC zWv|8iW8SuPK*ikv&=96xJ?DPrgt`uS>H3h!s`7C_51Il~&g?uB z-uaCg%*GLD4Mpt4rKX}Ua`x#|nyL=Q=a&z^m77=-A7hbf2Vmhxao59Fyg+RS>N+6amL30iTjx+%3^XDFQDw>D0|z4?P}+na=3I-XLja~wz?;>M zz!2r0!jcBl+BL)N$y5s0g{c|3l$R>S5B(51byS}abzQ0uvq1XEu=vj4Qo)rm<0n7F zI+B}?qcMd6a6GZ8(wq4rmV|Aa6$WFz@s9ruKbO*@6ZH~jf4egb2F?nrJ-H5d)v3Ls z5`Yp;7_|+=D!Gs|i;^YcVvX!#X@1-`?=Tc9&E>L8yAC6C1#s(6sDc7ie(+#!s&iuU z6iPir0zg%^n@XtM~XHqJZiesELHET&kC_}Bdm1X zY-J7Z$&}k;E6N-fY}0Fa?i<~!EGdq~ux0D_{;Pc~kJf?c-a|PWrw8)~iiI&WNe*&F zTvAg|tLtAS;>Y!tSgl@vsW@GOg5^9N{0L0-#@t@ppxG4Dz>M#`LCgD#20x>Tx z+$9iBw>kwX@4GKUJnMj{v1ACzRl7!Zwx~6HTBTTIV?O8#Cp`1~Y-vb>-pS&lVlQql zpCa1YBV2Ek0LaYsCn#9YK;Gk}$q!ut%qVez8RxdWzzrg?g{>QacW;_ZU6Zwsi}BN);5 z`qDmdK&5@AF6u4V@am!RtlgPaIB;A?ua^4z66iQM(=W>tU^P!ISBrl7K7acJ`04M; zo50prlwW)riV^vA`fi8ovv7VLySJ%^r((P=?d?Z;T3#+COP&^+efzn!@G!H|-LUDN z7AG&>WEe}u)+L6DF+m-Wh7Go1_;0~|yaoKcuNSsueUl|Gn=h#A@0J3&Dse$iy9PN% zqEhRgbmchx%b^SHBB%Q^md^S+rgZK{v=IA29#bnL*$!FJ68CEH2^6I0+#7(BqO{y7#DC~~AxIXYHW zJOx%_6jccNb39E$yBFt&uk>=bmUn?2P*W;t=_^`~c)aN2Cy+ ziq9a5IzL@`QS)`Mu;O$+o6`5=&;re64}V2ejxHCV7hj0U;;#~+^2P0ECiHLZ1!Uau z^>f~EGhw?t8hT8|!;4JmsqJV!i(Oz+7`11A0NNETt>19inBm|^gHlYhKbCplI)r33N+&ITu(HL zBsUsK8t8zaRS?9dt)k?;+@K#z3U~^^+I{c#kY-7%wiLEra?S{(`Hlw@%{}K z%1Sj;`0$NZNV^%MNwSqO-DMq9P#?n^8QMPaF0bTvf?2BD<65WDO8bX;tZ2zc&dr9r zA>z_{RwC&lLFfimg1;?UN!YZoUD zP?c`Sv`h;zA8?$YEK7`;JTZGwC|+@`9Du+Uc1FEVNpD4gGx~2;-sQa%vqXxXtL;$L zyy<-yyRenvEfqJtdi$}E@z+>^42+3jjE94dSfcbdwfGiyU*p9}Deoda5IH5H^mDcw zam|JUacQ&f3IHu-VJUnkwdP*cF8TS0Lzejn1v%AjH|51b8l)` z592qDgVcAD@?G7#PGZMgpUXMtQpdTzx5{~Cz1^zzLn$knoEp6yuV^kd-CMC`#DR;N zEe?T%7;MQoAI!&P%ml^a{McK-LuD?ADD4#CaQ+V4qM|`>znduHU&(-4m#MIWd+eL? zvIUj_O~JCp8qZu1<3I935NgEwYSw&c{FHt}Q$K`RV4?M1&`7683reQGZ2lyz1y2Cf zr$y@R4=-b6#@OU%#)wc2?ge=>LhZ-4@@)q{`2{WzPZysht%+wb!r9kh$R9v}{;732 zN;M@O-3#xvJ22&py3u1evaN_e#;=)T>F_7iXfixC0L7!cGuZy0TT`Wqq^=nUPrG4B z$B3iSV!3g@?jK<#3Z_pv$%4nYAJBUAAzii+7bu5)iaRp8yOaXz6D0JDg8n$occn& zC*B}ydJt|eFeoIO=C5HOT2tlH(VgZ|G+$HCJLqB1$AqIYI!XHa5Mg!O_i%kbPaM+p zut50mEpiQ}{xjS>2G~;{I`93dDPZ1&t_VU&`Y)<~r~GVp8;)ZwZhXa9&ti@0sWg&R z>Scmq@#vGbCVET0{%kzEo$@vh97Bfn1C$|e6=9fiF>uT7GedAAy%h_JVBG#LrQ$S6 zC6%6xP>(H*>Cv1nVvQle&whTm`am$xw;xpK7Cq7k zYt?N>YS zP=H_A`#eU*Qf4SEb7}+N#cS`L7;=SU$DnFpBMpSTRzGOD94uMT0C+JtO>K=mb%4;};XhrQls*V|{8mgrrhrS%y=O_HkFq)0=ir{0X; z!&AAjpx@BNUax6kgn?d$%N#qrRs$MlFNN7JcP{~; zr7ddvaiiJhkrazM1o0*lW`P;=Db@_mvJk_I^o+{|*kY#j6nAT7MZF`6AzG&!5)?|g zGVAsx z*supYPUGaM=WU-ZIo?)NZrfOs?~3oD-SOy*`EI#rau7}IN(BuvnzpqT4qUOjgy$zi zEV*+|Mee?hFpc(2*2sR*BBpjUEoRoPo4{Yb9ZR_D-S%lH5_@EVpC@QNinBPLX(GaQ zkW5g$*t2H!F;4Qw@@VEWH6!BnQl5N|Rp*l<2CNaYk=oQdwN&F}Ho3+T%ch=F?MMwR z?b)!Q9qat-a?XQIEBRYNDjEYzbDvdov=FUa-8#6Q3})>0QG>j(rD`%!{?wcA-dfhfIc6fAj&4riQYJxRam*twVQ6bK7k zaD0m-nAg30y4Q2@N~#2ImDrarZniwi+a@eiUIW6O$hR%WyG8J;8}_{=8s675kV+Yz zrv0^nX2<8KGr*3tW@|620LN-5vE}_~Q_bK+oy4jLV*^tyMxtVM`Hu!_{QwoiTmFJ{ zX_3i|Frwb7feBX|=!M%m_jwZYU>vn4^0RizH!D>#Rg#a`%KI|)_V3c7JSyy zwATKqxj06?8g(oO?G zA#P+@>-7Z71|We$VaCCgica!jot`Lx%Es+`jL?D2bW+!z1jY%`nTA!`%+a>an){>W zoT=f@n@k&XB+nGozMrq}YvZxdl<;6n@)jvW-i=IZZ2yp=m6A{IV_s{AzeMLL2X2Dw zXwA0rfd#ikY|l0RdVGZ}Ch5(|%hJ)y`U8-1adND%@Ed2B2W}M{^@*OHOq_{ddL=a$ zBgKWq(U&Exd^iQL2^3#g(Q|fwlxAL1puvc-=*@1D(g=E@GqWZ5_4HPF@RkXZP=b*f zn}07lrEU3}KUrt|l9<2idhJ$vEpncptXwgTX~6oB`OQ4}#U0{BVNaV}(Ji4ydZq)u zuUlCXw`l@DVC}#R%Y=r_Gre~lcBN<#dx#kt8-)=AWTHE6N5fdb63V*nz!8X<9BJq4 zShl>%8Iy+Z`h)#N(OwzCdb?>$Uu{Bbu7BF7HSi8o(=DvyRN^4pv_}`kY}#mJvN>zj zb3N_MWEF7@c5O>;PA<2XSelp&yp&e=$e&7^Gq<(fmzz!kHh>hgGJd&pm5ufDlzYO< ztH?V{VG5)+yZ0M>YPML5Lhs2{No5g9IK{!9aPc46@wes?jVi@mPlYYA)td_l!WPXW zq7r1U%Ko^@n<&fe_if?$Ty+AEqmOG|YtLWaY?{et42x=67&J0|bU|otIRY!>VM(||e-&XjFN9={n#INjK ze4(<{i*Mg$#~+?3)&yGKph0)!j#`MlTw`IZocZ!=ZkK#2bPD!GIMdAmUwObr*8Hql zs1~S+-#fu|HhZ8Vdo`!CzL@vzwR*Y(X_#2cZB~qz2Aqn(Tm^bL{Y#XY5;qOWc8EK} zK3u9N?lZuFWrS~M5O}d!#f9N1M_#0%>1(0HOxsEim!)|YE+G3{c|KW^+ehu(E9<+2 zS5H5x=%C!T02){t6>ByqI!RZ;3K|!J8E0Se{t9JaKxKW7|c-+Vu z!upv5X;oMk?+AJ+9;yFyRUyMd1l42&I9?sU#qiF~k1y}*V@ax=uaZQGJvPh(9{~DN zisM%Wdvcbzsqx?=<^taH5{>(qs)HTTV>|?wJf&>(@2Mh0Pu;iDpV!Qpobjx)#gUcF zpRRX#ln$tUAx2QJ9FxXxxS%43h&kv}5_)cgvm3E#Ztws{qb4B_GPHt;mXJTnkVk$p z>rR06JqjL0uv8)%38Ek8_L8thDNi#?AXZA~hP71BZf&>ATOJeZaik&K{LLV>kzpgAFxPj8>6dy7X7+mf+l3UOd*bpIE*UgSzTC( z^^5%E-TI~NJ+iaTgOOS9WTV*=s>R)2@B3F_Xd4*V5r;IIk_~Cv=#u^&i>L zYY2HM-)o`IX3%?@>B+V%;f>o=^7x_bqts{mXFDPlpc?!b% zGLzXq7WUm!4`$LI-7LOor~a!EMt8}}bgdH8w>9#-eMgr8$eLT{!gLMKe)s9Jf;dOxHr*_mR{hB+*>vM{hP zRQ4!+$}CQ^ACc{6QAu`C&dn1q`=D4i(-HaDVRdCMwhm=Z_~Sa>p8|BnyAY<1Co?T~ zcp1HVEP8Ug=^t=9mUP?S%7laoiLQNg7z?tMy_1>g7eFmbs~E;LdNU13^-Td0wdDb# zTl1s@vSY(2F<7$;Li3KXQS^KQ8Ef5hOynp z@-CbWcLa|N(*$&*e3cmG54B8nXvJI}#Cdx5YnTO=c1cw0H6YtL6N9nC%u$X}?9uid zqYXXOEI`K;(eM?4GJ1dQiL6uO7skOY3MKSuB$8|FWV`$s_wwXn=Vr1AZZQ)Bndsbx zv4ASpdOD*eK>S6}K*W@DhFe+Fs7b8eB)^s(^X*Xub{Ow7qFIZCocfgcAA|~l)~`En z`1x**7*Iv~03Dh^*HDX>eYi3whQM1|?A`F;K-w#CU_phRAt@2EFaFIIN0$LN@!lkr zc|g?NH%7%P4=BYGzUxQFXaqU@CL|UKt)ycW4rk(pTnyH%oC97guZ{79&RyC1E6{73 zBK6GL13!ysjacD?M5S^>%UxY%7EgE@ye`(ejaqtsXQT_M^I5!~_N@M4*&gka&kR}E zP1pV5CwGL#%5161yOuAH`V)SPO(RQ1GOyNG+O`5U&irKplrIe%_w`f;f##Bf%w4F& zlU>AASJ#Qfa@p7Vurb=v64ZH=9lGEmyf^BAEly#G`;;M_hKZT<^@~+N46N1gqk3AV z8?CGvH*o(L-RZzn16W=&`r-%kiziBBX{XS|jqRLRvhptY+s2zP;YB-QwzhJeT`~XG zp1n`$x3&>@@9k2=E|W#;KV_aep&!bMlFD2m)tf0jR}M8!lwC8huh|ye;QYw*v84$b zS*COtzcSM>S(GOSQRj^kZ#%X_Lw&8LQB;2^&8=`6V;7sgc#Dtm@#>FI_b3nYJqE}hH8l> zsjh=lu|0y6Uw8?-q^z)U^FPYTux}4#?FH^7)C+&PP?$CF#%!JP_SJRyBZ^-8dvc7I z;t#*rUGPfoGYUx_vTMMff%6`$Ou|pn?`2qv?l`Hoq=&0WK4$hPNGSUCbh$V$0Z7G=z3=N`UH4G# zexG&NGc-D-%dxZ`%f$(t(lW;=Y!^nQYks2i)l0xP2SQ%3g!2K8BOP(yc zBRKTB&_1Pc2qhus+z3ko&@`)~ss49{5Akk|AD49+H&QW-)$br+=e3uf%r~v;6CS!E; zV0(L=02>U04&Rmuzu0=W-n@p1)!rKjCE6~>8udMt1#*rp@Ke`gjh}&>)~m?tX_L67 z!zNHyEVPXJG<~vyZ`!t0jsZRdC{#pcy?lY=q*yF|nUo>Xz??gcNS?Be@8MmEh1s&+ zyU!Jn0s;I_j?SrQlG>#p0{_(xgee12p$!(f9`;)~XF&0HmQucJYdOaEpfBp?7^@^7 zcgS5P!I-@EbaoJ_G+Fl57ra@1$B^IuWp`@{h~nZ=X2kBreYj9+Yd_j2x$rd80WJ83 z!(1a8p7ugYy3MT?QpTxL<;A-J?E!PL`t>#pfJ8)6k$T@pgpPtuy5Kg-+0nQ7_n$CVIGkqksWDWvv z=|<10BAm*)s4QRb&}PmfHPNez=XT%s#c7%r^^m$N7@q(rzm$i zI1IiruxOE;(PAizy$;uX-8sH5{XU8g%_9i-zwyO+kpfr|VXok2p{@F*Wp3)EQE zM2YDMGLFcXYiP&s`IU2Az-rpdtn@6cb0BES>uBKG|H^tRS@TdV*82H(`_&b;?E|~B z>#<$+vRAAhl>f+1GebL#A`N#bwW8OfZQ$C@;a%YN(bBSh|4?sQ)!w7w&ehu^2GS5y zCQc4^q6Br;LyUuDlv>}ke&eWe%*VhR_R@_^FKQlHh%$9Wk$QrVdn2N3;Ycx!#}q=> z(Pce!;{b#Rl&I0M9!%Ezdi0UFe!qCaEhc}=m>6YwM`KE}k&iQtMVno#e0)ngMwon& zf?94!|2tkDoESDS@I6rP}2Pc2uN%_$x(@dwgr@_ooET@Cjp=DBA6Vjx$+-~fM$O-n!OMUgn*>$TeF&Pz~yLN^SrsaxGQj3 zLz6h{Xe;wvn?Cu*o|<{_wUC!uOnqO~;WBQ#Q3+VIHm|BOzVAHvy45c0iA2ZV9y5=| zX}lw-00Sr$2Qlu;LFz(IMM^+O-X^G%-Ddf@&>I0i8W4ot7&YfKXx5d5+-YeJi3`dQI~jTZ&pk~0R{Gu8qSOf!~% z>80lWo0HqCN2rJ^81xJlJg8yNi@{5SB27 zsy@bNA;?hoFYn`J9SVlt%mjoJ5L;Lro6d3c-@#SDTc|&Ma|HzgF1{okEIM+R(f4b{ zt+A(Aj?tdaOXf_px8`rlPs; zb{zI0Ts=2q(n9B(c~=ve@1mkYy!20ZTu+<|J#_W~MUB$mbs;p9B#TT+Y zvSME3_Eb{4xSPh*j=*zSO&%SWw*C#2Zy$B&FgJRA5{DG^8`&bL_32$Jd-z1Tfe{mL zlbT-wmY|XeM9lXrN9D4l^@+MRG%s)~7H;S$`$nI~y66K)z4^}q>wnu@kT*6uDR4YS^9DtA3(!=Zc?R+o5ll2kFW zpF{Clo(mSS4-p%tY~!Xbvt zU}hq}S#vEP3n?m5Ky=df-NX45zOGKZzac~n3Vl;)fS=>=PcyUO7=qE!OraEfL z924^{qn8<^#KAr&G1WD0EcSWzFVhMrq1XVF(aW3aN2RjGPe5Rjwo<;Vf~V=W+?Y?; z@aetT_(wMkQ;$wM#iXBDXoTEmn^JjN)vjtGOGt9LB~~Xx^V2SVCpoOC$q!<>P6nr) zZ|I^M+w@_L2X>)v#A2DTNbWlUh-tIyH5P_zah_LUYfAu4K*|kZdEU=?>(PAT8};ab z6!6AT`UK>hHHs(o+iBwyH|w`oOc%S{t$Ms9Sz8=j+LyxStLTsB`>SdUq}E=c>&ows zr87-cEM*Oto>iR*vXUjSq7KoaF>n__dMTIwS3-BIT`KZP&oyVIxrEdkxC9pYMzn4_ z(yTfsX+%sGI&|J(9q*3jl#wA23KwG&pzm>Qz)4_gA-$5P5p<;yC1mS-tq1Kr(68Q8 z%+4Z07*mSW$zPspe(|Y8fPd+0WrvkZ6V_F4ptD{_8&A`DCQN6#Y*L3xgiw0MK3-k^pJ^Y$WkhB1UB1x2A!Uf{$8ncLCF4dz))H1?b*I|}7L zP-lWSTu0zRrAV=N^w}?uyOCa`Q9_Bd8qxU6dPE1|Ix7d#qeFCam3d*&AlJhm#mi- zC4XZ8I4vc^uXWOM^a*VSb_$4is1sc2bSVn2J3jomdjk#@BDTBjefW}Pjk8~w#K4dM zrJN>D&0O92^2A7zUfV@4gJKyZhD}R{=pMhV#l(-t>#8C#74N)+qMxLIz6ehRmD^rH?U&lc1y>4C`5%gg&6*ZJYQfBQL$AF8wAnuh!_fwuSwsyYpBpm# zh}acfBxE`iyw-TJ2ybcKyu8Q<>l%Gr>>*xDG>bjEjsMzhVos{=m+IKWh_*$>CZ~~u zy$s5-L$QTSBmDy)aL&~n3Dd`v{cNu+p%`D1Pt(~Nr<#^#yKyVQX)4 zQZJiDd=~l|NvOzp3=&sq3qT^``srR>ogVf82>%hvt!gtXt{Yr=Q=h!!8h*mZF@D)H zk0>U*cZt#I!$$L!8F^jDa$Y05A?F(`#37YGxD@-cdJA;z`1vjL>#Mq-sw$bzQR94` zakCjbxkUM+dhzglhQ!?Q#j=vk%IQfI!S=U}984;->=QK;;ZF#QzxhaZ`E^F>(vxeb zIR5nVgV2w6KG_b_9w}y;gTm|s`@S7;V=zJI94EO=9{)&~AuI03&;{MX(|QML(fJPf zDR$l)b$Ys)m!xPi)d#u5_LehsgK3?}@ic<0d#en7R(9%_K`RTHE2Awfc`M&n608RH z^5s2p?$XijnmAAXO(K~*N~jOV31_Jc{z;8SVR5YVp#=BTPct~ppBiI=MQ7CVL1hK~ z4d3*P2RNLD)$Lc2XirD1D|Fo1$341Pk|AFXb=R>`CqWEM)rfTta-WClmWe|63X}b; zWkFBIpMX~1e}yh1?x$5KWWiC>w`uS zO^KokZ`14iZu+^0ob#etCna|7$F4BfwW|!PB(O&cUy8;L`Ia88TWE_o>GZTPl?o3z zdg|Vy?d&P0>?32}KMXvFh;B*V!2|MvE3IpQ{h=1>z>pD`*auudFF$P1e3Vd6!(Eek z+;zE?cXnX*^!fMih2A^w?7OpNF5O^S1|ACgNw-6Pc_?$@5h@cF!e0}0^VZp=mY&-! z$GjDrW>{BVD~CeKqb)=74R*&s%6=e$_qzX-4Bes1vV-L;ZWLGZP-cC7UzF_kDsrN1XJc|$i-((`uxSbWuG1)TEIN2S z=nyC79juydK&5tz^7!?*yH&&zg%f^31IR8o@I~*~YPr8=hGIuQ@iYl^m^>VOyVhcg zFYHr57Pd%LX}_GQ( zmD-Zhit#jmO8k{KiXX&F-2X<%2Nio(Se>!g{fMGRCz!QyRp6ma4kHh55%TZ|1;TJZ z65jVh`bzXAslZm+syG>zb=uf5(TlX8t8b}z$C|&sa1Bnyw2#2W(*3XH_ zkLJ7J&kujetpK1GGbQLaNW4BGM%=FnY_^G0;3qO{m#^s4%4G#PP~ycl4f;$>7&+Jo zo`xD{Nsz!@OcH9{i=&s4FI3aj7m>?P6wtLnez8R*PlznOJ}b}v`Fya)rKXd8l-eFx zJl)X!I2Q!wC6b1WD%ZHH=Lji3y^s3jZUGBF-u>yi(BFsj9RY^!?BMF{$N|9np&ohU zmMV1-5F^Uy1CMKXKkV(7-acG^ruEE6h+^z5*0{ zn`WGGuksO9Ml{la^}!!ARb%~u*pdIhsiEtM>WSGjRjA3?b&2bPS8Gva8f>s|$hJ7) z49KM>Xp}&U-j(0Z8#t#-I}FSHf2^H#SXFDc_GzR`T1pn7fRqA)q=+C$7$8V2x=XrQ zA|(w13u#dS>FzEOq!H=v?pVb4EZqD1&iT&S?|#qO{-GBa7cAzSPs}mKbC3I%Auo6n zV*L>ViiK)e(}3%f=(Q}bgWG(USf|0FzWK!fM<<8U8@O%}FJ$ml3a)BEFNtlQ}xAykjBLZJdXuD&oqSw=`i_`|fu zRn59%E4{>-L**I=>XaGq9#&MKVWE!O%rA7svi-zg1cB!2#2fk$J>jM{1NwjS85BRe z#V#(b=%cVOKyil2X=$juzXJ`Y%H3A@_*6CDm@($;U^LGnLRVBNo5Mk5g$&{rIroj3 z#eesn17WT){+g`FtCKZ+bAK`umX*qZ$8lo;Qh%VOwQL$Nd70opXH<3U5&~@T>g?7y=g0kyKy(Y`OCES~#v(x569g@n}P8p5hm93vUDH3P&f+_h#H? z4qmDtWPWENH&=LI2lvm^f{H!oD>k!AK72%0w{h0ac4F(U(V{MO zejxKz#Id^mbec7?i%UCQ#dtB%DYTtq>|PfT3xp1o0=WS`l|yu^6^$fSYBa6F>p^FC z&@~W7KzJM9cJ_U;oY1;mzSjldqV9(f4`oEK)X^Eur|mM{c{8W8Yy3wVK;?ld+!J

*F5iD$mS*y(nUsRXtL&3v@MWCJctG}vZ>JI&l-fRm|=s3<723>iGq$ct`U0dc7WOp%z;JJ|cx>sS+X78`hD z4GF4Lb(aSL?7>(b5Pd;K)+>C-e=rqz&rLj&)n;VBThm(qDAR`99$=Qzb`NxF zy3^G_=kupkF>5B+N=Q>axCL6VL%71DLWXTp{O}?Wg2b|}_bp1CVmcU($S#rH2zuar zy4{92q3m-FISpuGWQy~;i)F;b^0h9T*hf+a_l$WjD*UHVZwENExQgwY?(tUgEtR`x zF`4tj{(xGxlS=mzjX-?^Y5O3UK;Fur$ETGQ6^SJx10>9_ z0&-c~IFMoKCv^Hkrk4BDV>jMaUa|BpKHfHY*2ia>DANISoj<|mEx6?&t;baKAu9Vt z-aT+KVp~Y9Cw^SZI>FDNreU2JxHYDX-->Y0KtieOac7_=bu6ezC^eIw3w6)yZVKL# zYVoLPQJOCzys`tanRxs~w|IM+1;{uCR;XW9JY=L_0r8WiGYt|*Ey;Pv9CnX%wrH0_ zSuMp2nlDxJ_T(D?p3q|Vq}J+P--4PUw_UlZV@^!71AV+G@7N^LM%bz`P97^NTN3KU z%$?Ql`SyAU!m7CMLjoT-FW!q>9;GF7oh*HF#{&9fQ%a3>_N7~na+UQ}M6%Avg12B7 z8*-k!mMGC^uZ_*p@{kMk9XfjsE2Wq+iXhBJvJ;TgwBRX6i0pg91lp&h9&udoh#nIm z#`-lsTB`u&QDkhc3u2!yqo^|I#6+b^=-)sC6aB+7gMk||jQXm2GQs1Zi3S@&ntnEf z5EL)sUXA^3BiTdKrj)8HdZuX|e|dh1e9@(GOQhXtxXFO{*>wGR@_>~Nop{j{NYk{fL2!1kNqSpiJ=RkiKG@5cQi6S~G zvXYTD6Wo}YCRXQJle~O8B`u^9BN2pzs9qPMP*=cZfMXEU;b7o1nA#+REP(?ZN#1YDOWs6R}N4)42G6?iYZ-x?J@b$`c zC_FN~Yjk&N(Fk{P7ugfxKJYSW%7MP6FHq9p0lF7bHh+F{+9*90>!3tHB`o<2=yhDzDv9GmI^&nI~-@&m~29Oh1)g0J^iShi9AV;B&PQTXjfT zf2o-Y2((G2szMzF^jMA+b#1N~U9)~xzOfAzkXS%p`qK+Q!wB)cjq&ObAYhv&&a56! z8h?LJ&h}PU!m-xvZpFI-3m%o0}DDqVlJN$8_iV{v( zGLVLorr7$@dcJmE_I^Z%w;vjTWh_$(!SR0+=SnBC>C)|nY%D#AG%0UqC4npAYSU)h zCdWC;!KWotXyk-2-rF*}gKfUeV0yqtdy^`tyH~d{gd^lyyk+U|N2=aZ=g`Y~5qM7< zkV1)xa)&|dC;x|YkX+FL%6E)V5jpId=HzbT*6mKRU@1Nj*{+d_@WJO}Ni1C~^Skh9 z4qe>A#vMwqfUKxB0Z=8G5ALnXr4L)6jwdGe-r+N|uP{+ty9ArX=&$B`@Q0@8<55s? z)2h?ejaL$x^T?{Dc(5g}hoHS!&DjLrwqTRl$274vl_rlvvIN#I4T&KKtOqVa=uojIs1K3Pv7TK$}NDl9E2L_gSz7<)E0}vK7Kj zEIrT}++->Waz0c{*0g#Zp!H&@)TYp`{mD1Iz~)Kgi8vhfw|_5w-cRm821`C2n)Mj1 zKrj>`Ja3)Xo3o-(KEFH-ql!Z*z!N{|4bwYq0tcFuQFc3L(t@>Lu?S2F4 z9Y5r7Qt-V;*i!*M8`+1(s|#xmMdrZ;+#gh`vE|~Lp*@F<5|ZGYofP0}YPWp3-+LTlJTB#4Y+|x>rZ6Qa|FxjeRiV~Bnz?IHJYmxiW|>n zkU7I9QQ*aN(mR2cXK<~^kMk_zqr}->wuemBRzri4{kQ-E8Rx`Zuaa8 zz|F46;oP{9T;5^3I4T*p9uHyEE0T3IzwJ!Xv3(x36f@asl3X_WR04KDI%S>QfG8K+ z6)or%L%adJa@QVnln&C}_7b0i@^?q-_e4f;hOv3C?rr8Ps_U{2?-qjC>wG7+z{3Wp zZV0SF=VJ#&Sa*>QDL?hItv-3~kT4mJp4@hyRYv~L{30B_wGjGQEkFJGdhX)19jeX7 zeIIj+0;^^>>G5SS8AFm$u_EFEd=D3q!vAEqnYhff^lpM=2fdI?!L4iinT3t}aMvrA zBbuwnwA7rn85S@1lZ>krWb2I+lFKGN5nG@qsK8of15Zfxpre4#&Q4t}u&TPO1@Q)) zJ+Lb4O!Lc%c&ck9o?U{oYu4Ybk6xmhG2jJ`2eatADGF#C7st@jxv=U_9G$KflFlo> z8bVHv*6`ZzA3lPG9d=rlw8mATuYD|G$vo-v@>2HlGUfeXT@jN6wl<1Ld`l67H6V4> z2qXaXR6%b5AqR~=jl*>w4hF$bV_QF7T18vD5uovuZH@yj5YC{1kA9&uj*PaEx_~Rh zezeoYF75jj!`l+QOI@-yoqX?-=LIeOX~$gy4}yuEkO!JFw|+3RXK0w}Tnc@Wt0;#g zC70K7q?qcOgfWH#F3n!t^v%g5#U?QrEyhP>H74pnVf1zx$aY`mgauQ|Yem*lTL95) zIa8s`R5O9(sa-&Cdl~V2g2oGUf{j1Q>8$Z64OaHS{eJa#VpT?|o6}xgIf5FP95zAwB~eR6$^?Z>&) zxFsE8vV2CL23D_sB~6c`3S+)fZ%)uLrsz`xh+D_d{b5 zoJOCRCH))XzKLPCte1^$LTWyW@sYL@3Qc)?2G-*@%Zp!YH%U+SJn@$PWG@Ec-1gj! zgJc@A^FB1C^*QU9q>}?|ZGMUEn3q<=+%Ug71V?W9b$SjjH$$8Umz-F4G6XXYTk=}R zO-&(D?fUHH8OXFWnUOpAT5+OoH(t#ZHhb)tK+_|4vLuJ%QMrjDP zBD!K~DOt63)mfdUw*d9p*ULWYF=$6YR$T}Kk(YJNWAukOT4zM z=OTZLw5)q+AoGVF3PsVm0Ortu^-;D28>wLGT&JXgGAh}q*K(dck**y?`*$OQHbGZ2 zRaIx@HMZ@CH%#LYZ<9eM(KHCd34k=*>S$!BmICo~b=}`mWoT-XMcjm~V<(P<_q{L% zdVaXGW?sJa%KLY&qnTx)mv-798pkZ*tSF)QY%r|MVXS{|{nhbn__a>EOiu9<(yob8 zj^KIxa#Nl4lg)RWi%uuz8cz0|!tSkfcw{gaYeG%0waCzTQH~kY)$vV%s&n|Q*g-n8 z!?F0s$@c^lEj>2$aaUW(;niqkUiR_W_gp5Y0$q(d`w&QWW$0I#wn3n{(HM!4@F5m# zALERA_w+|Zvj8%@5#cRI{Uqrb#8ny~nBRgYLU7)>(OD1T43cZFHjizd!9;+2UWrA2dV>i1rNjBNHrUW()evNW=`cGK~rZNE~@ z+~;^J+^n2Z4de-7Bj{-!c|g0(_AB)s(eyTl6g}($=ih`_GU3nIiiP?VMOu9sSYvD8{v&0LaV2`y5n`e=!NEU zgIwOM>{H@_u{c8A0{hmjC{JUCPPlYDhvcKWvvuo!dGvD@*?2d|i0LJon+@mQ!C3Ol za;;VS;7Q^^F(=LuR~`-3yK!q^`>w{NEtWQ|lXPLH>3)1;oj*-?`{+#TR|<{)MKNBi zVb`C*(|N6fy{ma}DIrexof!H`7sQnKsie;RT4F)TWE>BApv`t$WyO11maeGuiqK)5 zOz9qPY1{3nYjQdmyd*x+XNQyGtN}g7_xto~j@~wu^2X$m`ACuSBxM<(4<;iO$1+v+ zO8hYh`u0+}f$I&i24s;bLjE%rmeqCXiH;g;vopITO@t!N2AdIUPSrmQKcEK#ZKntE*SEzso!dlKx{5n6Fd#?DOWm~V7<8o_1?M0b3-!U^|EXJG9f3o9 zRAopl=2RWq0wA2M#|S;BKB=>uayQ$lb#brEwA&;?I{^jeW)+$D7BPKh`~iLB*)rw) zM2(6RU7HmM<^#DUo^W2$xR20%9&FVNca|g;rBBp<XY|q7>bgF2C*5Qdom2V$5N~5Q(OXBaGtmZKw9U|cP)ytpb z4}#>yd((a7U%q|F4NaqAWGB$MbR{KS^I zM&?6~e0RZ&FyI8vKN?w=C1#en>?m0-KJPyUVLy|6OzxXyj%Ru^d!7DnO~J!VYoeB0 z&HbP&i8kjMj{Gf(Ml23h3d{UCwuxB&jj!jiG!mS z;b6{y%W#**hu)~+9UI^mW+NcK9e_k}>mJX?Zkpq#N&0kgN4lH5OXoKPa@zm`YWoBm zE69?~21agg<^$ryOBo4>vo)9{!qJDcWpW`Xe!X_3APDK**YZ(MJFPdF+G{!6_>I!*#pN|UI;&PwiFAH%LQ|Rp|VsyG!_Sf^p+B%o!2coGO5)?U0s@JxuwqPqec9VYs-XVVjJOK%%3>kGW-;`+WN z6w`J`b<{{%qALnRJ<@!~i)bzCT(B#wR%k;FFxcyVip%OScJztMe!$hnR>$Z2Cth-yd)7|#u2qRV$b!TAg7|@E$II*t#Hs9W&yFbe4!!SS#32#sgsF|yZ zUnOpD)=b-|-t9WgJ@Ndk6`^lhhI;>m(1uD}!2n%PMRtv4!4FQItHf}=~@J{O}>j6Kmu0<6d|l zU0=~2cyLr51(GCRVay@BmE%*cA6acS*~N($Q`q7GZSwfL55;yOUUu;XC)1k~QHWz-s(O7IZ4$kqFB?@Z3 zr)ZrL9cy*+N;uQr7OW$RNjO3mR@|V9-(R+YOY7wZvKE&}2XoyK+!8-A$d1)P<)eeI z%JH1*LbNtf{$l4rhWjh|2k&RSuF=B)g|bG1tLw_xf&Vh9;|G)?fgT~d`DisPQfrqU z8|g(-!47+qULlE8RR1?nnhf>N+S3c~=k$zxQ~KYwve9g>hCF-NL?)QjM(3niZTgAi z?f!E3dr&^A#xb58BW3`uNa!>b)Tm+cuMV1Qx1q&fzyVD*w_JcnKb047g=tk0rp}CN zb)&yM_0dkBm04!s|FOc06}?x*CHaFTI%E&h6J9;a#_MU2UKAr3w`WwLoD$Z_<7s3I zIkQh8JC$U^PTVoIi8vL$ZANAxseggY!;e4#AInju?_+8*mct4^r`k2kctUh6xl}uE zKYMzJ5brF70|hRsP_<$kn!|Pa0k;e%8!^be{)}26%kcxbZ{=i0^0EH@A14bV&%IZv zhMoyW)&b**)>UWRSIiP2=6sSY_G0{Bu@5ix++f=#ff7I1xLdoQW8DHZ1r=Jr6c2k= z&Ttn*+Or;H{BgEQ_akj={chAYnKGY&Spiq2+u}adEM_75D2p-(YNjI4xIK)aAlHT$ z%MO1QGQ_oyUY75-$4$=%anb$on0HNceS6q-q2+wEjNPcwkxd)3e^ZEse@Pw~ZGai|3 z?NOH8XWHIPwb-ggn)0w3kPH<*66oPPd<=U3Ue)XvpKBowHI-~Oq_zYefWsJ+T&c@|813gBc1MbdLc{|^XJeK>IsuOvj zbnY3bex3F0M z(nnhH<6&@}f1&WLWRFPGOtoG=52S($;*{V_OnHeA=Re@Ay zvx^r}rvy*vOko<(x$C(>E~L8hY_FtmLnpZDv-Sf$!S#zVJDwxrb%eX1a@mI@xdi)_ z(N8+DN7k2~YS|Kxo`}xn3{T0aC#@6@ah?t}PrhpSl>V%7!aCEBx^9_3sYV%Zv1@~% zzl_>lb!vy}z-vB^^8U}&FBYx~*<0tu9TxRxVXxkKW*M=SA80U*J@v{@P#Fn|N9tcz zaC@>w9zS^ht>Usax$9#9wZf_hguIBIiyipdT+r7bAw?iqGv>adZuN{R^%l!gBy^O$ zA_mGIX#^Jyh*!1}HeI^j3zxB$Y5a6QIDSh{7s6qsZCYKvS&Pt1dtReI>nwyN^VM*G zL)h=hVh_Ns(Iai#glLn~OeuUX19LEq3n-y}+QNu#w;QGhy$=~eh8zwQnr;ud(bQ6} zS7kFLyP`8Y+9jrw&C+c;KfIP&y0(O)nywpPNrdw?IA30mXEc2_=>#WCkCbOLZ9=M# zjQKO)$~}Ue{S^j#&Tyzg8`}O=nZwUE*~r3GQ<6FHzdT-*ULHqi5yCA^mJz8 zEh5#Gh2^-9urnA3zuA5v&e)F#oayqS1W#SB&BJ+-OIe&yDmMjvUlQQ4^Q$L`lk}*r zEf*Kh46o6zqlVx9`AOK6*W^#WZe6p)dC$G{j#-+~++z9m_aVu( zs|-c6Xxyu}lpIZ~F)vL?g_Rgtj0M~l3@sT4QZw`TGnSM*ccS3LkDjpm3Q&;pE#c`Z zm$>@Q$_7U4Yv&zm4i^l(bi95~P-C;jIg!IwkS)>viO|LAuKvZ&^suKG7Fqo8 z17G`bCQl|>`zZdq=P^t7JIMmO$L{L@a}(IP@=Vc_X$MmG03E#4oQEmWUS13=dyO zQgjw1xB3kHJ{$Zz5T9u>fBs=WA17U;2J~^@GRD}-LKd$ea@SUJfIq{jV9l9=bTyM% zc@ke*uOXm6;Hj{fR~AY2ZP;mN9TIZpSQQk8d+Rak0TkmWt?fUf-u`TAz3{p-ydH9VZ|Wj&%rf>3lNrJ%2XP@ybX=w z>F>l$v}1Ph?_E-Dx3Z~_v^=;cr@i2jYFn)`!%MKU=Cj^cFZRykw%hrwKfRcmC{v~r zy&ciI(k- z!f^Xrzl-=gk9UcA-5g*g0K*MMG_X`O@qg$WtWat@GZ8CGdu||8Zwv!#_l{ejULZ+qwCkbPrQKo6R{5X<*(5y{PnOf(30WQFt_Li zT-3K1kVKnCoav0TP%|OsE$FD(!hRuY>U;GeLu7oNimQ1M%RhTFo>Q<|dem$G9wlLo ze%O}Yk9615J!@aX^lD+N%@J`el3Si8t>J{mpE9%F=%;(6Stqt7#nXxjnqTC9dy?hP zU32`db}>l&8Yn+DGMu|u3pveZ-7k4UKq>012tPgwo9*tt^+HH_+$w8>vQ$&IYUa!R zle5otn%80{%_>^?^$AVWF7f5|0Vp{25bwx6+2jUfvE?U2pMt^zf8xg{{U5F0Iu?T^ zCBh2hWfV_F-&kJRzw_8Nd<|ncomn+ds_Hubc4->s;r1y(H;D5U3neCJgjcisH$Cqq zN&8&%($}AzYB`OQ8=L0E7XLz23{fv$?9b>i4-4 znd-+izsThj(9@DM%qLxj8Kpyq1Im-{Hh5rm-u|wIx5RBRuN>(4 z-2+`hND*~{Z7lUE+_r3(kmHu8y*+`kz5Gpl!?f9jZlAdYMXEtgudY9I+4a(-DX8&X z8L3y%#`3^%IaPg^LhS&{`85ifPt(1E=j^%lsytekTXy9DjqXUHb%*%#!^IAUT4?=lp0=Lv)`6Ne$!G4W zZSSESB_6WvFWfpsd6sKjd9LQpTg!+ds}+re5cuR|>DcGv=f5OpDJ^Ia06DkMMe%rw z{DSi;w0=CWr}QgIE2Mvm$|`j;{Np`(WMv(7ldmQvi!khokO_n?jo*OQNosB+xFp5znwF0V|3JTxW!VnoG=3a z9xDs&Ql(@2eCm#0Y70f1emkjyxzo9o0%1_dtF8nj>YO!J!pTW3>hS27P-f-z%gE2}!8<`H_&uA~+wN`97BsvcenbW2g12BzYD7 z4lVen3qZ&E<8fE>ga){bn5JW5F&CWk-k7vf0=~>(%eedxCdVw{KV@T#VTA>Y@b^L* zcq+C5F^XW(qo*A_Z+EL*c4vY~J7sthBu`~Y10wu3|Gu=6*8QvrdeC^|D;^h7mk}dY1hs(OUBGCq7UWymE}Ckahov3nzEkgB|ADkaKRf|&-Ke6UcEUzCZK?(D7B{&#Xqzr z{o1sq^1^5ox5$gkHB7X2qoc|7)w^Hdbg$WH|9z|SJ_v>E-B>ag8E9==WmV;u;b)6M zwe@V+qa%%2Gocg~ zQPnX0bVRjoiA;o%3J}iWngQ?r7u$v$*!16HmOHGTRho!EP-E%;HH!ZWB!KdTz*>G2;dF`*A+9`Ufl2YMDoq9VpOg3$ zZJ>$XWiH~y#S36k+?3?DLA}bD=RVKDcG2$UcX<}}Klrbn9Vgu4WMlB=^bFlgLEG_n z7HUxr0#kdXJm_1o0_)#MU8+u4Fw%$Q&T9;<&J2nIdGKt6iOyL_;CyB{g+bgDH>)sk_O?0b13jAoh)*QI}s8QZyq2TL^BoQa87iO8C8acGLak-6hjwH(e;4lbl zP64|!d?m4CgBKuZYyyU&JH6DB*-ei{J^t?k21~{y5ykRLj@e7AarKb%B#I6hqe~a~ z9MbZ0ZsS%h%fhi+IV1CL#=9STRS3}|R6Ewib-NqTk$Rv%w}JosRmva`CEU|@RC2Cm zdo%3SbrM|U-Z#29DCKDcjVu(e)m58RXvh(3jaUnl!&=6(ZmUj@NKmuYIysTMx^}(c zCs0D1VJvhf*KHlHG>05cZC1r!JYJHX^0W@Bv(bIOMwxCjX(%>|v`#Jxs!I5!BLqrb zJ=E6?z#FZF#JY_DdBFQW7~D%G@DDVwwXfAjm3*wOP>ClmCrC{oC?}Y_N+btUq4?Sd zxnH9CQfU8>-Fo!B@dWgd=M z1$H9{Tzdxxe>1hAxy0HUk7ZH5zZh8XK_g1_T@9_9fnp(-n0XnU6b<#Cu?W4*-L$%G zx#E_&sA|^UOqZK~ec9|hQm}#U_+(M1H}MuhgK}t!WN$v(Sp=c)GJ>6H0Q2Z&o)Hl; zRsYKoB!-ihSBcO*DAst+%;t@K>j+8YfT`kgR{i{D>qI2xW-QyDn=^JH{gd+6k#gja zw2)Pp%731se?F4S2d2Rmeo`-&^tfoL)M_)WF8$QyqaE#8P5A?`HYj09Nl87HJ&&rY z>FE9c`l@ERCF&&A)YROAFM6FZz{N`#<7Ept)NBn=G7yAatTr4iS%kD2cU#mu|2ohA zi=l#jKxr@R%!o6>{H){tVPW8t-PE>HS&$wnp1zj= znxN|E$TIkx8&18%_(|%@C~9bHxLcEB)s5mFbRS3k-NpR%c>X!i+&Fs&2Uzr(U@|_2 z$H7)vP~)#8Ovqh4Ptk;)pC04sWZzP)RgSNVoJZwHlrcXnJ4Vb+|KpVyEaBZe?7qWU zw7Nj%^DuvzS1VedvkN0ZJdAg|f)?llY$>5=yJk71CRcrI#jg|nr>XN7qxdf-8*-}h z5;MX|8~5IGN6&CJEFU`_mpQv%tCFi<-`Q}!hFh$dqJa}sTdq;|^6>A+`0pO8ggItq zWhE8_oqyIU#8hzfqYY;Zb&wp|!cU|*v^ttCiuc;ftfH=wBBc83e|0~9{@T(Dm>!=^ zP4nn>qb|jio?f9`{Va%tbTCKmr^It3%M^civ41zC6R%6~@l-e*pH}Xst~R3X!!JqF24 zTFzJ3hZaF!i3i3}C=W@w#{^NB^q=?tA7iio$@fxx(IBIIv#jloB^o)CNlznhv~rv3w48l<{K;cG55)JvFH=O~YpO*Mjw=?J-Txdeua7BPzWyz8{mG0q$?G>- z^wmj`cE^GjcD8h$N`bYeXQy`iF^hMNQEx?o-g)7tDgM;3<3c$d_Qdun^?&?;?JFf7 zHFC*cX=R(A`Q-JmgLB=jxOCAGyaRTw*sB$0cHCF$p1aH3{f{571WXtA<3~->*Skf> zgm!kHvOcN%4ksheVcE%+HkQ@HYgBKzjbG~7{^9%=g1t+LkA(HtF~elr>y{@YEIJ&3 zW#JCqv#jVItsF;88P-2Ml>W)YOp)+{>GfGXy0W12=%nmIV|Xty+Lz4v^M3zk%lxIj z4BXfHGQqf^4xb?h(z)XwFT0!Jq;4Ynr=r%#Xi=ghi{n|*HkL-&!{4w!{jubIaKYYh z6j;B2N`yQA&#(B`Ef+sY(Z~TG8vTgl5ZUGYPdB(<9{?qWX(W(1hx@w!^4$5KEcL&* z0u2u34o9^dA!Zx%!SVRtbo)M6!qGGdVD7(<Uf89oaJO4M`H^@w6QnIo{ zo>f&=f~KH*#J}Zff-$)QYxgdqhJE@CV{HvV8kno`S1+O=^|KI#H> zT0Hl3L-ThmS`!Y~qfew?m=KpS$!~eWBl0N#8#b!_aIW`v&4p<+JY$Rv8w@3#-*L|% z334QZ@=cjl(YbS+x5=8uHYB!@xh1rRaXA-|< zfTUusXy&Z^?VS5}@4=E)d3{;E$H;$f`ge`@FxXI7o@EW@cByWy{Y91ifBG~`_+b-C z6UsS*W|odG=tFA#C^gZEt4{8D$WR<|kwPrs+QN$ouM)*_F9}i`4sX zt*{g=3|BXE_3QaAplad?swTf@grs2`8@XUDtw;Z+;1|BZexh%bJ0iV-g8vsR^FLnx zzj25E-H(8(i!5sH53`zeJ8AmB-?V+;zL}WNCWRHbz>v)lzi0ChK-1D1PaPQ?92CHw zgZ-vYO_8{wfR*t53a9Vyy7S^F2_+?^4Aq;_j>4?JEdvRq%uvlrt%Nqb4)rJg?}qVz z@jVbVe>FlUROid*M=R@(pZRTZ^UGBhl5%+@BKd!DRsVJMnJ~jNxZap?bzuE7lJb9; zhZ1f$&p1;1es3%n{fOn+{R8peP?G(%W{P7aO3KR0YC^xJ{ic^tN)slWq`az$;NE{a zto(0>Da8^qNn^{vd1_e4zpnPkWGQFAc-=O12=<&qOLw?Ml`!kE7B}a3&8ah;AL8%J z+^y2y?cR?u=*ct$zx?ff5Uk)0nHpWtF-5aMmx)YEcArj#v)4@XZSqr>!=kzW?s%fU z8Pp(G02jf$B+zM&zTmHxb{=F#^-nJVa81F15?rf-P>2eBj4v|t+sb%$GZMMaaFIh< zp`=ira`J1t*oP+Ebs6b;haO!S-{|)aQX}g#KBG%tn6{9757?uV?-3c3$61 zdRYLbvo+MCD|tBTh}QXL>vXrfjhaNm)@FQs90^*6dHwZ!fteCo`jSkGR%E6h?Dft} z|HdIKYe49`ney!}D4xvr%3m9p+E-wlh2b^=vRxU~Sw>h-fDKP`_srSPY_ zJYa-?%tkp!wQzIZEEXmjwZKJ}OvZ$B1a!Xy7=a~U@|EbHrYjPFKFJqZj;o&kXe)#F zpg~ZQUzsbxx8pf?%M_WIj$$f;cHS6?RWq_;TZ5JeAtw>G{U8HT^@Bap!ymmIE^j5% zhqSZi9wZ}qB;PPy-CXZGaYPAr5VfeW7S;m+5ixLid7&35ByY{pw-#w|?TaPu(8@B>zSiws!l%obZYO%_ z4Pf&@+_gvfWxNCA7vgS*sBFe6Vm9u%(5qLUNx;0qQToiK*ikX0iNgt>NQaZUlBh^X zOFw%i%ue;}BjvJI#D^!+#Clh_yi>d-l$A-sp^Q$4US4F2I}0`AHRBx<;}h%FJHun+ z8&%fFR=_UaB`J=CS*BTfs(4)^d2{D)XYjw+o|<%0;HQ<{kLe!A;h(M)6CYH#$iVk@ zvNa1`9=_pBExVI_r?AA})$R7WI=57g1knQKkIhrd!_wvHV?)%eitA=w!!Qx^1kfWx z;wC76y>{WG=T)c5vD>A_JcGSwIPUuu3woOe%nZ-|(W?{2#ICG>wm__%_=EiY4QhDv z_{)nia>81}1!-@pa1{5S?{jodm%bI9@ZI0S3Mw%uJE(V6tgB;Ifd8b9dL2jST!FLc z7gTCWl*QdA&5wlJ=dvZ?>*iKSbr6pF$IQq27QbRbJ^E%ov+Ha{X8PQ5TLtrK#-o&y zk~=_E++HhOX%sT2Jjkyl`(Iz@VC8v|E`6ox0LXbSf1qpg4+Hp$)S8+arI&i5KnLln z+5H;t*3ZTLJ=0k+x&QhBRE)3-VEQ?Yq|5{MxtED~*0pkVt60#@aq-3rAlD1HjG6;T z)P+?qI6FN0EG343`Occpqbl=?ld!xyE#d-tFf6}qE~HvWBr|w;?Hzk;`7o^Qx@B^2Cdzma=CKK`bX`{bVCUrQ ztf*0^l})d5rpMY7A4h}v^s*?@`=NoZP&{WZP1i{Ff#XM#;irTQLlnkDV!N==EvNwV z*2_)5{KXLdMtwy0U>&znT5b)ONOE4e)TY~^B38-REK|6I1u|MNySnt2A8qIeLa7E7 z!b@;jh?@qsjD)x67~HoSbb;+oW@f`QXk#ECV^z3fUzk(e_vljbWgq@ur4%WJC_^*d z;ur4aNSZa$#p(RG+pCo^6xo5eed1oVUW%7|+-dAeu$IzviquCTJFUxsSs!{s`VDpi z9|D0Zvu$BS9h^l9^NGl*^8ZI1DGiwCU`YLu1k*9VKAGNbV+gaw#m^9M-CNoM5P5;@ zk{=qE;QN*|XTf;iIe<~eLvlVWVwu|gF_J-x>8)0gY|z9~lj}o3%wgL^AovF94>Juo zz48haG}Cx+n@z6R;t_H=#(WTrDA(U}{G_{$c~>RX-BwDUFaW zyNo1p81Z>vF4|VrJ)f=-W?NHV6*v|)jpo8;g+k5>=*xKE4QLMgz0m0cy%mrW) zuu-FUON!B(ayz-D#P{t%&#XVPy`VM^>HTnYcYSrr-{MfM?&NtnuyU*vXNW#9ytMK> z8A#Xb)AX#k@$~Z>q{B1P58unFeKIf^C{q&~rSQ0(?bvRD9eJ80bMMYfL{PW=5Ahp(hczHVkD(M^@3hlXYP}ybV-f-+Ohh23`aBP!+>2qL08oP39_3TyHv@;zV7TGt2kbizA52QbaNaa4(@VR z%taO`O7op2TwQse9d_Q)9>s>;)dJ5yk5<#ZTbsVzk=>F;-xe><+8K~~-W%rmuC8is zQ`0<{PDEf0IO&VhFBUnpkgGL2>^tY%v|SJ4B&{PW$GXg|&X~ZatZlOqW78F5IX%np z{w|6}5d^(XnCp#?SM=lg_~^Spxs|8#p2>Ot!AKKjZZb&}EZy*KuA5nyPKo;;8#luY^8JluC;|Tk;mR_fbT6G!67iSQ%dQ5 zi-kvn@n35e%Ie+_C>kLWXZCYBG3N94w}e}hfV(k`)bcmb`(Jw{n=sDG6?M_>x|G2B zb-UZ^-0nP8=+Qk%It+Fo64k+p2aP!vVTeoBm&6&Si80HI@Dx2Pdv;bPVtmQ6B z<@;p!FG*hp_@7Wt6$PWg})&7G4)@*Tu(@i1^G zoOV6P{Xq8htmyE|X(1q2o%@*^r%gnjdu5 zu4WUcDy7`TIP%n$BFB8_&OD@5h#o@o2KPa2{-}xMKicJsI$^&kPUUIA7Z;Gdv-oOMbc5 z^^$3i^bp(@iQA>{d9kN@(vecOzQAD2kik%JaShwITw_Hp`cbBbu}c>$!5G;e;p_=< zgqO}f+W-D&v|YT_HBhd0uTNfIG(gVXET>L683FcG5^;3UFeyH>l4zLzADLzgnNB)+YV9j+ zg5`5rZ2)Wb7LIeL6n~UA(Mt~b>dtRAN?5JBjXrd`eH~g3a%f$3s=J-!jSogUA7Rxl z=4t6RHiz0w{fsL;HuPWH9OkUmiq)a6%&n>2V=`Dg&XtRr&DldU-1t5sC>(az>Hxb4 zRbRt#f~nY|Zhh?#5ew40a2$j0hfK6)sV9EV2Crt6)%#LvQ;A549aePjUqQ<{EH%?& zC?e`zZZUb9$(z+D49^Tu(UrfGq~@9w_W!5GJ)LMbaNsFKuhpnA69_MmUgW2f7iT&O zD4%EJ6`fwAUD;!1FM8HTnPx}!ti6+8vm3P4ZRi4Obr_5t;|7wY9T>*@b{_!Ui63TK z8M%caZAzA`QAZxh?HO)8s0|AWtcUFG)O^=3fuNDyd(6nR-q9%_q3l{qY|z>%i@`}+ zEd3|bzFNX#U5{Vz2yHlBS-3xLqO&FT5H_<@OgpTULTmd&~V`A(;g}C^{Ao@6HDE>*?X6 z=SE1>Lrp%B;$*OqO(~tZfM)REqD)VurN2*Bgz}@;jO{T+HH0RP2e6DK#CB zJFG=L6iuBq5P2l=d>XuF!*D*YaTgKt-?z#XNStx?0v1xOkcWDA7J#k$7b8)w+_adw zVfyy>ojoy1=h;Sd$T$kw{xfxAgjg>NlT!iyNqPyr@B_}s=W_7t=Wh&65nqfO&fkzJ zUTH4>DBW*1)V-=i%CpL73;&r&P^i8He+ig3<25c6Y4JGS@dN8XC zx8Te(WX(H84j|2DoVr;xJM7$E#t6v%cuabmzb_&n%IF4U7@r7>t*`nC{cx!7d4k@w$i+8~yx>Wn zJyaFS)X;jUN9R(TLk|-_cTN%e<_!VgP|xo7*w518s`}=7 z&AKA`so)fUL-wG%5eGqbr+EcvlZe&)18*;;V-#^q4dCrT$TClV;0fZ{ly0;d7SuzR zmit?Q)(V?%+q>3Isy9t>+(N8!R#n3B-8}{Hw%&?+3MzCj^v3fP*g3PHIT>N^H%B?# z;ixLio*+(g{<792hJ3Yg^$ZV3v!KOekBx1jh1F)G5Ihkaw}1K*^rX0AWJJ4?re!I{ z3*-#kpZK#|^8cl@h8R?$Nhnt$z2nH|gLF>y@NYd1A{&#;$DwP+z~`wHk*l$%S;R|h z4}PL#qnEkQgHE_A1URI#oRsz-$uQI&Z<%_E{IX|M*6@xK4HY|Cs!zcO1n50rS>A$@ z%LQ(^)LRTK(?p4dJ=pQ*?Rbpt`Kq`S^Vh@jwea$TeS$bVWR&KWYv8FUM(p?rwwU-|8 zrO;siaUqw@#2ZkDgu2z&uN04lE4;YL#Csvk*K`Aisd%4flo9dBP08yjshHeK^VJ~3 z7b*cKi29tpno#GYFIJCK0G&EQ;#+4{wbfu(u%_wg zJ();cAC3rUlf!@*VvQ^+EkI{EXR9)TQ}Gpf`$ToD^LoEqAd+k!`C1Ojx*hc92eR+j z3FUtt z+0%OGm9;Nw4pmQhH^ES?d+l3vbK86HZ14t#QTz2Ha?2bR8I)G1-n#;$>X1Ckb6enJ z3tJ$Y5_xa%s5bA|3i^56{T0`uBNo$L5X{XS$9dA#Mg>huLhelfQ=sjHW;?3pFqqB_ z$e&z$^_SGo3_wnbLu-8xk@5r--a-(K!Rf%+kuZ|&2880t!znfgtSz+~d3v4epogf0Z z2@Be44GQ@Xw5JbsdvLLQ+q=CL`W7E?Ofn>Dv?L!l6`<}N8$&I2Oo;zAOvO-vvWFg=?B>JD8QaadP^$7V)kg}?`I|a7;!$! z99dXue0-k006Tu;rPow|WrPDa%{eMV6@Fxlfy+3`&lhLgK>a(8M(!Kf?28ndufCfV z_Kj}}Yv|A3^Tglf-iyK03v^JD7h=_!_yef{<3J4vFY8%}yF1ag8u@%gUHc|m6<-); zcXb!YuojZXVU$B))L7ziSKQFAGH?@=bg`T*<1M%7TtBN`zfGa_&ZDHZi6VuBm)mo? zOt$~g4QoaC!9n%lbUgO_BEQHRjkX}|+$c0Eq~SUx-DqSa`Jh}oHdV!cH!hW^2EYUY zMK(ce{YbO2JfHQbK&g8uoo+I1W^ls{cJZff@016~Jqgrdg{nTdf4E8mk z9{HOHA3q?GxZ_9+!1LGt1)lc;;CVH_d(=VmrU$xJf3nJzP%wVDQ>$BHT1L6e4#Ad zMv8kwO5q;NYEZJftiNx}(1(9VKfa`Gt;>}vVg?rED50%nDphIb3kw}_c+AQ>?w6!^ znqK`tmL@_3mKh(=E3MT~BcD=QBrwT;FD4yJ7?9`3A@aO;^x%P z&kC#mlIQzj8bXP;*HlmS4PQDjeDb}m$$kpR^W#_K`6yqAJnzR_UR=|>Rv9y0JqOmX z1U_NOPb|{+)tP?&p&f}xri?WKc|XKc)FnYQAPo45J)e2So{xX73$f>4NyjkqmawsW8HPZcv6?U(m1=e%m#%J>T+*I8rXD`;tXt6=9ac_a?(u4kC&=<}i z{eA5cC8x~&cBhNCd`Td2VxV{+Mhxakq%0T9$bf56y428h8twbsk#v;?=I*j-!SqwI zkk&RP75#xn>_?`)DJ5qL-mR1WNr)yceptCT!0doRW1$w(^Ni=gsS}? zcv^|G2H$W0u(Q3%BjhWmleX`y!~DcOs8hfFXh>pmIkLO841{U_qsgh$4faVZjKVT$=#ZLR6+f>2)Ia zbJTcuIv;gUVvL|%n}vA&dX>_;9k;NwqiJ86{Hr2CF)p@3r=fvr8>0Ndx(6ovQ21zm zUQWNLoJ(P1EU6;RhdZwC77vq2Cx71KYXa5UQ9}O7pHyh86Os@^^U?BOCCGOir^Q>W zEVGIPbg*IE!ZSf;ey_{OC8j+Py%bs%kKP%j!%NFu2p<~iz54=T)W1M+a!nTI`Q7VN*I)BaiJLuCayGV41Dq%U)?0harGLO z&<(=;VIf%w26q6ofAT~rOv%aj;=|R*K$;6ZP+K&1h)Y!fE#E(t`<3vXhzzZvlF$q9 zRkYCfZ(WSw9oIbQH)U+VHB^%8>doeRmc_9`M;mO2_C4`7K(z(e%F?!6E<_00wS z5IvmNGCPKtr2E}O*JglPy{opdBfgQ}_ov@ZCuv_|!1ae z#(?q>7vI9n)vfOb{$Pr1j@0>$eauB%h29Ey2f@UC`c6s*CdRg=51JtEr-+YytMf|< zVhTpgJ3$4stNR_Si>EyDX(o+!VFxMBm}Llln3;es3F<^Ke`$`?(s0#kAg$9!X+gMVW3I|Lz$OG<4ZGw2SoYIu<0h3Tq2 z|Ma!O)G=zC8q*uf%M~08Jz=jD{(K{Ig>DMyQ)Ucs4~A*Y&y*)D3fGaS`6+pcFK^n` z6d6L0gEJ9y$St2aL7sHI^ut%BZOG^^{A&yJYQD8v>4^&#bfYxCa8%c+HvSy9&1Sm* zmi)yPOWt1l#PCne#un6lOJKcI*#2Od?zOJORu&XIt>8~rD?u}cz@VZ*2*b;{)Phq) z6|pw^#&(`NeZRUWk|>r}SBW-QP)8}x`fzVpOQQ3wriX-+{-W-T8gk}l$3PHKP2a(C ze3BD5sMzBK7xz0m67bwz{3V8+4Mr9%(Xc5fEJ0XgiO#=P#1iX1ts*r83W(9fQkTaA zR`s)12Sjoz>6)G4w$AFOn<&Gre?pXM@f=mN z?$~JDFP1`$YQp1pj>WU+@W*Bi`%92f6W_jOV@Ww?tA@DT>S@Jg@^Lf_b+N6?1QP)G z(4Q47g%DB^nwB8qn{tmX*R7=J=w?P1&S1w8dt$EGIQ%SvX#vq%%d=hpr?()6SHx+; z!Zg?-DEJdU>(-ry)LWwr%XTaxVb2$wUBfg6P?CRdVecrIkAz$c$fb4p(t=PNX`iJQG&4#6lZ zjt(KrDQLjZAjXV^`|CAk3>3}Jn-pVe27KesvG|v)LO2~G^oVO?_P%gL9BV>FQtpyJ zuHSamO_wU*$QG||P2)0Li)eHr41F`;0MtALNaA__Im zhdixB%tp6*IC*;)h9W_1!QZZ(EN|6(O$ z-gWLSA4foe#jx5~ED7ex&V90$-E&o&I%g>w$CO_vuSwN9q|0T=lYBECl~UNW;($`D zV2RXt@qQ%~UMyi*%Qp96blqp=c!l%avSVu2h<^PRZ@Wp`C#eHhN{N#tc{{eIa2cYV z{?w!vr9k?eou;N@5#sXVUjt1X zUTu`OJh6$M5$PR;uj`>INt(9x0z9YF*621rt&?esP<=Cj<-`dvqnUrsKbm^-e*1R= z1Pa#s1Ja1*B<+i)aXznO?eKPdi5vW8tEH4Tz}Lvv&_&esq{J z4JYsUY(f*|sx<^fQ|u3X_Fe3kFiLkmd`#CoT0~v>!c#tJlK@+$O5Hl98}dk0W^x7{N_- znE>03gW=KuitMEa1a~6PvhIQud~fA<76oE~@k*{|aU~C!X`&7LI9gr4QHZ%g(!$Rt zUD%sodPGIHVZ&o8+yagN`sWV|`F*)Ku!rI_USvRnM0&224mQ3%8q<{i*ZAZd9n6*> zs|`n%Ht1wx)_9Y;;jeifFy(UdvVIleshYZRiDfSbuzI=!r-|Tfz>__jC{EG{7k;FL z+$+xOVOWKb{^tZU5L?}oA;l^cD#1l3&m52fG3%Yg*W_Z6hN^G3Aov({Zo3x^%_m)X zBQsHqC4hw z?)(kgvWI48-$j!&UKDI>Vk3-~YdJh(RIM?dq|?+^F#I}3om>Ikq{C-EKzM2qsAt~7 z!g5{^Ppuwu4iQ%G$HjUjbo#h(%d!cX{wtMvQ%hH>RU5+h`PhL;o-#?Nn*q+_x6j6) zy^K5c(*B2pm*ywjb2-00=+@@6nOlG*gRLy?%_M(lbkeRHu&8ReUC0}e8Bp~s7*0M}##}ggpK{m= zrf}ard|5pmL4!Y)%KONCTHlI8*I_W1_wKR*NT+RE{+FjmzgRgq+4(4RT96K^1ipdF z57o;Hz;W5e3Qrx_hoDCnK;G|Kvq`8Cu!`fcB;h~QFGR)Nf2<)OsMH}fB$qL5+=pc} zaA;p=z6+E&YB7wCo6hDf607)AyCHs54zP>(zOQvfhI57hu&ySNKh`E1;hX2z>X$^D zndLfs`S34Zoh+mE0%gT2aC+w!UB}yIrO!B@H*uZbck0={e}?N+X|qj2R#7Kt{<2h7 z-0@AsoAIDSS>6yAL&@~o6y%@O!W>7{{+Wj9jy1^&WYeXC9l znG2=l$=suarBv$OvwO#ow$_tGZIV77DXBd%S!$I;H=FJX5+NBzb>xYWd+uPG-ErW5 zf=L|^a&qG|VVZyGpyw3wFunNaNPba$S1Z-DdRmYWwH2PZq4(ou^dn1$d^X#?91+*F zyJs0?;#xBon>85iaskKElcnn71Q#DYRV3oX2;!C>mOGLu)6-U?}8u`GLX+rS3<|aKDlqvWW}Srp8Xv3sx|hfX2)$Vf4el!<(TrKBzILcb{?+ zOg{t#K}^I~to)DXKBmtNX}i)z(>4mLK)if+hv_QUD{Egc@4ZJVGIs1LW&USe!ee+)Mupk9=;^f7TPR1CQw3Tz*a+T~F)AwAsD1(5T#+4^iW z_A9%cG+wF23d-Gx@MVL+r2E3!mgNkhk@;dJy1QM6V^hzqlFP-?m_kf)5xXt`kxB@;=TJ%-wPXGyvOb&s+Fb1u`!S}%cf)qaLxH{u zypk@2gE-ypb6{FdM-G_hyu`1i>Dlj*;YGT}y*1JgJ-DB(sl?)R1U_5#XPah&ZU=>i z!-7Hd*GCj&S%q@{STUAcWJFin@#0|CZHXi%sWb-Sd%Q+SAP?EU<9zqcOdW1vJGKa& z+{*MW)k*A7nY@dMjC~G;{_oq=L{4h!=C@!tLZ+>}HB)2| zBQ)Xs%ke`Kqj61DQFhg&xUp6>4#Qa&1vAN=Cq7J9p(srRe>-m9-xum0Muk9R`xV@p zXq4TE$~omw%@X~(7q#Af&hHjVlZC+XV3~7AU9z7Mhb*`=L09Y?t2C{Qx6XJU|MHx( zI^$z=d_#a-oGX?(xA{f*ito>|kJ&+}bJime#^()@4R8vDg7x4`zWYOIco4nxP#)_~ zV6y5RzuR<JkSSCj%el!-FE*Tm(O0(^QBQ4DU=IH)#-X~_ zJ?j=u0an*nf(HCqt9>5w&x%D7IP;$ke;mM@hSBXMT7X;k<{5|C@8=+~ojZN89{7H` ziA4_Lnd!uoZ~b^`X;A6^*R3xgje%tmht|bR*%#9$ngFjF19_3Z4g#3fEvJr_`2A_N zih1Gv+gcj3{75m&k2j587B3wsuv1-vPz=>ssbNpVy=^SVJ$m9@Gv+lO>h_?(%8 z5}E*e#}m;4yeXcwz~C~!Q#2lW$dQOA;Nf^;SkR5LYo&yF=?}&ZmkV_JVjv-Pb6v-K6c!N+d;nU2Muw~xL*dzRuInk+iymmR~%%t%rM5k>g z8-)Zj$uM0>sHyr%LI&SMe$g19AvpYDCNS9jfQbS5jXl-ycGoB%mp2RH`8Qn`YPXda zza5g6qCbs$?0T(mW+ej_pW@K>~N0~>bF^oMeHl7KAYE7}32t7|L7{<$w zx=JE1-)D0>oEj%=2Z6m^5~3O!IQeTS=t7nu%l$5@gk`yp)X3pK1>k0zu++F|st85kDwSVir? z2>Z6`suCo)6WH=Ll8+X*%L4=gn@OXwDQ$rY%k*jv6n{NaCawjX1T+KDhKH)z_dToE zUX*{X!}*h`yE2ivutY1)+rEcq{psr39#sq2G6!j?B5#B>F=jO9ybp^q9A3{LAp&`} zEn@^Ga;Gc*INQt7m*1O7;S5)fExa4wB@&irV!`KujgxJ)uh(~6_-wl55JH-% zU47a3!3#YS-zY=ey(X&`6AoeKIX()G+ss`NFb5oNj-gbUnq7QdAQ}uX1l_rv&oo2F zB^f~zE2^5{EMhtKsGes>6%fcJ{6R383trr(D|4J)cSPxw-|2=@m)PJ~ceKmPx}z>w z*Fq-UG2guC+4=_k1K&juhaQE9bS7RBk8M>`l!!$E5xl(B^C&Ur?0GEu^hi_EX^$vi za2`|`&QXg<5sx04f5xGgLcUf+5l#I(^coi`Q4^SzKxvSD72R3~ugA{)wl!6Wh1cBW zcu+9oeW}Wvj=6(_Y_3CEqQX-_&Vw*rHY2uGozc5TQA3=HlfvZJ;uj^UL&gKWNOt6u zrMCf#<8C#w*w^%uLp75+2GDi?L6FN?>?oCPrz-^Ek)FOfnhWc8Egayg)M#Mh`^xb~ z4A|NhkV({><;IY!O_~?u2kb~2q0Bo85aeI5OPC~gv<`DFpazO4SlYnA+ZqWFq4{Tz zR;Aa&C#jn-yq^n+J96t-8_vCr$4S+|zq6EmPyNtEupT&1y9iY+3uZCy%m}vo8Xnzt z5kTx{--_Z(r46QzkBKiE4za)REUnBwr9OTE9H*N@@*R&{>O%9Ah>~c#ch?U7MNk6b zuSw#x*nPaFo9;$`F6NIfj4w}glyQn=a)>!G@Te{7wM-ZN#K2WwypyxI;A4vOA!$9M zIcH`?L_P~PM)+FnuMj;bkCCaQa3TT9b~E)waIi34W#{j4BX5@{ism@`_Lbehs&umN z4`3+WzK4`79|BsMzcvvii@@R!9ph-VCC|oZ1I70LRLf%MQp6Sc>^SE^q!1g3VJ`q{jCmiwW!#kq6~sH_VAnE zdEz%AKjO{7C=4*t&#$^uJse+S&h8c{%)ezdAhjDgcS-EgJmbF{9^mw{Vs|thl@%u6I7!=VDKO%M z_pm@#Uc1Yl?&tf}yo}KEW0aR}$u-Q|SV_`UP3Miso2o+9UIHdhEMns}+8HoC83>XB zNi{a6r}~p#GB<2JVO?*_jfYIaMok=a_iIjC8p4~%rY8~AdVj*+YNwH`dG-J7lfQaHmc;{hKXDl2Kh5jXpfK;(oHf8!lMpuu#)dVl8uKU<1m6$Mo@h&+E*p&$!8-_FZ-oYPmy zW64HQDZRMPHNEwBtO7p#M;Nc37q-xnCF2EbeHh#ZnD&>4x;)tC`yMUge;Z|LGYl-+ z>o=WNON((VmGThxqSgDu$BFtLUB{4N!E&c0_ZXQmbSIZS0cweO(%Maqk^~n4c8Qna zPIdLrovod4yHQnIv{eb~4XKDSOIjov3nOP?Nhjmaa{ARyF(Qe-=F0N1Q0Inw=8I%bua}eZ_Mu`fHYSX?x zFk4XPhit~oPC*A;I9#++em=j+J=Sa0|*-#dqvW9?y zZDTS-uV-ZI@q|q4%0=89Sg2}ZCg4}ekSkZ(7rVCTMTO;!4<`;|pSi$z zo_fw1Ftehf_4*c-iYcO>o~Z`VM6dB^mXq@`@CuO^4$v{YOqP4WZW4_enjMh83A|XtgQBJ z$vT(iRmZ3L{)o7)j*6V}XU&yvPuz@gyilLIJbz|R=v zYjNk1x$OJ)8R$Lk6#=cZc+cN{PfSl5cJ6bIYg((P;@cyf*^@eIC7J&)%8FIt!kuDC z#L7*3R0B?3H1sc)DJ_Ld5;@ym~u5UOd=UYPXcJ zx9RtecEi?T#f5OgZ@~}U3j+ zidZxj30d$ChoogmSLOB>N1r~kU*qOr8-uqE__L`dZ+bRL?L?^UE3pdv@p1FW8IhYv zm33BK4O;xHf=Mx6#g~}^HW9ahX4kpsHlYngVBW{r?;h;$vismuEFgllb1j(wDdEs3 z*UP-Z^(SxC#UsR^mBma5-+uK82O*0jwI(B6n-Pd+K#iu_%J#5((|*Qtn6v7 zos8^`rYkfQIW&S?=vU*1!wWwy3SwlkUbkW#`2c$sag%`uKTvCZO(1*L39G!dP#QWdNAoRAL(ipOa2B5bwL3$Wl@`Xr_@bJ#liBwC; z14TZ`q84?^+{Wsx(1}+vcauoEe06fAu&tvYCQTdIY@;Ry=Zgpe3!yjIf&dgotsl#( zj}=bJ;%Do>-Fm-&%<82Yn`~0&9D;+ddh#zX)-n^iSfz#|dKPt*WJXTUJO662Q~lD! zg3x_uq%X$H;gT(c)MWGDb7t8Xowf|`)6IUg_RlY5+9Q;HlptbUb$i?MoO~NwTlTPA3(iNP%MQ&4*h}!x&OF zH+K?kiy(0pV*IW9MQz#QME(TRFANzw8#Fx1+^hqt=A%cDVAu}a!}ORxN67K*SUus) zZ(mr_s}AfzMWO&b_C-89Msv-E?;NJfcdAX_rF(PnNW1js4^~H( zvJ=Uq)6qX_$YO5rK4x&$Qr2azTma3Ip*f2e~=iolRQ$ zI{6Y^C;-VcWZDE(BivSF;)bSawZTc3$M;*`6TTw@E+ong^r*Jxyr{}ws!5&i|Li(W zuJB>+VqcnBXc+&gBWVk(DfH`}_lrwPBy<0@6O)##7=UakGlxW8$_x@Hmt7;yl@u?` z!zVenbVQgmN%B-OTqJVhy*M5trmq&&x{rOqeh)F&XtO)qbbQv*RQ2p2Sp@0K&c3-j zL0Lggy`6qSuq89GvxiDMJNd~tLvBctq_5_u2YjdK*sn4AbWaCoy#I)UE%~H3sKfo3 za*@d0v=B>nl>x6$lFd6`G9?60`MF^%Oaj%H)lYLI+@S6AmO(P>@AH?F1_lOIiu>WD!_(Vz0{PW0yv96OJ_5JzGVB5#HdrVGJZyujF-I)MCobn`z{BSIEEbq^i?HHP--rVOemwbo4G{5YcmJ}7SF6NzQZq}7AFdZwJXwT-v)R#YJ-DW&=Fmm`Y zeU%plCc=yKmZR*X@f|-Q;NoxmCW>szL|ib_-JxDoYs_qQ1F};#qgr0hEWj1NhS=B- zBpS6$waqMB8^K^`VLcy)KJrK779-204my z198g@($dpC667Yj-wGo7;7!3*_?%b?cC?$L6lmJ-J5@{epY3SOm@%sma2(4h?e?Nj?g^0*Pv82fm`W*hsVVdt>YM?91TlmM z)wL;1Zi8GNrNy~I{^e$K7nC7bJ)ye`4z9RIc ztsu3&E&Sj*<@YX+iIq1raG2Jnl0|EsL7S*SHEIp8u??b6T&Byotj?Cl95)yHr4#n+ z66>p)aV=j?>Oau+jo0%f#YFtbHZX08&={|mNTm<pu zMGEQ*jI3u_FpL`1M2X}X*L_)Vgbkv92FtKGw5K*_$O3t`du)B$liTfIU%ZO>4F;QY zfTa1PVX#=R>8G@?(|m^`u=I5Ii}9VLXO+|{FE#R>-@Nt|=?%7cCFHOz zu(W6ytbP6!Ml5}aUj|G$a}WdU>QHBJpCWaea_atxpFG0~$AT!5>pVZfBD(pr0u9cm z7j=#2s<;ICZc<<$!>PmfqW_-TV6B=uZcrFBbpn?r*t!z?x^kUkJ86#%zX{7~kr4T) z+=5f(&5a;38rLQd2~|!Z#AE1mngi~I2Sf?xp&34=;xGo6tnYW7{fc_doc~$vs7Gsc z#btPCt^=uGn}P+2{f2|EotZBZf+2`yZRWKapB|40F1DGLX>x_i<0 z5WjFUgI-Z&Cb`vcz7+f@P@|`9bfa`6G4sK*3|-s_^LGj{h7f}TT#i52__n|(Sy1LV z_)eG=7;F< z!#<&bD9qm7iwTGN4YMluoO@#7YGM3d7KzL2MeW z+a*}Oh{BgVYXT75aS53)R8_^NL}(=u*kM1m&#y-S-AMIps)}e!+^BaRnF^DXH14aC z$Kc0bJvL{&=U`Y1{w-$E9p~>V#->i<4Hr5_o)&iXg%ZiOhhC_s&Y7UDvsOk=Fgsq~ zV`!ZvA);(Ow7iRSiwXB?zxBS1EJgoC>5=cZSwf=gZ6=JRgDKtT9SXOUz-8J7z;e5$6MbN2N+7a|8$t-9#oFH zS&tN|#V+dN)Ig(0OL3ANdK|PS@O_sbpn)J!(=n&TZO7bvR$PIJ57Q7?Ai7Z!g9ge?dp3r{&q)RG3)R^Vw-~wMoRo zX(u*=w)-wmQwMw3oc~}l%(a$2(@W6Dv%MotCO}{~d7yM`Otv*!ES4(u#ZUZTH)Ed^ zTyR$CO~VuQRae2k`+U+tlvG7gfsf``y}@0ze8ub(;?e&Ex*sM3A>t+D?}2>yn?yDX@mKT$ z22_|y^kBwNDHLcwRN3N-bs3jt@i#Flw;3C6m-%N>z!QuO$Z$p7q8P>>vbTsDH)g_< zT++{cXqv)LDOQ-y5EO8&-w7+*Z^PD@5ZYX{`>BQ$!D)H>pve%;)~WHYvaFsV-DDq6 zL!>H!8Wr*W3bBEyb)hT>$+$bplQ)G(;_f$qB!P~DVYAuW29}iAmJiIdk6sYS93`h% zOk&-6N_R*1yYLl-jG6$${bD1fHiO&8@u1atwQ|e0j1BDEJ`#5zJP#z~6ERr-7owUo(^0l8znCTj|64IbC`@*qI@~Guyhv*u zL-5A9p^Hi;Zwla{t#gqG$UEQvD;DQ|fr9}a7T$Sm7T zzH`@jTxySUV-jK=I=SAq@ zE&Qtec#4^UKJA{J__7YvDjb)Q+B{HR*<252fV~KpKf%M=1=d=acK=zdxthWkyHr@i zJTim^CtWZe=3t4%(G{NF=&6Fg%U+k-S__$OIu4h>eTb6Wcki_8ISy=tyt$+0b?r87 zqE??{0Y-vz##12ADiU9fH2EX2Y=O+H6>_u^Q zC zv5M=ZYCY@eur+4rt1S~IKbmhiF!#itN>lil`_6u>+IQJ~T!_UXDknX6P_&~e14!3k zN}^rhA}Wy)7jZ>uAdWPsW|--wg1oS{Z2Nzot|QbAm49AwrviFL8%rX${Wv|VrvaWC z3GmcDluAF$f9g#1SAeUdwwb7@4is0NC*(7v4Q`*BAwcJjuIC1zQwt@Ey6A*d>qpxWekhjA(j@r`q9`DDgk{yeC1aO+nHVzv>OVa z-tJ`&-K%@T#3CPp6^8D~A`Zq{7hrmgbpbo!ZME;_bM9-rOtO&-=0J6|UBFVeN4t9(aXtE1lYzh9%pZ~`I;USq~37Ra&;g&7pKF_)AfNXbmOH>^I#IyLN9I$nDS z28!mlB$Hw6mTq9BupoX~&8MG^}C8VF6ydQk5TYg>XA%?&V zHZ%fN{=*p`3WH5Z9iNe5KJ>n&@Et~$tQfT*SYE7f!j>2Ike958s}Ccsd`p;wwu&iNvQ9+dx=Im-XecC~!S@HJTDYRayuqonX9{OyRCbK?WIvj2( z-?mlJ=9i*~Vd?F|c&xm40{LZ~OsH|LEq*45~BH8V7K5B0C{qjiRd5$eH z4%1uToGqMH?NOn+PFXmf4IO@6&rTDqn{|{>js+k*TFn5l zSKiu*4tnrsVtHDh-=v`Jwn@uv&73*CJA#kHEr-$<+RsYXMD}6`78TX>Zs?c*U__1``*Z-VTO?Qc}96*!jJyZ@o;1V z)*yGBIM3%%4W|w}oB`HB7-?6OmZNrZ>3;JOobxOKwPD)!vG?)w)OXs8ve0mf z9KT?(?*&PnAswEp_3Bz5{kIl7Sg>>cV3({cjshCdJMDKLnYaY2Mi&YmNDGEzNSVRuI>Kpq>oX5j?q4B}}r%fP? z$Vs5{6EP!aYnYpXC3ekK0s=Eff?Kt|Vx7w=a6i-FKDJ6AGb1Zf&7yr-mazvBpkq>$ z+{#>yzim9`?H;!7C3gKwfS$f0KnMLy(t^Zs5rM@&87~6aIQJl!X5NHydB~F?Y zx{XDmWf_SeFP%4{o*#MSEFw^&q$Mu}gd8tEFCgS3!VkPp40}7($zwZAZjV-^eG0$O zA8U_pL*WSFC$=>2e&Z$?%lq;S4CvZ0`kcLg>g&lC72ZN`AU15|y}y50F0isx?zmLMoXx2D@Ioy)tG>xgxLo zowO)CWqc+xr4vasM*|nP626~g`-#VWcG_49T`qA^Vw5T3ee!|x$7%zcoEN@PPY~3o zs2r@%LlL*tISq}3EFY{cFNe!Gqq2Q#wFy)I(hYOdv6ovEu1&x48nRZsy)8{4_nSQq z{sr$I4*oy-mw+xtCKb)6*Tkk6Kk*dVqi}Zc*&MBirmc7@c{)u^$>*2#1vLossrhXIZKvqX^E~UhL9C0v&H`M$jN7!oN%9a z=%AV5Wf;HOlte=Ysx({R52xt`snFVwnub^Qwexi@z4q0M=WIwuiWsKz{LOA7ztc{~ z{etuMl0$xqrn+w{zYnSWMK9tjf+5RNX3c?0-si%*h8ODtZCU=8PtSZOqH5;nbBxWJ zKkl>{P~_;3%p9IAyskYfrYgBFWU0dKY6|^E3Rv=J9Oy|+7-SbvmYDRsDgs+UxTp8o zu0RAs9>WHdo~8gwjqcL`+b{6f=3I5=K^~kD)8MwAGtW3!E^ny9rtiowVC4aANZ^DY zUh5a&bxq=SKT&Bt_PWD%Zf*+ck2ykBu7EP1;)F`Bjhax&L zvZ>)ruS>bmZo&;ev}7oY+(KW~rgvu>`+zetX%E>y&Uv@z>7JQx>{%UP3uWwrbi!cF zs)FB5{Y9B-7?v{%B7Sz4btk6+FZ895ED2_7+J`>K--E8l$$4uNYY)ES48CaG4}5cA zRULw8+Up?&a*hRxAOK-y~yoa(_jNgA!-0)N_VT{+Q_>?t0X!>0RZwo6;Lj!*9n7>_Po#5TCkee8cM zOk@tsaNXv|;%N~c5hOe#431T+VdF#t7s!>t`LAm`yMF=%3S$G{3{s;FWI6Bu*`<_?N*AIc2zu(pj z=s}0XhESx5dY|YOntAssQ|-eX$pnLzG(7Z1+B9jKFTYzzn9(ME+&NpG>)G<$z4UK^ zd!L>o&}ys@=mU5o2%lLHLz!IHj~Djgm;T7ku2-g?|Kl_Pr?E9bwG@Lsn z=d*rXzof^X=@^mgrx;fs{IH7n@{B}E|8(!24-Q}_f|M!~5B!c_OI#y9-49F8ce9T)kia{sn02uka3Bt0q zMZ=yJ9YE7}f&j=U|J5%JfMrI|w-VJ-w)KJL5`Tkt!}Q^#cfF1jYJ;lH_;L^VvCoIw zBBE{2ea3h*RpGLM|5HtMShCb7uh>WO%l17N*WAFybgLisRMUU>oz#ud6I2y+**#rz zzx}nate+~^VSdi>{oVh5MD{204-E*J5+Z+|&XA@DR|w#{_h37U2YGnGLv!lR|EfO) zZE9M~#d2+lfu~;e77@Z%Dfno!A$}^(p7p@uDb>5H9 z9|7ls0KFxa!vKzp-GJoWgVSWkE!<}T@$8U;XzO(A5(q`J~djRQYz@#mK-1hVN;@`GNt$>4I^iZJVb#jM1DU+G^M)-oB=2ed)t==J5V zO}+Zl9xnPiNOe`5O`m(iePG?{Tzc`=ds36oZ~#f~e8ToluPM8?PvUT@^%vDD*XozQ z%IlRF@o!&p^{`L8dH~7f7I4slt@BgbbT#g)! zOYV4YNUs}6lDaWh-%Fdk*-vcvy@@CpYxWh-K3s@fAfm-p0Y{bF(ruPATiGAA!5Pun z%R5MRe4C=_kk(mL}VJ&0Ww;JI|@mvf@E!7B&TP;Y;eJ zy&vO7WMto10 zNp(2_LuXM@e?uk(;Y0 zeL=BXY$|4G0oe7fi-}jY5``|Lrtz2mhZ_L&*rOaR%Pnq1+QK8nRk5*GmX`naCj9#) z7|juP;yKb+7hUb|QYE#LHZ0iR^Th`|!kuzyTLS~@Yop6o9X}gL8G}%kS0sx zayj25B|#cHiptvFCX(B?#>MMgM>1+G%_m`8K%$ zhoCPuw+m-*;RMymCWgoVzhy84&*Wzmv%w-Mzu}W!=QT-E zF|X`UGp}vI@gzqY?!>J(swm9$5rJ{U0P%eGWsm4=*L@UcO19G7&cUKk$B^1i-{Bu~ZQlBm9zDxPhwOErQNO92iu__Ltfw zNBFNgY2aZqPM#0{j~}#_II6T7-`0;^K9j-NGeWoGmS3!J1rSP#%>5(s{)g9C5r2`~ zQd~^TOvABsiS}4BA>h}c)^2ywCgiXnl0q*+cko=0dTE8uKs^nKUu;m;tSdX5&0mnYUmX-{{Q!D>VN*n zYuwTp_DV`hhZhu9_?v#YGJaf}Os5WHjzJ9~o1qw(FiWf#AqW{YU@c-cfiQM7U z)zx4gcp`&_{{*xA5ql8}O4tALPW%u5m}`Umx~HX(>?JN? zm|j3+L=Oi#$=LfjHwtPWczJpMAS10+ZeS~w6UqPZ=l=JPO=u%pPYdj~vl9FH?}-W8 z+H21hCixs(1P%&oNuX)N|6=bfp->@qF|rEasYX%rWk`?&}}OgpxvA1?bWV z+^P7#ydbgUXc>+cVvxfzv9Y4~cR+JF8;)gqlwH2!18$)Z%m7HVM2AsI29JL=PX7LS z|NSqV@e(dy&d?h1WXv?pK%sBm;W3MYyNXX=`Lj{Q73I;HT(GYu>G>BT_X-MhG!p~` zL*)K4U#rIkMn492EthV%zL+neYgIHV8E}VRp4A>uo~ne_Egv2pDR6ZAbiwphJ{?`L zR6(zEE52p?5X1F>|AIHJ`@nzx3jED)AYRjt@e$dx#Cbc~<+UDp8jZLbrLS*8p*l`! zSy_myEbG#e{k^H_LqadDzxmz&{GVlrKk38YiX$GyE0^GuoL9}&6#UmSY-Fc2E_SEvREoJZRbD4PYVo&*wEx?ym#RlNy9D)&#F_s_ z^i>gDSMR2x*=n@c@4_X<;Fgw_KKT}PV)57akzacd@)M!u>W?2b@5LSM{{21ZpYC6l zjz}O=6%-B#`OEvxug58sh0RT7Z=JOod-S_*WC?C*MH%2u8W?o0O>Lgf1^tzm?AP!1 zsKKZ@5MzCPeQKifU4Q-a{`mHv36XkwF8?S}{!bqo5{t-kbt$pe_~YE#Z@R>uF<5>+ zM`l@BS$@?OG0(pkBmB9x$dximvUp2fU0nEw9#@I}i(TR0{VIGFztR}s^Sa4{5gPZJMjpqJ*Qq?yCzL8kK z_fr42yYJrw%ThAfQLr&;*dZ#ufy5njSuS{8{fAYq|LAYxV}d@31Ot{%Mo9@x9RYCk z(GgvWN4aBUzv%@_G63{#-qPTLhZY+Le>nvHb>A%6k@4~IJ&&mE&1#wdG=ccbcK`~X z*@jVn&LRBeto{MdTRKZ@Qhth}2Xd1GU(OfdcYhUQOp8kYNHJ85xlQ zbr-#+A5o%zHVXdo{k#T_QvtdU#DDpN{`dee5)4JGW!c%;(R*Kfep76cv5cv!rzf)# zM#h^&eG~0BJtp!yw885KGlimT6@J6Ze!d$@VPOCNfb_q; zN60gab&07t_U~8`-K$hsumGG}Q`uQsEZA@a6+9bP;})DH^M3SO+KlV4>+*IQw_nT4 z@zKkCoGj?o_O{`63({jO-z5{8GRZ&@1d==I<3!+#w(|M4C}`W?p6*{MTouQ#gZW`Fj( zwtQyfau{N4U|>LvV*b-_TmPS7NL);+-}KGZG8*7G{kf{vACw)Pe$!`?j=+L$YBCe! zbjs*8)c&p@q5-a(TM>K{?D&DU@yWj_-9hz`F!Kq9+(cl(F0U3My?#$#1}q8l^M{|J zKA{Vam^l2VMH*Z!m8k}veR5K8b>PwOxzG#{K9#l*ASoWjjFJAPV}#hU9f6}9f%qXP z{l9-9|KBhH8B9nEuf&IKq8NYMB@%;HR5?5TkkFK@F!ke*-;$R2!_+gfvxSLsD$3h3 z|0Zz%U;pzWJ-!_L@ncTD9>yw`-xig8`$mqFsf_q-x04>3late9N@-6=MOg9Ax>vt; zn1fqW<5qT0>QxUF-B+>_WUNPbR2dKM6EpMihX`Bf(y|M9w{Uy4kG=iZduE_Q1_v(e z@ip?w@Oq_x427ca_0KN*UpB z6LKR9($hVnKYZSpZ&C^}@u~WWUQ`gNn;X2CD@I~?Ho5IEHy2%ECq!F58vc*ld4C>t zZwWG}R2zEz-(@;d|KrDsRgdO^1?EM05tU17nwbI5U082(*MUZz;Z^8C%!=T_I8~MH z`6OP?-SvH(`>$_5hGIrl6$5cna`J_5_db^WlLyel27}imwc##lPJ99jxknThhNa|4 zKh;+Eg~{V?+GyTC`Q2Zc9|}c~G5a(Ahlxf;D=h2@H{?`s{buCVdo3_sUERhHCQY#J zf4D;+({RdHLL5R$R&ks zhCYxYQob3?wx&YVwEy}2{MY|sj6e`ys9*El-nmAGr!R(Mk8_;rqmA$?)DjU&@}CMR zjz*sCI9dyNjfG8_zyef#Kk$bokCAYWh&`vKew0`CDcXNqg2r6Bd2j+hDMIA>S;2X4 z;R>eis4h1A@uhyUXJfDb;eGq}{sWH?IYVpgx)RP}Yr>3)K~(8=G&$PnThT>|z{d#i zyR4gy|0%*|!F+f?17Q-5nOrRABxLz@LFnxb^aeO$a@Si=`}>c`MsvN=C%xMjGYR5L zglfv~sD^O&D`i?G=ilt_Q;grWPM1R4_&WHhdvo~O>#r(PG~oU9LC1nUwrYQc1RGk9 zHVn*(J#+`tS&hIH=E^sHr_P?f8*ndqtM@W9-+pZ)U;_Xa3y&*-(@`T3^pgurxX*q7 z1AHMSO~o&+#QbptKPi7d_e?G`>MNr&Z|~tafGiO~qF(?_d%Rxwae1lr@1ABx5hh@x zo?(8cTmWu`J!AS+(+{h}J=SaY8=(n%Zqw$c+V^`025NJjLUvT99$molbD27BC3dt6 zxp4~l*>ks2Iz0gs(ZbGyMUs0l#ibCi?eRAF7GGD_HT-P6?D9(hVc8S!_3V%y2`8Wu z2N$4#XY!=b5dPra+5+zcVAr7>AEvr|LpfH*yy1P-72>o7hQ=f^3W~;uCpFT^K4T4h ztpysyqJlNv^h2eJdc|ygtnXgCZ*ZyJ9Id%RnUN2N8YNt5V?{vmN@Ea_UWgX=2F1D` zA-sbbwZcB23@F1e|~E|G+~Qx@)fmWA(HWu@zGTKJOH$9Y4= zx@$zh$LeLl{(2MMqdX=VTK^$vojJ>7WV7sDiwc4#6RXjtID8_5aKBu_3Idq&xE!Tw z+JOAhh{N_Cp9DCg8on$2Ilk_DHO=qzPzu@l{axW&^dC7OQkoT@0BN3phr=rdhE}E_ z;F&f63SER#ziu5F*MJTs042gg1GcGl2Y?PundZIqWdpcQz{#mN!8|>XjNQMqQdsYc zx0GMX$J%)~1`Q(GCv`>5{^g5~E=wF3v&&s0O{ARw(zxLKiV2bTz))dYGySJl%gF&VMbI4Z9=iLI|wH^{(sm;v$>;J@J2 z6Z#!+R<}vH8%3BYMu66|(Wa#HQ@i1vST^7R>T*5|jBw!XJQD%0O9QEkqE;<*cOjwaZmbCaG|WzD<%>b+HSg?z$3bLk#B+!14au z{LvJlLFV0*Y$_1ePHfS(`P3hIXlr2* zXuw%k4@{tlj`-2Um7AAz3g8-@ZY7xY==;{?rg^fSi)pv!z8qF4o+?{>IydY9M~J3l zwH<2)YKq7mm|Hc0XF$GUeF_GZS}xsR1Qgs4Gtbz9!9dcD_r-0j25A*mmmRvr_n$y0 z#`LKBdD&Xo3=0p|N%JK=vc8+tTv^&5o&w75b;6}9pr%Uo}jxuWW zE%rHs%7-t8;vuu+BBDs$guAQ9a2~T~UzCR5IJ}SaEG*ElBXcgBm|5nk-O+gOmdtH( z6Ifzl&g{jkzmKi#ayt14cA{C{L7Odk=Lyk=9f|I?CS;qRgJ)TS+F9;%cV`!iW zbvE0FxmJnWwTmB*0QVsXphDQAn7%*8_tHi8P>z>7nQsmy!WcAF(w10#aj2r1pz~Z# zd{5ApraQ4Wco|q6wACAI*{$*4)3wTm(9$kRU3Slr1lsrRTLDDc^F{cYG!(^3*@2S> zy<5D7MgN12(?O$!g|okaRseD`kr3?d<}xsgP}-?NPF;k>MXZyFatTv4+B*iKI+ub<-8xG6+6^ID6&uN8kVHQ9vp?TGYc_@?CZUK zrXJLPEWwt6iXxga%w+gzM(QZKc1bV-1)O6ECBR7H2Qa^M|E|ZtsGbTsNqBhGOOCkH zom|Nv;F37#(8ri;LwHV}HTR=xV9dqW0mftV`56 zoF-m@uPy85OW6o$GZpQ)iGfS*(jzqu5L=fZ^QDmGy7RO9O+DKD``C)UHxP8%Gxvru zIY!^-v|z6?*QcK?&DoPbIcCd7T0FrfPs|6j@}k-7pX;fLP|S=Ki7NdT*+^A5PCAjk zXE^jQGxb7uL=_XbNE9te22m+wrKps|msgwRM2-&T$IhOihxq}FZWAE1QwacBRxvsU z{GvB-?YIbyjM-G6Uu~z|Ku*a8T-hJCNFU16&7K;Cyi_0p@$(hNO60L{x3h0opeqS{ zhj32ac8V@t@^CK3%$UQ&i;(U|ymdQ)#nQANoQ=}&cyl_9U5F_^#u!hmS^;9JPf62W zz{UtjMSPf-o&qyMsl*WUGzlhJt!tMQaTT2b@-D!;xxn9`YbDv|ot;)yC^GYuZY0tu zDWo@3*cIM~PppC>$r9S*Q+M5moG^L-v{>VDtHAx_gEhhpr42_M{f7_aJG_!0^ELY) zMXlwcIr9#99%^-;?ILM~>S7ES_O>BQV?0yE)<7wrHXg4R8b^#G(mz-Uw6)rTw-F6G zvF)yLh@ENb32>&!$oN}hV7%?Q24lfwaGaQGID+4M3~U*AGRk6csOrI9Q6Dz%w0s=B zMFy9ocIx2SBtM@owZ4~UZa<#e^a8w6f(3&09FR-glyqu01T+p*P~^xO@N!ORaP)Y6 z%igh?GkgF0p^~rvNd~We*BAYYrRG=f2K2BD@&zSRZE5|Q_eyB!MXH$6wOHatTo)F} z<#SmC$LwWlB>G$vi)R%MI~7*GsG3!k-pL8)8XPxi3jUhncTG~xfT11F-1nmTmK9^kFHJG6>#V&z3RP2Tfz29QjTHPq`%T zz`b$8RTfmzs(ktAJztYRr(i~UiMsb_L5)bMft$?o_~(Lc+gYYha?50GHOX;yrPMAq zt)V(0Jj$cCL`mf%#j{6s&;ct!V!3S9@ahH5&aJ6FMXvBG*Pv}mlpnV8#nJ1hjwk!* zqj&it#9Hdy(t%~b5uRBAEm0{xG$;W-3XFJiM8JuF2h&t4=luX3i9 zHsc`Y=&fu!kA~F@GqSlg-M3b;hs{j29lE|YzACxfYwh#cVcmDfRcL=WJ*d9+NK4zS z8LNur>&aoO-zg^+t=)|?fv-NODi9xninG*48!*_tng(nPg<#wb)o@dAVzNWw&gJ@p z7A{j`yoNA1eh-;$o)m2B3*QFQSq+%K{M4&;6@)%RRpjW|a0H>vAv9qhaqwyDIgz`c#*jz5;!Wl%xTt)O`e_Z7tPYZf}vRSt*khQ zp)5zC4vnoOX=Z-Bzt%U0y%PM|?kr56RmJCSr?pQk7hQRf)uS#UhsK>%S+ojwBw8v0eqYV&I=*CIPdj}m z8~XB6)ymS@(jsYhA=Juh7WP%u^)J=|cG?2cJz?^4v?}D;QO3Mt2jQviGohPaO5FBU zQ&$5lNih@GWyx)enMUs&54%nJ2x7h9+1iIf!Qjg@hGL3G*0ML*`BTws)<&`GGKn~3 z^EnfiP|ww0f#9F}0CNc*aDyL4B3vIOmoXM$ePi%qSCG(lYvKGpPrQ-LNtLnG%i ze|$I51Nqd;hq$sVYpmIhG9t=%Y$b$g_U@VLE#C^5(vls>XV{IG<`|Wlcv{n8=XW%! z_ME6I7MDm&DedVc-{p*8F*d5EsBZbz$YaLVNEJDa9uJPu&o%mPhnzjpcpgD(^mXr? zZ*=UZ7tiZ%KZ*PHrTB2XN1f*owlxNvSA9AxHYnVI3k}Ahx(Iz02wY6l);5|hbA9~m z`NOU%C`NEfTeG6gu3V$Fjk5hViO%b}SK5~juWW5W{y&trtLMUoljo#KbS*O-V|*ZB z&QiJ5eQDIV;RYD1_^3GwYv`hvy|uP1k7|K86(>&CmY^o(N_zcsAD-l;iWj)pV*-Sz zDF5>E%=lXBpibR2CC?nU_*D%6Mnd7*ck>}y^=UR*Tc|A2kY`8rXU~IFvvC@$LUW3r z!B{*kv!cwv1oe-Br}^lvP0D|p2WL!UE)gUJXTs4VNqMeE+>i;3(i&E!<0T&>C{;w; z0Lv8r+!bNairV!>+XK(MT!-uE%@-)`rEFd&p;;PBwHg6fwl?K_z%ys^hP(X-d>l;{ zP`ZuWf`$Fq1e{RG9u^q|KKq%P&O=Yg=DsH*GFVG9M=4bLLfAo?Y|KZx|>Duu-ku@HE-1wEEj=n(J z5e#gOdBZ-*1s?XRh;m@15|_8-PHkSO>eKC=`VF0vHR2~<1Ih_q#l@e3g*-wj~@*y2dh=Als5^qEFZ zR)~T2Ninr7#REN-_2uweC?@IDMAISwrfa6P7J08ohq|lFm45AM5XxdCT#dQ~;YT0Q z1X);;K2esFg-6}kVr|VIiH>H=Pqu>R^;8j}3yjIFg2S6f=vgoFqXMO1Knr%k^`Yc- z!d3~N1kyO$RCeY>w1=>Ige8=OeB^)2lQn4i3>_&AcOrP-vL8l3_wEinMju!M7ilur zQNXh`y5v6C@}o=s;6h)6@APF7BjsD_aYe4be*eNs^6-nQsg60T-`q!Vw5!ZYdob2N z-}(4u9V3;PP)wPdYBCy_U~)ug-I1c$|7^e@Qu<0p##eCcXzPe|P5b#t1H?AI6vmxW z=VrhmDk;tp==B|P8loOOc0L2sL77Y_;Ur^)uE7)vQY%3k{r0j{&lFzJa5PMZ{FMp^ zTNY_y)a!uB;jTQx>pUapr9_CVfdD6Bem5Smr9NY?Ki)D4yeQ(z+Tzb)gG-mDQQvd} zkw#CTEl-WK*uvAdaG$H-@6w);+!An3laAs*kAQ!Fad|1cS)dmi>yXE05FosUSa4Dq zAu}O^g5nCiYJCR*42glNX+?=JZ(8YTeNnrr`FPH&4e}r0uo$__ejJgYFOmLAJ&VvV zX1(H^Mdd?6cNGiPGR7?%?*v47PFNC3OC*in&)kX2SGJYVPd>uE~WnZAqRm_m)+A9XPkSg+nZzk zC-WcWPSZ~450bofB(l*AMUW=R2l*~cMU;C;6??=CQ@Ry&=KciqV%n)k3(M`cYxyok z=~q<}3E*N!AMigF^k{v3J8{PR#GnLa7~s8cR3#jiR(QOqi*xOLm9ZwtvEEZ@l%~Y# zkK4&^??c`^Y6%z8ye6alc(^B4yf}H2j1I%X+(Fr>t-wx^v$uqU({SR3Vn~j8?c+VX zuc~a^?wFQ=yz*V|1NUjGEW(+jrbWb^dfCW=Fo}hFMHBwMtp4a~h65GdP!wrPf>hA} zij#w5S45c(o4ljf-d{HJP@UNlUz$#iHOGowswv9*70=wvb1rJONvqFt7+1)7J}8~n zi=?_Sg}VFjqdwhZ=|zRhb}41W1kYgg@qKv3L({KmCMD6nQXu;!$+mpB5i&-EoIO$e zv~10J!su&SXF{BR zE=AHv3gOFpAAh!qP0xAXprkHhdQ5$L{XhdMBWAonvn3|AvIFTf4*XjVs&j`-*42DY zA|S|WP>R;n;ktIf4YD>|X)fR#wQtSk@ z!I>LWFk0*#+5@ERHB#pKu$k2v{;Ut+xbJ%mvhnGv#WXg4J%b6eSknLrgcb^UlFf6) zT>bUT35y{MQe$k8nC&vRiyB2 zg8*f!sPetW3ZvySf5r%2L zcK^G>kJRmN9*TE?MlXh(fA!UZ27QWqpx!Wa$9nS{SH+kk@&c6$cOyrn_Xgu4W5Hp{ ze$@iO18seY)8Nxq zK|5zcr7K}U{6~r**SzJ1E{$9uN@+KiHemSH;*zE!CbTB@nQP-j zDRf?g>5EIU=bf#pmQZhO56U{hxzxjpz1VjocAfpcM81%_RwykI=D*Hk_R0Bkdda0N z1m%VDijOQDA(t!^F!|ivs(e+TN}Oy)658aWg}jFb7$#}QmZ*};);MNzuAEt16PIOo zeKz9C4-#Eu3GQsNryAz^Ps=e?U{Xguh?I_z;McVLxlq0XAjMBYfjXkxVz{i>>Z9#V z?MGkM)*sd;44!ao#()q|3a(r1H68z>z{lUFAURKJ7NMrCIt{NaSC5hkEY}ECXV*X? z;>`GDbFPQqr1zX-zw4m(NH}{U>t=WW7EX?~fM7duwHi_iU6%v%dUpK!A5{cAb9B#T z`lzQk=$`S}(Rj6}J_?R2I$^T%TxogxUO0PsDb#56E%N+NYb-#Lc8PV{{Zv>^cvo4Z*^w7T2ni)wV zf4)J+8m0N_)lZ23@P5F8T9%2$Wqh7oA&Pr{LF<*1EkC{x%g^_ql)9tjFNIDXxkx14 zP}6F8A?$|Q7<1YY1^J0Xw=*!FT`q_&QmnY&LPg?m zP6&r+TTP(-vvO>D5)?0GvPL}$vamgL)_xSBO8)xZEYDj0h3XXBG@Vnf4Q7??hwCS+ z^{N*A3*9iJ1^k$}d)E3q&iW2VpyhN0M7X}8VW#fo;|N0GjNoz4a3C-6p;V<4;2Ro@ zjm4jzC*XdkcSi~!;9sM1&qfC8zGX@T_+!VndotUxP#5vlbHGh|bX4oan3TIEc@=1O zd19{+_^TJBFrU3lJ5~0_SurgY*+ZV>3#eP%5+*{b%#ZkSv)))0X8!p0Q^tr3FHLK# zv6bv~&WcmVc17vJ0Uq^5t+fLJwU+(RBf;-oq{0PbZaHJs?&<4n`yueJuh&hp4;7V@ zUB2lw8G5Z=SzmdSPci#336?6%xV&CU{r%?~qPds;-yLlqBl@P@jsU(6FCKRK(k z*kz3sxA_R<2QCkAk(_lMt~%CtmOFYsGVs;4!+|dx>8u^(diX$r#kHscWXeZ`7Q4&i z3~Hv-+|Op*B32Rv1BW-i@z5@^%A#=y;xMDv==)Xf@t!^Bsw0(qs@y&!)_xf7-v*%6 ze6{1#FIBKTqJ}P&=W5V$r?TVob!;)myrv7EP>MAoR0B3KUacy+k7`eMgQYo++IK^` zy!L?s6lsRus>Sf*t&4LyR=k>&&8bG0`j>_RUCZHhFX~@Az0f7>8n_5mUw9&=Mh)vU z%NPR;vGEt(9c<}o43uXoU&u}K!qI0x?TV%+1~a&%dgm%K_}b0+=ui*3Z#89WsO}}W zL|Htb5-Ku?OhC zQ=T?QJ2v>HumHAjdi}`VozWY`H<0DIj6pfnP?FK^UOnmBa0S!>QACMp^JRLKEExo0 z;N*TpG{u-WdHG^Ul~wkuz(JPYWQy?a#CXChkBISmX2}}F5+2mXX*5Kg=_gh6@z%y_ zNWU-=@aykq6L(z&wl_s?6Eki83hmBGf3$eKuS?OcgG%%)e^MJ3d9voFlnH-mFzcb% z4g>=)T+w>?`j%=^O-_Fl2>V2_F83Sfd}}UVj;?bbc81?%xFVk0-+=HS`lDlnq3$p1 zrM1;>A0bQ5_w=>2v4d&pWs#Ow(bRF9e0+C8S2(k9obq#uYc*ysu1&sFnSB9S8gexL zN>mK-GYAN>v`Gai^)P^{DU{xM^F5GHy*89 zWrwfS@>f29LJc)U50<}YU;&{ydu)u_r57-qJ`1QlvZo$E=8~@!rJ9tm9OkWHfbDVU zt?N2$Zgz6#v{u6}W38U*));s!h8vf ze*&7Y@9g-AN)gSNYDwz&MaH#9Mkxj0h*$Kt=89AZEfy%ShO-ck-KU&tebXpb`FIY) z<$1y#gG>Fwkvup&>DTO{38_l9-^31GqNXp;>hnn0ODZkQSY05|vmuZ(WT+p|3w7EC z5EKfzm+1hK@v$t)lVYHl0RDUpG+%f!;>@%ZUv+U(U5(&Lb%<3|Qtba?vGIlhKe5lE`T%0DTJFzHk_nX9QCRcFeAt=DQex$opyA=QJr$ppp2CThHO)cl0_eu$KmtBI2v zRGPSTTm<%dp17ra=D;ucLB&M7iw6##C%WgwtXIJbyyP5rxY z(ogDOx+Tv*_iZ}Ft@1vjRG3Q98ZesG_ULkX*)|)CV4wXd&-%6B zH7U}fV45#2i!dqpjq}FW@n=7d9Z$|}xQ%6V?|?8-Q3zz{vmWtSNHL*Bjd)#7^>T&4 z8zRaNb|MuPA9ev`q$1r~thPjWzKYmpvyd(5aVf^Y!imT_mmz?}k=L)+`Yv)}jr?b4 z-WiUadE`v<5q(Y^6+vZ%V1avTJkMSRB8vmOG96|*_Xt?l`~`2-{CKpM+H=@98Kp`p>dv8Oh>`dqC6 z#cYIFEv;D}S7xZ6sV9F--pKY2zfrd1!(720(AxjT*?Fe*AQhD{(&5~Ssf>nB#oaFhbi>}Q@XnKOj2`sRK2mN&{>><^Lr_L?nV2q$kLC- zN1nX+iMFqZo=>QJB_F7!3bWJ`duv+;nkl?4r_xjWmt}up0y?xuGTJK!96QTb^; zFx2|c#@FyWwwL3{f;&-EN0w;l4I2@PDxe@Rl}msVCER$|dg>U?q^iXk%|&k{MIAV~ zh^sTsvHEJs_~qF|9E(ZjT~M9F}vVw6yDKQY3D;VC6T>YbvyygI2P9N}>DlIuBpkgHm%V z$^hrP8(;vbh`Z$;Zg=kxD}-E|<0v0j8_pO1qbmVt9KWU)L_5+KuyBR&Ncu~&wQ(W=eV$l`6A zt53V@jvffPk8zqWitKQiwW~M*O++NAUp0o?!rLR!u} ztXR8HYO2P`?UYd{)U5r4P+BWCA=@m~h#DO=W}SU&Tqipst!!C3 zv!8;1BRCVc?j#iB3XD7VtJLS$jVyYjaUpG;sZ&JmJfILtgJTj=Dvf5|y{D7iU&J7k zs$MBv{)5fDAVqMiHGYtN)@$;}aLfo60=mN7rIf5urI?9 zAHNUKb;@{9r{^&c2zl?1yR|x9b&<7EtP*$u8IPIjslo`!(D@~|>^W9!N>Z^406J?5SyWVQ~?Ak#6zfq_Zr+n_qe?)}Ws;kyfv+dyZ=PN#Kg3 z-+a(vYK$cP(}XT#$kezpd|3#ev?Bx4dCGe!Gb9)oOh_s}@_R_e0=P&MNwd zIn6b!)MR1Sbkf>Pee+(ZKLr8fd<*e1KO`rw@x7uEe5=*-0}yPf%EP0Nm=AJID5Qn6 zaZ2y$*_M&QZ`pSPeFB-?Q|uqh-xpHNbE&r>KlT@b^~{G(N#D zk_mC{s8F75wJ)dxQ*{;nEp+=uE{c-Z-F&=>Q7WAu1 zibufYTJ7N&c)`b_`%^C3@N<^@{e;=fCAWf06 z_&jE&xdv4nbT1DlTo`d-=`n_-P*^reT|n0#B|`+Ma6TiF_KTg24q0OTz!i?@uyEH( z)|MQVroDbhR&EZ8$ZUrWU2$-22@sN)0BT)05_M>8@a6JFe?jn4u8e0!L|D%Q{1eGJ z>4s*`PS(GZX{5;>k428L_fuv3y0otj#Uz9xa=%DL46-@`(ELG$fP^@jH1=cJA z6${li{ZbFbr2Kwf^yWw<0n$pMFgr;7CysBKIzg^mDJ=+4 zXL9HPK4j`#>CgPwwBpstZf4%D6BPEWnSGdw)xr0$UA3JnGHr=eItqsOO-|LY<%<2` z;bC&JX}mHwp?Gc_YwRmp^DhhuVdCB@Nqj6=x>vbc!D0n}1gKh5(dabPuQfP1yh0^e za|BIlv8y*|eIM!Y0-O==_x8E8DbEp1xWb@ze7lclQb>1iM(?jywj_F2YuK$@vzFxhJsaW-T_1toz)7sW-~DOx@mM$h zd#N7o{rho*`;zbrJ(kQ2<)da?I(->Q}2EckPtd-U|f}cT0ad3vz+bgFX zxe{07Q?i`tmL1tI*E*;|P4YJ7i1k8&{c4lsAdyJKafhhtllqhWWug3@*fM-p1s;4M zTm4=>qjR4tEiS1Z8ag*ZEopH$5?;#2tP&d(C79dbljj+Je0=Efh(#uIyxA%@l_c;Y z2%*~(<#ONWYE>`JMq>^$uF)yjI^}g|?_0`9f5eqA;v_&DzT4we)$O`}0>QM8ff_Rs zog?hEECu13#WNywt{*bl%?|SOI&ll{%*pqS;7sX`Z7fCoIf0i%n7xliCocgxsOS5u zbojBxk3}kS3!c%ke$O0=D5w)Gvysc{QnUz!%W6KpW6v=kcFb5IN8^-Tv2ReK!#Qa2zkYZVA}#`^&JT5&e5QaUu33u+H9 zAr$hsQ?2P1uS+yJ-5(^~qH}KST6(1~BfQvqcqgyBNUXqUj=%B}WTI};{FESDAp3`x zewOfJvCll=5o))nuq*N_`G+%oHr9Dwd$ZA%)NWcy>D;|JMQqx|>TN*ErZbciTASmA z=ubnB<5!4A?=T%3_*k@21tNDO7D`uiEHi^<+|rJ(cwF=~gTL1-uLUckRY3lv$cPem zOWe7mqydZKNz-LXvG~PYTa%+tfXo^p^8JmwM1~B_mJ+T72J`D?J4To5w?5{`ToR_| zWIIv$qPOK!Nl#($FG-*V%pL)LK}&2?3aY!X;9Sc ze``f%F^+?ykUng+HdMlhTwL9(qKs-AZ}$eajI>C|6=g`CWL0my3~ zG&%b9ev}+MAx4ZDg!?|E)#zJS1?RNQ?Y)#Z4Gg|!SFW>T4M~u+*lxQ6y%&*OLv(Q}QGGOUFy@rX_pOd9Q@wAk3goP4F zu%-cj77s8tL$lBm-pyukK8ct{TpEVttDwhbctcx&p_dQYHP z$37#Ko5-tNCy9|f{5Kk_`x(>`Cskp7k{dwD_~LDzrPct%BGu z?Gh^x9vih}s0Ty~hs3p8h@BqH7S&=77P=I7UHFnPkOVZ*@k7XIw!#{Xy{xb>CA~gi z*_-)8Y&S(MA3e3%KD-{3LD~zby+llhftanmt3MUnxm-y>JsTr! zQkV9*hLj%`rs387+;+M#lviXW(aT-)kJ*cyy+{f;W#fnV9PURGIU92n^RuQ4)YOf4 z^~#({2}mbH46QC&L|R{LN&@(X`J38SbrGrFH(2fSVlUn!1A=qd!=G*2K8|7vouYG- z)}0wl;^K2;a8M`uj`_L7V%+NP8Tz7@@f!s93}+n2SX9o=@pg9zXnx%A2D0%Y+H}~~ zi~{H5Hc%&A9hyosEO?fiYrff`%t?1aV>3n~Cf)GZ&39Poot9LLMEhq7gS%tdP)Yw+ zDQW~>8Qh;RpZ#h&Baw`# zH^cw{e-Cdh8%ds}ImcFR^#mF`XeD5(1ak#zRXntbI1gCc?tp;W%Yr=0G2NixRibR1 zzR`C2bFSm_8d%0-SjtB$HI-S0HXbV>rB^&xFFr|Bk>ZLY<~x{8QEqZ2$8?E1z(Zof z_j8(0h{9j=^95Ycn5kO@?OH{y^Q#XoMJ1md-5)!f?ku@vb-~RLd?8XBSgcD=9nx6x z`}HN-km#>%W%m<-W^I;lt1UKKZQ@11jc2x&L+Rgd_ANeJ{8FD&5a9nHZ5XS~gVn{@ zv*pVDX0#TN8?a$*@#$XJI`nzBw+{APN&HIbL*i88aFG12q1SwUtjNDj@Izq~!0ajq z6iDi&8Y+#ap-39x|C6I<1 zO{IWgyEp1tZEK_+1Z`;VF2Wzy?Yu0;J#mK(4n60rW=@Mlus_gp4!EMoFrorshj=QZ zw)v5$yiVD?!wYR*Wu!a%648aTM>oC5WMguU z-iZ!sZ?(hA`WF%-bRZ%e>Bht$-JIi@6caa|h8BxDSGzcz>Pe66%k5hd(f2caGv3{* zdv=Ut`^r-Bwr{rn$>aO&j@6)wCyvb1De1&6>;z+5VASX6eJo}TIb475x&PpSU;JfC zx*@lY<8=5cAwn!aPJWVoirq`s<@6@0t=c-?Bs*7s>A;K3)nSoul72NE#TR<8!LfsC zh0P7?6~CyqQJdOgaw#JweFh`$bCPMdyvfqLO$%HF2~3!iUy5v9#wMG%MZ!Dgg`^H~ z(fv++$@X31bfb#%p(e;qRXASuEUb+mREYO3_v>I>e2IWPq|fG~$)bF{)v@=o{kXy7 z6u>^8M{1PxxTnfFkUtCm{OscS30lMNB1^l$&e7Dgy)J&Wz;Aibq`-u(@#)LOWx~a} z!F2>hcfB%Dd?tu?9yRRj&wBa!&aQ1V?69QfoUPXLdX`Gs_C?;z4E-c?{;iOn*!W7D zvovQy_{2BUeu9A?E4;2u2j8k@`35Ty^Z-SUGA*yvTbmYrPf>HZ*lO}FS6j9>ld7Yj z$OsFDA1lMnzDqJ-5~~2e#+$?yemV&3?1zxuAdMKay0?&Z5k4<`S!_d!y?fd>unReq z298uQZbGB%J+gt$o!eyi^LN97I|K@W8h!IL_uVd`P%W4*`BYAq>UugWh-iMFV{oUc z+ih0@y+CSzj70V zk75KC+Cmet7JITEDLW2VH^g&!^VjGDoHR~M@4LL5kB=m>%}N96PSUZo@1pIU>rmyk z5-v2T-u1~cKSgtiGN2qcj&M+%I!snkv~?gYbHF08i_Rx!Ky0}dD(U&p0UjuOn%8i= zw}rn|-Olk+R%9@km{Q$j@E{dgk}vER(U^$A&Unv6+2qG(_7}UebMmM=uV$Tau(~cNxus++7Uq~|@lRa- zRDK-3twblga((BSqH1CsYZCN)_F&g>k~J_D7~t{B(?qpa>IC0XVSx(mZhtm8L5gLr z^@v`oDHhD=#M<7vY2rtopP&$4!1`mmJ?B**^`q@9gS+){U4P)X%>djNjO1LMJ>dyw zW91()R!t01FlVjbu2t-h^2dVF7gSjdygZQY^;62-RM0ib#(IC7W;Yvp?WHi$$%7!Q zToG@}u}v5vg>c(`f@x_gBk+=fotx)^p*5sSg4lUSd|iWKhNWO=1x{X+eTg;^8SGwv zG``*#qbfm0%9DomgGF6daRV0q^evf#kWbgSG>RLi<|QVqdrVC6U`NU6$%WJ1){O%{ z>U=9fJFng97f6)d@%rxGyx8I1lfC-qXU0h&gJwM!S$Hx^F$tejZle}Jg^MQ8srqf8 zwn@;H5m73pp{Qf$s_xflYO-kW2PBYN@pKVF*W-urhaB2ia)KD<;QnEtKG|ETx(z&Z}SuqT}AG{j%CTYML`qLC;Tu@_RE5(+&I;K{+RU^ zRe{^g=^oHxg`1~9TserIEaKgISWuLI{LRuX_B-42iolE%;>vc{#dc|{!CUzFRP2O5 z?v3rS)Uz*`3B#-B6Qa{w>p?ELfxR)ByDd@BoxXX!JGbu9m`8Q%eMIBD2$n{bLXGo*nUe!gdqoh`k(`KE3>ZE1; z1fYV*MT?XsDe6|><1g%{#J4o(2?x!#uEkn8uwdW@r7&_D&wyE4v}ys^^nWj?%e5dZmseWZcD?TY#nTN`cYybl87e6Gw(IXuHa9!li(F1z@h ze&KsQ-5fVV!rD)FHMP6E*^eN~)D;f{yCSDQM%Sh6u+`LRK}9V1P7Gs<6g|##d-j04 zKg^Sr^Nm(vtmUmD6b!f7{0tlw2;vuDED{PcNyA`cxVZ$pmOrF#3-8y>6J8XA!bEL{ z6p7XvKG!VM6f8zCyk58p}Pgy`Cm zBQ~G!TRxh7r7(qfRp-~Ay&zHTmR)PT<`isk-_CoMf(nKd?fgLj9pU&_+#wzFS&kn* zNxIL=p4bybtNd<1hOnPwZHwWw2J%M8*5E%x9S_+z;|D~ zAiz>#IeFeBdP6H^e~Iop;Zu3zACRt%bA?8mvLWy}d$GLDoN8T4m+RI%yH%X?YXMLp zCY6#Aed_2!QR-z(JO&MmL`W#$bDMJ-lp&2p8U*sjZ|T}?^3F@Mba%foDRsI&lo1`E z&JrmCaTie-`y62%O8$0qwq`|aw)YV3uM9wa><3}{Hw9a+eVs4`IsZ$kym3d=#9Z~g zhD19rzKGqIb1(G8S1X2%w9U$_F-? zjMGsJ0X_#RiwWwHJ}zWnP&pBn!X-S|Bm2S|gRZS_(~ut{Rc-&K-h~Ap(2kxpi@SSo zxio8Fmpj!PZF`$JZYlNz#Ig~C>>_CNh{P53`=Kf-mTu}SPhYq=QN3P!T7!9ci!t1_ zV5LC%zHF^<>PiPdIb1EwwazcQH*9@n*F)^hBHdD~XB{%W(lADl_8@hg7~^DPoLbz| zdF)DCDxB!hnsD@v;WbPSC!@^} z$>VF6qDldVGT7*N=BjyMi4F;<2XP&$i0W`o>cFzT$n>D&Htc3=yrp=kn>Qc(Gi)8| zx!f@Z#as5uG7h}<;M@ANE3?ZKgDTG(8fp^y#+s5FF_Za2CMeMb^WUkbhnVx!XDuv$ z%r_gDE@|yVz!!#olxVDDQr-V}|DrhS<9IjA)UlI|b@c8%?xW6#PJ!D?ud8u5i@bR)#?s#iIIIV%@d${E@HFy5}G zLGn8(iq~ZMG0k`0%Ar#Te)djFlYGvm!>pb0BGTBz^QAaIripZFI<|Uip67u;l|-Tq z2+B>+IO-7o0;rZ=0wac+xxwf=Dt;hq{*<=5toFJxq~DRvz_#q`Ofd~eKJ5n$+=6T? z#>@MkHt`7vL34RQjnMoe)DV4kMQ0vN%7T{=1k*bSX#1r*8=bq+vgXhbv$o&KuA(8= zGV&*c6}ExP$@tHp@vy!m6W?mYbDBVbIP(d0&rAu1rZrC88hZK~MR0Kf@LAntsK|s2MO-Oe)?_8XF zpWl1q+;hW!e8i1wub6YqF~=C+@8{m8vtKB4`!m@DW;~`y#01M2o+mx zUR`9?gLP4*#72?Tym8Xr7w?jR5z)Ngm?SBKY1GJpmu7N;Db{q2J%Yk|@S%O`h((Ap%(-^$Y8bn5BAq%t5;=*`@ASrS#k z#E&ayS64?|^ICy@S3!ES(9O`bU zb7*i(W8&qS4Xus-_Ly7-C3+oi-N8eGpofgVDLkGz=oJaskF+IhxT|&Fy1JAnM%!0#ET8Kr0>3L6y*PJEZ zIo0ft0O#h%hi3-5i=X_2^VzZdN+YudZzipQHaIhVO7ZexkUEkWfe^+;MloGi7hOXY zD(|i-=V#uXn4wjih^|>W-JfLK#)jv=`y%{GO`?sxcP&A{$)B-#HRRB6BlSz>PewG>X`s)aau!z^ywO~>xW^#U={6<-jLG^1H>?aXp+Jv)IN zzJzZ36riW#jQd=|#^0tl)xaD?l8)W7x()57GHB7|F&Jbr?}(Fl9-%l>XI>M)&TcvD zC~Hv*J7^K4)Xg^!I!-+SJ`*eA8zosJpA!$0Kj#UC@Ojs9ZRLf3ak}ASuxA{PgP5?& zvPCD1sVHB4x#L9QFeFf0y!W1R^zpILz-TJS)_C8{C@tm2hp%OA>p48z@ofLA7l7@@ zj(ZuT@Cs&&d$_ceCE@S$o zplQcZQGq_ez*%wSG`WTE(rDjCaLs2(nP2$*-p6%NJGSuY;3^vd1o4^T#qY_FWn}BQ zlI2{l!xwgfEyy`KAA`Qyk`~_PAw#HRQ> zTFbKGl3U-t2fT+{+@dokecx!l`p$3tvGJ6jP`!S?cD?!7X)QfhQU-MQO#9uS*!oHP zaV}wT^5S$$@n`9(1%nzP7Xr`j642Vx+Oj!$G*~`edD4JE91$4_df2a{S-fv1Owg71 znF1#^2K1}@b_DwmU(Wp$|Gb+uDj$45zds3zztEW+yJZ|A^(#BA#NFeyqE*jZeY#tk zJEdG0iMTbQ2|d92nbCkokzo|*j&j=;EKtl0H>`Za>xbbhK!;14j>v&88aDJ%5c6*_xt-a1a&5VxfcW@#Y5n1ctFq=7W zbhxccD?1#4)lrv4%22K6&Z72C3$Yi6rW^G5w_peA{Mb_#ooB}bASPfh+hyj9^s_~&P1D(Z2CC-+# z>mRKzq%2crhQ=$U@QSHBtF^OtX}K~WVi;HHaSF0(ug zsi`Cpot4SUB&#fy@>0NGJmeXP$%pSjSBk{>`keD(%d*%XfjoR7=sSn9D&ej6Y z7xwaG^B&(MXTyob7cv~bkSctsGA={9_1zG{;QzjfyPA98e`(%psrH-)LIg_wA%lQ6mI5tTary4qEpa_)$f^87c%$Qs~u@aVftWUCgknu?Y*p*ZIv1J%FF)1OxRj929E{r(QgW+c02TQ zE%8A0SSt?t2h7wn4(Senq!7#7Y$iK3*@!`99xGl+kgRrcbL3K|)~a-06Xi?1of@b+ zsu`QY;?hJP3u6e|Np_`^r8)lf$509#%BlgQs(ZFsEz9@LwC*+rFH2*d7^ExSdqw{A zRyS&3uC_9?FBh9oenBy=MMzRdWXN|*!(hK%>!a;;&6>6SE+_Zgi{sRCA_{w65IZ_3 z&r?NIHTsE0e$5HYevR?6la+Rn^iIni(av;r1G8L?IsXHYZ;6s>eK z5>fR+Bc!&!IGgcLd~)kxw@*cRA7$=vO>RT}# z=`PS2GW0o0tq-M|JoeFXju+bB6HyunKO=j%m|mm>Hte#8LtfV?`zc{dco09|uG;U6n_&JOzAUWV&mb2Z-641G&WO48`d3 zqmJ2v6f46rc^-i}RK#TP^j3xZaF|{ha@{X?U7*8+wjK%98QeenEakq#Vt_Yo=re7k zS&`W}aRi&ct(#ElKYytVS0P2~tVF#IWB9G{1Xw0Kxpl~1Z#>GzpMk!c2q$}q7hy)b z-v>YaUhoXT4a-(62~*nNfTIiY?xup}5Y#;3BfV?L21zpd!Yz)3inP9Gx15ZgMB(C` z{Pg|e7VUD09zUj=3H98CV4iUc9`QwaKEHV}&DN1pTG>aLY>0yaY`%Hrce2Qb8)LDG z(Jkuxo1OA-9iMf_gjW&KgYZTgHRlxFHJqPJ%4>#q$M64s^N(q}*5l+*(; z)591EHA!mI1YfKnd-;{DOq!q#AI_rGte`+$#(D++zRT%gVU)sR@3X-<)GkZ+!7O_z zN#U0N;`p8Hi{8E1!Ydz>w*uB~Ecia%^6Mg;&2O`ECVsx;o;ze{8gk@ekWdE6;;4OL z*Y50Q+%N8mD;67gzTN35vQd6g1QO#~)YWp#24Y3RHOHp<71nZ(sY~ykK2(|Le$~F_ z+!sMj6Q!ORfn{Ly7F!E%govlq94Ai6Bfc;A>hWDDpioANU2$}AVeWV#!R45s*9EMh z!0O;q$W>lW^;MN?OpVt_h&6k@QYiMeo+y}_1{zOyK{>DavwJov->vo3aV`&0ld#{g z?ela+FdcFqM$%b(U&|_vVi@rJ-FW~kXA=||52rz`H=2-TAtxC2`1**Viav-TAF8MA z5Ew0&CjCZ4OV)E!x?`>?-FOdjt@4>K!(N+^YT^04u)2?F{Zu}N`sJwWd)f6ZA(AQC zt9&oN@A!Y?Xn;SZ(B^o@Jv&;tR<+#nJ&>m8iBUGBS7mpR=@Nd~Rh^S)S^0Y+`UtlN z=cx~+JUObdmNfI)_n245Z#1uA9`VVS%@I@isK-+lkxS&xkn zyD#TBk2@6GzlrXNort&zM@f(eJqlZ^D#PU!mn!Sy2gYkbB1JDTe+$Ko}&iJt5)xp5smHpX3%0vo2LtOO%k zg%Ar@aafbq^su1#7)a5qs-cL*}rQVZ^eK!Rr_3nPg{m8hSr!SJ94oPPwH$52| z`wYxLKChlz6CVF~6?w@Z%)}%pu->n!WMg5rtS6Sxwt6l|6{ziIF4GklzA*l72^xd; zy!hsxhxPKi8dGpw?HP$$kTI~qG z9gtr>c(dvDi)3wY{+T&9(AN+twK~}Jn}yaQg17!s%y&BNO1fpe^0C9em?37kqhEih z*(hA=q$Q?))k~`2_|U@T$2*f$gxO!zHoYr_E`^g{veUC^Z_b(SSLCy zR|Z%GA%bDF%TDyd7ahAVC^J$Le4^Lh0wD%FNkaz*F?_X`u#;EDyEDCV9_hDq4w&0*=@Frrr$L6NO~URcsv^ zhx!=RbDv3Bj2=7fIdE554Vz4S3zyOBTRxtwx!!gDvhsR#gl?Z)Dj1)asIS1_Xyj;0 z#~7nRT(vvBMJkp#!m*dHgjLVlYceR@7=_a8!41{{pZ5cOs=WJMUK-d0*DGf1gVZ!# zd93s8r8C;^LC2o{P3ls+_fTTJxs<8b=hj=R-(*V$9v`x&d0uf>k_07*mW<0mjdV`w z)uW8Sb_S>|-)`flQq+L=NRdkraBUJu`|v1#-zKHjS@{tK>eW{haRcqj2c*_Y^R*>6 z7~b0~_2+(nf-qU)<8*s6k}m(Ewoex*QQ^V2i4KrNbLzb{11jFn{Bna>936qOL|sKP&WC@CyPJJ99vlW%t-js;Fo2JhExb| zcep)GOs@JVC3C`2V(8^&UA-0>BTPgTVr<-ddirgtOTWHUur@j*d$kN#Ro`yp)Plk< zR+8$cx6$Kp6W<$eF|GT^v|bl=X_a1n0X6#)ph!@9djnn+3!>;e87%SRA9YF%Ooeu7^ABV+@CAPeqTNY$ul^BK%FCSeNp&jsMijY9dB>& zzRz?TXht-{ef#_gK#^@z(iir+J#S~MLWLz|=) z@`Ie+ri~HxZxr%=_5*uNsFMdg;$zdMvne0uCa)jvm+~47E}oJKcGEpzEHa`No_~~@ z{d3ZUb^5kU?Jb|+I11@qLhu2(TMtEsWRwX3 zLpBY}bx(%mD`oCY*2GW+4|2j_T&6iwNanwE4Rx56_6oy8*FcMgIWf#86pj8oU^Cp< z){{uQdBuj|`8HZ(J=Eh99Xe?vssVcVHBFXwNEm#xxK%v4!=+b)Zy}b4pGOM5x2tvc zTGLG~ZkUX>6++pVKB6S{?faBJl}4rCB>Umgqfz6i)?8v$Vyo!mS#xtUX+I8 zCeM~h8XIb3i=zt03hG3JxNFT%*?j>4@N8O8azr>KqUeu-w?AX^q?A=kEGW7iYol>)Eb{^DWewG^_wJY5BXe zx%e@s5KZ=7`FSljR^gyW`_BZC{f%tLM$2!e{gS7~Yli-u)iOULuQ9Lqca-p*m8!$c zas5HULrw>@2ehB6v7n_3+Iyqc71+BEY?YaxxKTdBqY^eDaU^>a^(sRLYiil)Ydi&; z3PFBrYR}B=Qr?U%7MPZYT5Z4&d0G2OjdKkX6MTX+QsfwxBgsa?84=;D{C8IE(b7j8 zzKOfmvb^{Go{S@Fl`AeR{c_ijH${vkZRH1-d+)9?n4RGrFDc zdJC0CR|Ws)PhKPAJDi$AOvc;J!C~^FZC+KQIIUw5J$eJ=Y5O)1HNBW+MEvA#;P#j* zy;vrU9LFHR1XbcFkDa#T>OD|v1q)9A^7wS_&1*q$uO3-Z^d1l!9^5kFi@@o0s17;L zR`YT0lZ7!is+8Qp0ood_LGhV9@Eqf;#uZXZURKQfD zQzThWh*vD6oxTx8d9-;Z0xY*kL5HX|y10mSI|Cc0Hx~j?AX_PA>5Be;gW|vV+s-pI z;75x=Ja~(V58W01bYFn00&L=Ja12|8FP5>GhbP z0NnViOr9UJF3ooOXZg|F9xt4M06AjP!*sO7M(=Nb|8KsqfrKh!MuQWJGXQrdcV*_@ zpFSHfdNidRXm6B%FDqmpi`%=lYNj6l(~3eP!wC~laCApVc#X2k;%~m=Z>MjM73=}g zgX}@TDF(LH8E3GN?74~_U==@cT{+- zp3!D*PFbIl(DuSh?*ALoe>3Vyn1)L$D{n|&y3pb*7$ciQ3#liL z7JhCVou@@dpZ$2%VkD7?K4bAOkMbKPPQ1k}l+bm)-D!+cmW2}H6_#TYCdbY1d!?{o zt&Et|GOYQuO6@cYc1A3Ze7-YQYlmF$ka2?uZu?I>-f3jB7gHo@6s1! zZP=Y^>C#eIm|Ir2iAYpnMpHcM!r`T@R1U0=Hj*c9W29{zb3pkDp8`?6iQRO~>#Qh%~ z^Y4F|zy>)tHwOeU5fx*=@1KvyR(bPjGaUWXDwHqMgK>|n*Ttm14jnYAF8}-g`!8NJ z|NN|OKFAQGuDFM?3z*_NvT`yhmTfq76EY}-o$~TYNwAkVl3BlMH1p3`quc>L;WiH)l3;CKW z2Ii{8=jgrmOcC!)wOdLz9%KiQjl2zBIah^*ErMYZ*Ediy<7=<~w@D6~8uDSyvenm^ z60J17d5n0{S+cs{4K;5cLJdD0>^+nUa30Zt{XgSM{K@q|Pc|bMfw!`{kF|0RUIm!87Z_8g_Q~mnCS8qj#Dkch`($Bwj zE`M@6kPNsbB|>4JcLoO;xW5qpB7j&1!Vh!$X~VZ~AxAs|Sr@sg|M)%pkJAGY4VZ*_ zHUEfOEPuWgs9r#5zgD)5s!ipoNF7)xwX+Zzx8bPZNNP_Zx-7_lY zGn@7c9@oXVMOfR}T_q7~$G82j`F4Nzppl`Z){h<$9ReUy=m@jNt-t&Dzy48uOe=5})~v`}&@Sc6lkoVhpR@6=n5^Ka3)`?mU+GzWJ-s8zCusjTNB;V<_>Z2S zP%I*rP>i*)!4hu&R)D`SY;&$)itb?d;sfgmcfRKd^H0pbNnd(Sd}bliXRjQ`m$3%zQ95G>Z! z{quWPdh#e~k70(7zu>sgcSUj|1Wi#GTR8#n$r@?z%AyMHFUye)f zx#$1EVf~+7B1D>JCWLJ)M3>Qq9|0=rq`>P`XXm4D&+SV_HrN4Uh z>SkSTZmy|&8~rbP=p=WrQq1A;a=&V-(*l`7e=c)+$4^?D^l_?Rw16DdAf(fY598zG zrHPq;$rFOY2i6#K3?fB7Aep9HRCW4Q;j=Y&2^r4#+b6#$$iWkeLA)lBtBbjXq5q3s zof;WM@U6<2$9YoD4!`J-3p21v1D@HNTQcFl?7HI-b)}`Hzcy}G>A)FpC6)C3MdOlI zHPF1_P=4&%zQV7WK*%~C&>Qvw4x_^J`_VY_tGL^*;n}gIVhJ6>{c4qhziJgCk#pfm zCpKNW$Ng^%B3cy_%?_WbtakNKV(9>u63UgKZ*@|K23m<$@gYrQr`otKBHamoqd<50 zCm}qNfwxD}?UAxGi6!RR=K5RgKMrO8@}{U&3S>gDOJ6nxykL))sD*uLA7Lz|LXn|( zEDY_BTYnnmE_i_rF^|yvw(4szAm>+WeIWbrzklcd^)ym>0G9P(J<<=`xOZ;*uz@?l z<>3JD{TiU}|G|u4Gl3b_^DjR%Ejr^2(cL<&GW#U;h%UWc4YyFm|9Z`Kz4pVoSy>nR z-pswp)IZaz~4?e)iq>H6`8uqOkavJ1nITxOx3s3;KSFXx{Vv7b-Xqd4`6x%1kCXIr(P2 z|0(QT?+;Jc(jd^b9INVq`wLJe_Ua*Qv#8K zyS?*-Bhqwm5*(i2BHl0U)w5zlJxuS8W13D@cruRLN-47h_2y^1LA!A;&Umc%+Bj!j zFZ-Xq-Tw9D1CPHA3;olFbyc1xb(aWKP;0fk6rUSC)Ro~+(HrX(^3sT#u!T3RMtW0- zO;;V}V)eDe9!6#--cJfNfjv3jQeq~kL2Wzk#(y(ylk?+f+Un1J%{Ns^VwrSxwrq@T zHr_Bh&v=X+jyCD&;!9t+dQ;C1K5-X;i~nO(?0?Jeu6{5-w>ea4)K~xViyv=&rqRjpUfC=;&cE$pogG| zJ>bd4u+$>3ze1{dFv*%A`p$s^_Ji*;##@B}r<|R@?8Fop?yi2E=|p63uL3v7@MB3J z?{Yu*BqX?3`=M+El*OgQkNg(ipE}oqA&T(8^WFKbfOLV+??nv?0**smgSTb6-(E#p z)Jni>ji-LnCv4q7C?>UZdF=TN_wK7STZHissIPcfd%v{MFTW{0OOr=xmw!UtzCV@w zV$>UQ#t_2T4&17dhmIo^gVGcPCll<(Sm<$>p0&4~EdCDb~$ID?G5EDsJK!Cddm6R!qC!@7}zcRUe-V_FGk;6~aq6iF+5ePe>8M|O;2{eoNpX;(eG8ptfEj66-5NRrT!8P5+| zovH^udY)S=9|O}-GMxT5o#2a|Ryu*;gf<@BWwlfj2f)moidE)$mOcjdB^Bx6++{jH zWce}`qN9#gO-5pvZ+>5V)#TiK)gJGxCLdVJf;1b$T0xB{|p&l1lN41 z%p29eP0$V(|#}@v^A&zlI14ICw2pfOIvWYAv)s6uh&03 z0lsk8^XiV4?gy010JRk#Fo|-67=Kl;T_XSE1F0WCpwt8i$_By!MrPiX4?4iDAdFrU z@OvXISC%pZsBBzdpr@Nx-hWJ_VfA|L^NqOAb-v)C6)x}BPS^=Jac3uX#+pZ(`9LJt zfdSW-vIB%xl%BvF-mHFXxX7#t+?mV!>E2dZpS{wyC&6qcKmVDcO8>0gCya=mwwpUI zQ;>ZeRijKl1!=ZTj{6n%fhb0)dSvZJ=i&JFh#&9*X#zg6q&eiI1TrikRcy63lTHi(rlfOuha(cyDEcjSmmIO+ra%MU?TWq&8toylFS)s zSF;Ne_pEm~z=oJ8q4zf@9CT-F?{W;t^KZ2AJn!pU-ngxB@?>_M|Y_4 zlunV!GmC4_TJKv??BvxA>@z{ND@H6EDe`Un)Muqzu)CVeSb{&HxCSg~Y-8HeqF#z0xDX>=K0|t|V zYN~=^ZM1<-pJ#K3Q1J7|*MuB!teh9161c5ao z=;#30yzd`lw>@a7qT$lMZU&=aqc+)FbqJ8~UDls`|M&;b1z1NI89&(F^g3t;cbMwrZn zDF1RfI(*q=9HTKB&dK13LON-dDmA_=S-$Ks?&LIPBQJR72VBjjR{?JO8G0?;tDp9u zZMhprCaHClf27HU8W!+Zy(h_Y9JP5D@?6nTtx6RZ@rVX{3)mO)II_e9e-_4TRD%yY zvp6m$)Sp;T^O+zFk@TeuoJQ~$Dc}{$kCRl7JE#<$+3Q_VAP%&gV-xkRy2UrQCrIx! z-8zC>#WXv~vuD7G64d`I?2JK#0ZsUJ%Z`Tuaf8hlSBQMz(LLHJEs@84?mvM(>u#F z5#J?&Gr{x?XW;u1Jr5XOsG9C4SAnne-EsTPx)VTlCm3JY2Gqlw4>fFZ-ClS{JQLL6 zEiWNSNl8oSZ57`(Uc$KC3heEv1LAHt@ISj<+It^spiKWn@)&XY^mJZ~gLU+E+P&Nt zqaN||m)qW6gZldC6yNirkSTA5*yBYqcM+1fduca>}f$I&x>5frb z-7nawOHlK`7od9~B@P;D+AYgX9@d=X8SE`~Y?SZXvT0e~=%g84n6>T>mV0ix%aJVT z`~B(fDPXT5f%Sd;PAF54B*J}x4`ZllC}Bde3?@?IjEw~&ohnTF`}yWp<=$bUzjL@F zLVYkbgg+JZU8@QCLG6=j!t(cXHeFx*p&ul8vwNd%X9b`k*Fjn+OHsie85I7ed{JppVZnjk1T_8Y!C zQ;=~a_!5>7%!&yf)F|uEf9|0`t5|l>eF-5&W?4F?g4U~PLvH6S#O<6 z89G)L6%|&}bDC%9;RJdQ9baBl@H1)%+!05S~Nfk<1TvFRir z2u79C{ErqLfqJ~9fjWo=m&rt+L=y?`kE21UpL#)FO(XZ}55EzPtLp#N3&5eJeE^5! zDk8?D1G3KHpV7}PqYiqC3-bp{^g}*;JAQLO=beT~zmRzs0iQ)cySPf|xF-3b`D8ET zV`dju(G5I=ge|1L6=bH>k1#a8GPnM~&h*j_tZtZ70P2WuFlh@oAp9JUfEzGzjXL}} zNuE-}CD|b^WvrSmuHkfW(o;Zm@1lyVZyuXFWyeW$BN6KyrZ4V!u9}EuQe08AZf?oe zV^+Od#yP^KUDM^fUm3-sBTmnL&%W|eH2GV9uojkEkQbEsN_CU_HdWyUv9Zu2#cbl` zg1&8_B-~qQtiE*fH#Ynf!03%vq%@JHvngx$-pd*-j$>2(4QXzkVg%moH_y*bJL;aO zJk3gDM@wFI4{}Jn6`nWn6OY{U`PES{-D`~l6`8EHSWKQzQZ3<>jD1gU@(P}mOPfiN zHzq_+8TssRMkqQ?5`c|+^p;1C9@&1>w$L%_XD-4{k}&xl45xC#LFXVg5ZBG=P)$kR zwsh$NTwdp{pO-t9aRuIXhaF++{`TQz$&VOCtzKOE1F4nwpX&sk!5ec~Q*z&dC0ptk z@#AS#r>f|WOG{3lliW>%!g9%hNrtmi5baW)3c*b&7QgcxhpjXIMV$0cQJ1=^eJK;~ zgjUHZ;HBSZqnX=QA9JpC`>Zr({=?T{fjG+uk?I3TM!)S6)M0c>AKPX~e|^FVVT1Gf z053!4diB>Xu?c6+clHfFs)LW}KD}gnFjxCFz5Jf3RKIpkhXc^{@Q1tR{`8CxZ@UuC zrMYIq2<6Gdxiq+ODw;Ugki02EStK~_*)gPE@`C7AxJOs{>#~es!K|=+1UjHv>!TU|Nyeau1=Bj^3OQb| zBzXH&F4S1{T*KIW04&iKjpVEZKJ^QVJ$HiuK29-@sz_)YDS_481x;5RPcy0MG!R$yxka79 zr1NTil7qBzd)#WCiL>(UxcF$f^SK+JQ$G3HRSj*n&c>^MK45*CD5v3|@RfXB<}v|% z?P*FO*!b0PAW#G&2h@B4KzWE6q=x(DHA!b{px>xoPV)Ir-hgM(78uBOh_mZ94~6Dx z1#wtHWI3{q#BMf0NL-XjZ5F^*ZZT^FicrFAsB}z_I_K;kLmQaRq?d9sKLdj{k}*3< z)gWADOjgSJXmg0PTgFgKSX@}L?|PdV6_=r=QltaC)XidqX&(0xIKC|Fc#QLqdBY1x z$|G0rnjd6tNMH6pnBvDteinhlbangsx_*Arb56VDc^pks=c_Q=il|Ymbiap=mqw5u zC?@`j+U+J=_dC+P!a3%xsKJNc3HK-MsX9Aa_Tc!yKk}HrIk15#s1j#z+C{#lO%Jb; zHNn{5E9KH}-B_}@CEKL7`pON&t;&HC=(Q>wx0y8ee4GG4zQr6%(gSGLTM)-WOwYQp zqRGV~v5{-OyuItHoBVu=%#D5UzD*ikmv6#m*-Eg{3i*Nu*4P$uH|s>0E(dO>kyv45B%A7q|p5{akFRlUrk~OtWshRIom@h zucpKKbjI#(ayFHx^UEs^B4Cee~XYvWGP^3! z_PQq(RtiTSDpv7WbJ4a7pQ*830rA6);}AaIpQCIO2eaEGdrB}L+$|~d(riBS*IR{; zyTU$piMl9mI7nlmtDjAmCOGi>$=uSWH%llKO=rrhkZZeIKKsyD&l2^Po2T5@*B|}&drMdEp?q>)+`V&n3~$t^rAqR=!EWX zMT663@}Y#UAyT$WvXzx^1ihEw<3m2i?sviHUfcoN9kIf#mtCGfGIq$vZ1ZxAeVi>0 z!GtABIG7@QD4A!b9!i@SQ&cQ}qieErI4PQ*0&v}YgG%Ro0M|7qoAAiZ9If4%S7)Tb zVU@Pii2cc)K#$aE4 zS1Xl~aBYIo84^ON$5Hmue0d4(Me;?d%rpqT1~NK{^rd1?6Q23CL^Cqx1jj*qU0WYQ z^cc7|$w@;U3BkRZs1^TBQQhHT510+u6SQuCUF8eCJS_$mWqw&k_;r$xjhh0mNojgG zMrPXWUm&g(Jl2U?DbiJlXMfDq_#RFR%9Y*9-$r$OKj#9>~UjOVd z=8Oo$Wgw7iu()zvS|8rgui3WO5oyM@@HFGo^s<@&+LrFjCxnJ5vSs9SS`aXHS6@Ja zVFT@4J944h2=aBn*frO*{IOVPIh1UOjl5<4-rN`$XSymLmna;+yQt9IK~1KxxqHaD zUU2Ykhr0nt+j~c$$tI@+-w@3!{(7BX-^P7i8^CrOBAkPugGq? z*BG^(L^-IO z&N^>*bTxP05`UL$NzB(iM`D})AzAFSKxV75-wSa(H^L=TxGyoixNFtXjj@cVhU11a z_ma2Erk|5E(ZA6f78YuOVfu(P=W0WFyv3uD2kJW6VsslRr_QDa8GCj+BQs1N*3BF! zr|tG^R9Q!EkDjJt5k$Yz!5L4xV_%!?YY`uF+_={XD7(W>icF5q=IxaNXT=+yq{$nk~G|_+uRl^d-LXKZZFH-A2z>l z|En}ifV)xAr>C+jolm+0cWqy9hp*i0oK~{r6n|D6In+7)4MiVo63lqA!kQ(%M-!nK!xwC@cxzs7 z41s#6xDNGy(Nm$k!y0FrLo!SlG7V$7Fh*oI&C?$CFw)Qh-c!kRLRocp19Nd@-0E+! zXI(CK?!b=f36z&xHF5zPz-BHLt|noMq-1ODQm9bjcb|#gUC?J+yQ!hF?sAM?cFUXWto{ecE>E{4~s?IQNsTm2)%9Z-3oo zJ~dyoD(c=zc2Ve+a z6!n&*&fP*}rCK`QmyB+b@Y#D&U-F3Wqpmf%sZCR^;N|2VZF5|FOSOFjZ9?2ii{hDKlZR?*1E-JS#2Q*{ar&5 z+~{?sTzxdhy*SG#9f?@N3c)?UM`YRZ2*)gyVJNo7%QRWtbf<=Vb!FgZRhj+(a| zWv(yoc@Z8T>b|6JEBNl19pQe*C&p{4T{=$@Ya3c41Zr zKixHY=4M@?itdPl&Q9R^=hLj~`6=>@AjUFYNIWGm#&||^b1{cQdfhpVEt%OZ5G2Og+$}Av{Mio@ zj?fELl}B~uG&TWPbox+=E<=TlJ1tYK5za~w`>yH9Z!#f%ToVbdKh8c}?0*m}_-})? zD!VLtOml4;I!#Ui3!>G9A$9d7mII&0^Z6TLf_KAy&oC^*sc+pH;~2U!J};WuC_`}F z+>v33uf%CdWgg5RQJE`~#ukut7EW!_rXcBXV!-88FCbU&j0pWz*NoOvY2%)c>mx}{gipvU#k zd^%-r?)Y>DiYB613_WLk&(Xe4?+!Spw*wllY44jA&qzI^Zhz+w_i%!kIiBnX`@d7d zdN~;97uEe4GVltvP{MprNtQtkqo0tzkviYogFT+A`Vg2{#AC*(9}} zzPEdKAUtvmdDocq9&5_hj94h2s)kAxsMCCCdQv??0HJulb|n~6$*!7TWxI1tV&s0+ z3PisfWBfFBC%tfwcO(nf*36eAj(XbApcl;uHhU1|u_M^*|4NgBqcbYb3v~)m3K3RxKh&!aqM}U{<@oQ7eWqete`w?dGG*^kE|;^Ia&j zzEF2W)2i2)tnXbT{%%?* zCsxmQ>ZX+%A@7fuG14mA%KlPLM-1VR_;#SeA-qr*Q$29$|3ZX;RZa566#{{b>>cjs zWJbNR$GQiL#bL61;At5%X`LDQPys#8J7y#SZFVZ)`UU$o1>tr- zdME)688*vDX@*a~e^E~yoEps;+LWq2EOv6VOXfE4h(m^GKFVI5{&|B+z%xqCy6ant znZ)RG!y2QDB^gg8*>?eK+6W-4-;-1e1e2)0OCRoUN;mlVVnkH1T;onNdmRMpw@b1F zo=R}%r8ho+aDl$|+uuah)A0z@W>1_>He1W3=IEi^Mj_r?&_%p-Fqyt(f522}fgi2q zSzs%VDwwETQ9A@qG{6-C6K9Pzm=`?T$o_$aL)Q>Z)&WlmD)r z0)AZt-D_3uQ^^55$iOE6(1}yYRU#^f-SgICK)g?Y?6g01+WPmlwPj-kAv) z*Xc)~D`6xQFE3k@+^u3L#*77|T}2FJ5>7Zj!uOp6m=+s=qSUf%6I9M;70bm&!Lx6a zwan}pm4Sk+`PAOt<}*4ve3M=zC{zA3s=XC7yv*b9xj&~PU%RW0dSI~NF(r>;Y>ar> z!r{#GkS6|J+FggKlrMD}^(Hb0r=0f_b&yOZ{!NE|CtV(Ocw35nyH0 znLB+G{c_KJiRNdq+b5zWn!Mm6yv8ZtbL!<=$1K5n>nvcW60ht`-t6*IM~fto1X#tX0J>g8nr4ga43cM&E1QbIcc$H?@l1T|)kKAVw~lRFMruvpw+ zeQ}eR*0uwLgk_doE7p)c0h2Pm-ABn5mqk?Z&JF)%nhf)ctHNPky?V6q>HLXe`q2K^ z!PI>Fx-G?*d3GtfsLQohBUw@Y&YmOlz877+8QXAb6ZJs}SWp--vW*(V*kSb zFsoQ1|7{2=SiK*QaxHe;o^Z|sPN_O4KREmy4(UzlTj@OvB#n+lK?_DtIi_{+OnD5P z1CB;^FFExRvYo7g6rp3M8SP3ly6ved71A(O59<7z<&Qzp`q^~ol+JS=<)(!j5mus`Vw#7*qX~)mXRun zsNGNm1zTHux1gH>+Z3us^3&31-|k#SvM(Lxe8A-E;dtwg9}{09|6OqD9+!zrL2E>| zdg*D0crU_aLYs`{1cbA;K4}u>GHlFb$U~^6G2_tUihmFWHGG8q9(QWBviS_Wf#XMe z9d|7I6GgSktEa@<7GFJ_-xSrqsr;Di=P}5UdfWkc-E7`8y5qvdp`^OOfQ$YdaUMi+ zz;ss)42O<7i-o0VNx1b(c72|(CKqZutDIM#co4lKRp z_`McVu2oJQ@c@JP!Ol`L2x-0Zl~O>b2d$wqjo>#not%4Nq$B*)D)SyvoD09ZUg5c> zq7hCKJTW1;otwmIQ!yXU?TXmc>skzB^!H3zVeuiF1OXNXO_&YII}CsD61Szo>Ga+o zHj5rc-hb31?_Pa&;qaA#$i(AA3skv8K&cD)Jn}i_`l(`g-w2CseA9avjVN)Y+}?4^ z8eBBHNR;gY;L7LO+fZ_rTEgAdmP%ESsz*&6&?OJ5yLOeF8h9~+?W-2E9p{p)T5z8y zZsZu4YM5lL)Rnw*?ugeMvi!?NaDfv>B{g~GQT!>&uMd5!On<=8Vd4@a-ClrxLMP7U zboI;^W3{8e)&fMFOQa?r&md1$YJ-o9*~u*pT3DM@2|^hOXoRDR#iu0A9Rh<)^c~OZ zK!=9XKIDu?opA@zL>zF?M9p!}ULP8UGv>J^=OhF@bW7_77yzSZGX)T1;h|91N9WTA z?RgUEEEr=H+6JT;`jwOt`{OP%y9S#+UpyJuYSjgIivs+vk{p2886SeYOHS*wi0pA!D_dIu;`+bl9 z_z@U3d#|CfloZYC)jvYHXbP94NHvw8KRfW?b9_WB=8mHaK9tDN(iR{rj z7i+<5_fERiTL4W(&G7~!mBAQyjm%yuCE&aBcE;hB>bYA$BA2JXEp9r+;o1-B_W2US zV?LYh{=>K<0BYcU=IjQGB0QOOR4|)>Y>#T=Lv0Se7WtfCBnUm_<+Wr%P((-qO5=Kb zK=mvbHu^<|{s>*tRVkB{9CN6m@h;nHV@>Eq=eh?-S%0|969lYsFViEW z&5vvz)gUoj8iZ%0H~IbjvvKh@MN#NQo|i-b{g*+@dxwC<=fRr~Xj3;Yh%-JHiA90D z>UjGLJ+HL{8zzKu?g2L4P?e51FxYMcE>jGU7fQT%e&ybenzkj!j!3Nq38etSIKQ|y zN8t3#pDOMtDwOq3#lghKh4z>C0N84~s8g{Mt+o1bZidp3y8!zP1Q45zk<6yH^9Fbq z2eZx_RJv?kwf7eox=bJ}l7Y-N7M{UFqLD1tAm3j&wKH3HvCR_9UJ`SVPaVWtEj$jw zzC>I&eIO=LdG(Nv?qjd18zTtNi4c$f}U0@&b;%ndP~bdqTO-v*u`Z**y(1Id1DB(6m?4b>`U@%ap!PQ zeM#+-vnfAU`+FiN8R2*2lsgYMBD;ll3$RY(39J^wSy2wVap``&Tb#$RMk}Qo(cW@)ypTXd;&xz>IMq0hG)xzlt+!NZwOo2xbPony~UL>Hxf+u6|_7_ zIoZml>AA*nmBW?qsyib#AA@{m<6&gE`8~bDgniP-imhz4SE6bI*QhC=Dw3|af9oiwvCQ^0+h)udzW918x#jcJ4K-q(v zGCm@`*6|M|XG94UmgtO1{)bAIan=Rx{;PZ8^mnYdFD;&}W3IwdXqQOsQ!%4)yT!y$WY-zYP3vK0=c z9*nU4X^%HzDc$VU6l3pztQR%z&Mg56doFDYgUL59@M0bbqF?JPu*%c}gU{x0Kap`a z)}y0Y8B!=YN+h&<|5Ym_;Hw22-&d@Uq47dY4j;TA;(_NKHO&@Yh+^ zc#G)q3&+B1k2wQ3i&V+oj?s&W_$BPnlLIqQ795O}bM0WO0|794>>i$OkEPn|7Rbvr z9Eu5W}{7i>7T?twM=RJ?3v=pupjs(`T`W zknMIV_FgaiLUrvpaff`N_w|_%^p;o%6LF<5yVm;Tbt29rCr!wts;MVU-b5Hvk8fT(qmh8A`QL`g)Zvb<5==56Bo&bPwiQojSMeT&ZD?2pXsASbCE46NVb6bwj19 z_r8bVP>@rQsi1EU`?g*cq79#Kn5Iimd|KK-4d1d4g_j7fQynK83hwm?M^ck@97YV#ItVa z248Uv3X->jtytUk1&LHw#&-X5jyliA^zn-P3x- zg)GPfbkxcpl`FET+q3|A|6qRrFjljQ6i3>C4mZ|%eIlMRjl>Kw&+%EQj1zm%fD4i* zl@bCc3JHfJk?l`qYu+5C{Lb3Zrj0$;aR7(%0D;(k4O}!hj{wd%Vk&ix`e%9MJwarq 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 0 { + opt = opts[0] + } + + db := GetActivityDB(ctx, a.DB) + + var list schema.Activities + pageResult, err := util.WrapPageQuery(ctx, db, params.PaginationParam, opt.QueryOptions, &list) + if err != nil { + return nil, errors.WithStack(err) + } + + queryResult := &schema.ActivityQueryResult{ + PageResult: pageResult, + Data: list, + } + return queryResult, nil +} + +// Get the specified activity from the database. +func (a *Activity) Get(ctx context.Context, id string, opts ...schema.ActivityQueryOptions) (*schema.Activity, error) { + var opt schema.ActivityQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + item := new(schema.Activity) + ok, err := util.FindOne(ctx, GetActivityDB(ctx, a.DB).Where("id=?", id), opt.QueryOptions, item) + if err != nil { + return nil, errors.WithStack(err) + } else if !ok { + return nil, nil + } + return item, nil +} + +// Exists checks if the specified activity exists in the database. +func (a *Activity) Exists(ctx context.Context, id string) (bool, error) { + ok, err := util.Exists(ctx, GetActivityDB(ctx, a.DB).Where("id=?", id)) + return ok, errors.WithStack(err) +} + +// Create a new activity. +func (a *Activity) Create(ctx context.Context, item *schema.Activity) error { + result := GetActivityDB(ctx, a.DB).Create(item) + return errors.WithStack(result.Error) +} + +// Update the specified activity in the database. +func (a *Activity) Update(ctx context.Context, item *schema.Activity) error { + result := GetActivityDB(ctx, a.DB).Where("id=?", item.ID).Select("*").Omit("created_at").Updates(item) + return errors.WithStack(result.Error) +} + +// Delete the specified activity from the database. +func (a *Activity) Delete(ctx context.Context, id string) error { + result := GetActivityDB(ctx, a.DB).Where("id=?", id).Delete(new(schema.Activity)) + return errors.WithStack(result.Error) +} diff --git a/internal/mods/activity/dal/activity_category.dal.go b/internal/mods/activity/dal/activity_category.dal.go new file mode 100644 index 0000000..04f1d65 --- /dev/null +++ b/internal/mods/activity/dal/activity_category.dal.go @@ -0,0 +1,83 @@ +package dal + +import ( + "context" + + "gitlab.guxuan.icu/jinshan_community/internal/mods/activity/schema" + "gitlab.guxuan.icu/jinshan_community/pkg/errors" + "gitlab.guxuan.icu/jinshan_community/pkg/util" + "gorm.io/gorm" +) + +// Get activity category storage instance +func GetActivityCategoryDB(ctx context.Context, defDB *gorm.DB) *gorm.DB { + return util.GetDB(ctx, defDB).Model(new(schema.ActivityCategory)) +} + +// Defining the `ActivityCategory` data access object. +type ActivityCategory struct { + DB *gorm.DB +} + +// Query activity categories from the database based on the provided parameters and options. +func (a *ActivityCategory) Query(ctx context.Context, params schema.ActivityCategoryQueryParam, opts ...schema.ActivityCategoryQueryOptions) (*schema.ActivityCategoryQueryResult, error) { + var opt schema.ActivityCategoryQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + db := GetActivityCategoryDB(ctx, a.DB) + + var list schema.ActivityCategories + pageResult, err := util.WrapPageQuery(ctx, db, params.PaginationParam, opt.QueryOptions, &list) + if err != nil { + return nil, errors.WithStack(err) + } + + queryResult := &schema.ActivityCategoryQueryResult{ + PageResult: pageResult, + Data: list, + } + return queryResult, nil +} + +// Get the specified activity category from the database. +func (a *ActivityCategory) Get(ctx context.Context, id string, opts ...schema.ActivityCategoryQueryOptions) (*schema.ActivityCategory, error) { + var opt schema.ActivityCategoryQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + item := new(schema.ActivityCategory) + ok, err := util.FindOne(ctx, GetActivityCategoryDB(ctx, a.DB).Where("id=?", id), opt.QueryOptions, item) + if err != nil { + return nil, errors.WithStack(err) + } else if !ok { + return nil, nil + } + return item, nil +} + +// Exists checks if the specified activity category exists in the database. +func (a *ActivityCategory) Exists(ctx context.Context, id string) (bool, error) { + ok, err := util.Exists(ctx, GetActivityCategoryDB(ctx, a.DB).Where("id=?", id)) + return ok, errors.WithStack(err) +} + +// Create a new activity category. +func (a *ActivityCategory) Create(ctx context.Context, item *schema.ActivityCategory) error { + result := GetActivityCategoryDB(ctx, a.DB).Create(item) + return errors.WithStack(result.Error) +} + +// Update the specified activity category in the database. +func (a *ActivityCategory) Update(ctx context.Context, item *schema.ActivityCategory) error { + result := GetActivityCategoryDB(ctx, a.DB).Where("id=?", item.ID).Select("*").Omit("created_at").Updates(item) + return errors.WithStack(result.Error) +} + +// Delete the specified activity category from the database. +func (a *ActivityCategory) Delete(ctx context.Context, id string) error { + result := GetActivityCategoryDB(ctx, a.DB).Where("id=?", id).Delete(new(schema.ActivityCategory)) + return errors.WithStack(result.Error) +} diff --git a/internal/mods/activity/dal/reciprocity.dal.go b/internal/mods/activity/dal/reciprocity.dal.go new file mode 100644 index 0000000..a026bf2 --- /dev/null +++ b/internal/mods/activity/dal/reciprocity.dal.go @@ -0,0 +1,83 @@ +package dal + +import ( + "context" + + "gitlab.guxuan.icu/jinshan_community/internal/mods/activity/schema" + "gitlab.guxuan.icu/jinshan_community/pkg/errors" + "gitlab.guxuan.icu/jinshan_community/pkg/util" + "gorm.io/gorm" +) + +// Get reciprocity storage instance +func GetReciprocityDB(ctx context.Context, defDB *gorm.DB) *gorm.DB { + return util.GetDB(ctx, defDB).Model(new(schema.Reciprocity)) +} + +// Defining the `Reciprocity` data access object. +type Reciprocity struct { + DB *gorm.DB +} + +// Query reciprocities from the database based on the provided parameters and options. +func (a *Reciprocity) Query(ctx context.Context, params schema.ReciprocityQueryParam, opts ...schema.ReciprocityQueryOptions) (*schema.ReciprocityQueryResult, error) { + var opt schema.ReciprocityQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + db := GetReciprocityDB(ctx, a.DB) + + var list schema.Reciprocities + pageResult, err := util.WrapPageQuery(ctx, db, params.PaginationParam, opt.QueryOptions, &list) + if err != nil { + return nil, errors.WithStack(err) + } + + queryResult := &schema.ReciprocityQueryResult{ + PageResult: pageResult, + Data: list, + } + return queryResult, nil +} + +// Get the specified reciprocity from the database. +func (a *Reciprocity) Get(ctx context.Context, id string, opts ...schema.ReciprocityQueryOptions) (*schema.Reciprocity, error) { + var opt schema.ReciprocityQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + item := new(schema.Reciprocity) + ok, err := util.FindOne(ctx, GetReciprocityDB(ctx, a.DB).Where("id=?", id), opt.QueryOptions, item) + if err != nil { + return nil, errors.WithStack(err) + } else if !ok { + return nil, nil + } + return item, nil +} + +// Exists checks if the specified reciprocity exists in the database. +func (a *Reciprocity) Exists(ctx context.Context, id string) (bool, error) { + ok, err := util.Exists(ctx, GetReciprocityDB(ctx, a.DB).Where("id=?", id)) + return ok, errors.WithStack(err) +} + +// Create a new reciprocity. +func (a *Reciprocity) Create(ctx context.Context, item *schema.Reciprocity) error { + result := GetReciprocityDB(ctx, a.DB).Create(item) + return errors.WithStack(result.Error) +} + +// Update the specified reciprocity in the database. +func (a *Reciprocity) Update(ctx context.Context, item *schema.Reciprocity) error { + result := GetReciprocityDB(ctx, a.DB).Where("id=?", item.ID).Select("*").Omit("created_at").Updates(item) + return errors.WithStack(result.Error) +} + +// Delete the specified reciprocity from the database. +func (a *Reciprocity) Delete(ctx context.Context, id string) error { + result := GetReciprocityDB(ctx, a.DB).Where("id=?", id).Delete(new(schema.Reciprocity)) + return errors.WithStack(result.Error) +} diff --git a/internal/mods/activity/main.go b/internal/mods/activity/main.go new file mode 100644 index 0000000..45d13f6 --- /dev/null +++ b/internal/mods/activity/main.go @@ -0,0 +1,64 @@ +package activity + +import ( + "context" + + "gitlab.guxuan.icu/jinshan_community/internal/config" + + "github.com/gin-gonic/gin" + "gitlab.guxuan.icu/jinshan_community/internal/mods/activity/api" + "gitlab.guxuan.icu/jinshan_community/internal/mods/activity/schema" + "gorm.io/gorm" +) + +type Activity struct { + DB *gorm.DB + ActivityAPI *api.Activity + ActivityCategoryAPI *api.ActivityCategory + ReciprocityAPI *api.Reciprocity +} + +func (a *Activity) AutoMigrate(ctx context.Context) error { + return a.DB.AutoMigrate(new(schema.Activity), new(schema.ActivityCategory), new(schema.Reciprocity)) +} + +func (a *Activity) Init(ctx context.Context) error { + if config.C.Storage.DB.AutoMigrate { + if err := a.AutoMigrate(ctx); err != nil { + return err + } + } + return nil +} + +func (a *Activity) RegisterV1Routers(ctx context.Context, v1 *gin.RouterGroup) error { + activity := v1.Group("activities") + { + activity.GET("", a.ActivityAPI.Query) + activity.GET(":id", a.ActivityAPI.Get) + activity.POST("", a.ActivityAPI.Create) + activity.PUT(":id", a.ActivityAPI.Update) + activity.DELETE(":id", a.ActivityAPI.Delete) + } + activityCategory := v1.Group("activity-categories") + { + activityCategory.GET("", a.ActivityCategoryAPI.Query) + activityCategory.GET(":id", a.ActivityCategoryAPI.Get) + activityCategory.POST("", a.ActivityCategoryAPI.Create) + activityCategory.PUT(":id", a.ActivityCategoryAPI.Update) + activityCategory.DELETE(":id", a.ActivityCategoryAPI.Delete) + } + reciprocity := v1.Group("reciprocities") + { + reciprocity.GET("", a.ReciprocityAPI.Query) + reciprocity.GET(":id", a.ReciprocityAPI.Get) + reciprocity.POST("", a.ReciprocityAPI.Create) + reciprocity.PUT(":id", a.ReciprocityAPI.Update) + reciprocity.DELETE(":id", a.ReciprocityAPI.Delete) + } + return nil +} + +func (a *Activity) Release(ctx context.Context) error { + return nil +} diff --git a/internal/mods/activity/schema/activity.go b/internal/mods/activity/schema/activity.go new file mode 100644 index 0000000..a4fe312 --- /dev/null +++ b/internal/mods/activity/schema/activity.go @@ -0,0 +1,92 @@ +package schema + +import ( + "github.com/google/uuid" + "gitlab.guxuan.icu/jinshan_community/pkg/util" + "gorm.io/gorm" + "time" +) + +// Defining the `Activity` struct. +type Activity struct { + util.BaseModel + CategoryID string `json:"categoryId" gorm:"type:char(36);index;comment:分类id"` + Title string `json:"title" gorm:"size:128;not null;index;comment:标题"` + Cover string `json:"cover" gorm:"size:2048;not null;comment:封面"` + Images *[]string `json:"images" gorm:"serializer:json;comment:详图数组"` + StartAt *time.Time `json:"startAt" gorm:"not null;comment:开始时间"` + EndAt *time.Time `json:"endAt" gorm:"not null;comment:结束时间"` + StartSignupAt *time.Time `json:"startSignupAt" gorm:"not null;comment:报名开始时间"` + EndSignupAt *time.Time `json:"endSignupAt" gorm:"not null;comment:报名结束时间"` + MaxSignupNum int `json:"maxSignupNum" gorm:"not null;comment:最大报名人数"` + SignupNum int `json:"signupNum" gorm:"not null;default:0;comment:当前报名人数"` + Address string `json:"address" gorm:"size:1024;not null;comment:活动地址"` + Content string `json:"content" gorm:"type:text;not null;comment:活动详情"` + Status string `json:"status" gorm:"size:20;index;comment:状态"` +} + +func (c *Activity) BeforeCreate(tx *gorm.DB) (err error) { + if c.ID == "" { + c.ID = uuid.New().String() + } + return +} + +// Defining the query parameters for the `Activity` struct. +type ActivityQueryParam struct { + util.PaginationParam +} + +// Defining the query options for the `Activity` struct. +type ActivityQueryOptions struct { + util.QueryOptions +} + +// Defining the query result for the `Activity` struct. +type ActivityQueryResult struct { + Data Activities + PageResult *util.PaginationResult +} + +// Defining the slice of `Activity` struct. +type Activities []*Activity + +// Defining the data structure for creating a `Activity` struct. +type ActivityForm struct { + CategoryID string `json:"categoryId"` + Title string `json:"title" ` + Cover string `json:"cover" ` + Images *[]string `json:"images"` + StartAt *time.Time `json:"startAt"` + EndAt *time.Time `json:"endAt"` + StartSignupAt *time.Time `json:"startSignupAt"` + EndSignupAt *time.Time `json:"endSignupAt" ` + MaxSignupNum int `json:"maxSignupNum"` + SignupNum int `json:"signupNum"` + Address string `json:"address"` + Content string `json:"content"` + Status string `json:"status"` +} + +// A validation function for the `ActivityForm` struct. +func (a *ActivityForm) Validate() error { + return nil +} + +// Convert `ActivityForm` to `Activity` object. +func (a *ActivityForm) FillTo(activity *Activity) error { + activity.CategoryID = a.CategoryID + activity.Title = a.Title + activity.Cover = a.Cover + activity.Images = a.Images + activity.StartAt = a.StartAt + activity.EndAt = a.EndAt + activity.StartSignupAt = a.StartSignupAt + activity.EndSignupAt = a.EndSignupAt + activity.MaxSignupNum = a.MaxSignupNum + activity.SignupNum = a.SignupNum + activity.Address = a.Address + activity.Content = a.Content + activity.Status = a.Status + return nil +} diff --git a/internal/mods/activity/schema/activity_category.go b/internal/mods/activity/schema/activity_category.go new file mode 100644 index 0000000..710055c --- /dev/null +++ b/internal/mods/activity/schema/activity_category.go @@ -0,0 +1,55 @@ +package schema + +import ( + "github.com/google/uuid" + "gitlab.guxuan.icu/jinshan_community/pkg/util" + "gorm.io/gorm" +) + +// Defining the `ActivityCategory` struct. +type ActivityCategory struct { + util.BaseModel + Name string `json:"name" gorm:"size:1024;comment:名字" ` + Sequence int `json:"sequence" gorm:"index;default:0;comment:排序"` + Status string `json:"status" gorm:"size:20;index;comment:状态"` +} + +func (c *ActivityCategory) BeforeCreate(tx *gorm.DB) (err error) { + if c.ID == "" { + c.ID = uuid.New().String() + } + return +} + +// Defining the query parameters for the `ActivityCategory` struct. +type ActivityCategoryQueryParam struct { + util.PaginationParam +} + +// Defining the query options for the `ActivityCategory` struct. +type ActivityCategoryQueryOptions struct { + util.QueryOptions +} + +// Defining the query result for the `ActivityCategory` struct. +type ActivityCategoryQueryResult struct { + Data ActivityCategories + PageResult *util.PaginationResult +} + +// Defining the slice of `ActivityCategory` struct. +type ActivityCategories []*ActivityCategory + +// Defining the data structure for creating a `ActivityCategory` struct. +type ActivityCategoryForm struct { +} + +// A validation function for the `ActivityCategoryForm` struct. +func (a *ActivityCategoryForm) Validate() error { + return nil +} + +// Convert `ActivityCategoryForm` to `ActivityCategory` object. +func (a *ActivityCategoryForm) FillTo(activityCategory *ActivityCategory) error { + return nil +} diff --git a/internal/mods/activity/schema/reciprocity.go b/internal/mods/activity/schema/reciprocity.go new file mode 100644 index 0000000..7e27d8d --- /dev/null +++ b/internal/mods/activity/schema/reciprocity.go @@ -0,0 +1,68 @@ +package schema + +import ( + "github.com/google/uuid" + "gitlab.guxuan.icu/jinshan_community/pkg/util" + "gorm.io/gorm" +) + +// Defining the `Reciprocity` struct. +type Reciprocity struct { + util.BaseModel + CustomerID string `json:"customerId" gorm:"type:char(36);index;comment:客户id"` + Title string `json:"title" gorm:"size:128;not null;index;comment:标题"` + Images *[]string `json:"images" gorm:"serializer:json;comment:详图数组"` + Content string `json:"content" gorm:"type:text;not null;comment:活动详情"` + Status string `json:"status" gorm:"size:20;index;comment:状态"` +} + +func (c *Reciprocity) BeforeCreate(tx *gorm.DB) (err error) { + if c.ID == "" { + c.ID = uuid.New().String() + } + return +} + +// Defining the query parameters for the `Reciprocity` struct. +type ReciprocityQueryParam struct { + util.PaginationParam +} + +// Defining the query options for the `Reciprocity` struct. +type ReciprocityQueryOptions struct { + util.QueryOptions +} + +// Defining the query result for the `Reciprocity` struct. +type ReciprocityQueryResult struct { + Data Reciprocities + PageResult *util.PaginationResult +} + +// Defining the slice of `Reciprocity` struct. +type Reciprocities []*Reciprocity + +// Defining the data structure for creating a `Reciprocity` struct. +type ReciprocityForm struct { + CustomerID string `json:"customerId"` + Title string `json:"title"` + Images *[]string `json:"images"` + Content string `json:"content"` + Status string `json:"status"` +} + +// A validation function for the `ReciprocityForm` struct. +func (a *ReciprocityForm) Validate() error { + return nil +} + +// Convert `ReciprocityForm` to `Reciprocity` object. +func (a *ReciprocityForm) FillTo(reciprocity *Reciprocity) error { + reciprocity.CustomerID = a.CustomerID + reciprocity.Title = a.Title + reciprocity.Images = a.Images + reciprocity.Content = a.Content + reciprocity.Status = a.Status + + return nil +} diff --git a/internal/mods/activity/wire.go b/internal/mods/activity/wire.go new file mode 100644 index 0000000..e056d3c --- /dev/null +++ b/internal/mods/activity/wire.go @@ -0,0 +1,21 @@ +package activity + +import ( + "github.com/google/wire" + "gitlab.guxuan.icu/jinshan_community/internal/mods/activity/api" + "gitlab.guxuan.icu/jinshan_community/internal/mods/activity/biz" + "gitlab.guxuan.icu/jinshan_community/internal/mods/activity/dal" +) + +var Set = wire.NewSet( + wire.Struct(new(Activity), "*"), + wire.Struct(new(dal.Activity), "*"), + wire.Struct(new(biz.Activity), "*"), + wire.Struct(new(api.Activity), "*"), + wire.Struct(new(dal.ActivityCategory), "*"), + wire.Struct(new(biz.ActivityCategory), "*"), + wire.Struct(new(api.ActivityCategory), "*"), + wire.Struct(new(dal.Reciprocity), "*"), + wire.Struct(new(biz.Reciprocity), "*"), + wire.Struct(new(api.Reciprocity), "*"), +) diff --git a/internal/mods/ai/api/ai_request.api.go b/internal/mods/ai/api/ai_request.api.go new file mode 100644 index 0000000..b2f8831 --- /dev/null +++ b/internal/mods/ai/api/ai_request.api.go @@ -0,0 +1,171 @@ +package api + +import ( + "context" + "fmt" + "github.com/gin-gonic/gin" + "gitlab.guxuan.icu/jinshan_community/internal/mods/ai/biz" + "gitlab.guxuan.icu/jinshan_community/internal/mods/ai/schema" + "gitlab.guxuan.icu/jinshan_community/pkg/ai" + "gitlab.guxuan.icu/jinshan_community/pkg/util" + "time" +) + +// Defining the `AiRequest` api. +type AiRequest struct { + AiRequestBIZ *biz.AiRequest +} + +// @Tags AiRequestAPI +// @Security ApiKeyAuth +// @Summary Query ai request list +// @Param current query int true "pagination index" default(1) +// @Param pageSize query int true "pagination size" default(10) +// @Success 200 {object} util.ResponseResult{data=[]schema.AiRequest} +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/ai-requests [get] +func (a *AiRequest) Query(c *gin.Context) { + ctx := c.Request.Context() + var params schema.AiRequestQueryParam + if err := util.ParseQuery(c, ¶ms); err != nil { + util.ResError(c, err) + return + } + + result, err := a.AiRequestBIZ.Query(ctx, params) + if err != nil { + util.ResError(c, err) + return + } + util.ResPage(c, result.Data, result.PageResult) +} + +// @Tags AiRequestAPI +// @Security ApiKeyAuth +// @Summary Get ai request record by ID +// @Param id path string true "unique id" +// @Success 200 {object} util.ResponseResult{data=schema.AiRequest} +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/ai-requests/{id} [get] +func (a *AiRequest) Get(c *gin.Context) { + ctx := c.Request.Context() + item, err := a.AiRequestBIZ.Get(ctx, c.Param("id")) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, item) +} + +// @Tags AiRequestAPI +// @Security ApiKeyAuth +// @Summary Create ai request record +// @Param body body schema.AiRequestForm true "Request body" +// @Success 200 {object} util.ResponseResult{data=schema.AiRequest} +// @Failure 400 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/ai-requests [post] +func (a *AiRequest) Create(c *gin.Context) { + ctx := c.Request.Context() + item := new(schema.AiRequestForm) + 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 + } + apiKey := "sk-f96d78738d3143d3859c63a5715490ac" + client := ai.NewBaichuanClient(apiKey) + example, err := singleCallExample(ctx, client, item.Msg) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, example) +} +func singleCallExample(ctx context.Context, client *ai.BaichuanClient, msg string) (string, error) { + // 准备消息 + messages := []ai.ChatMessage{ + {Role: "system", Content: "你是一个专业的科技助手"}, + {Role: "user", Content: msg}, + } + + // 设置参数 + params := ai.ChatCompletionParams{ + Model: "qwen-plus", + Messages: messages, + MaxToken: 200, + } + + // 调用API + ctx2, cancel := context.WithTimeout(ctx, 30*time.Second) + defer cancel() + + response, err := client.ChatCompletion(ctx2, params) + if err != nil { + fmt.Printf("调用失败: %v\n", err) + return "", err + } + + // 输出结果 + fmt.Println("=== 单次调用结果 ===") + fmt.Printf("请求ID: %s\n", response.ID) + fmt.Printf("模型: %s\n", response.Model) + fmt.Printf("回复: %s\n", response.Content) + fmt.Printf("Token使用: 提示 %d, 完成 %d, 总计 %d\n\n", + response.Usage.PromptTokens, + response.Usage.CompletionTokens, + response.Usage.TotalTokens) + return response.Content, nil +} + +// @Tags AiRequestAPI +// @Security ApiKeyAuth +// @Summary Update ai request record by ID +// @Param id path string true "unique id" +// @Param body body schema.AiRequestForm 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/ai-requests/{id} [put] +func (a *AiRequest) Update(c *gin.Context) { + ctx := c.Request.Context() + item := new(schema.AiRequestForm) + 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.AiRequestBIZ.Update(ctx, c.Param("id"), item) + if err != nil { + util.ResError(c, err) + return + } + util.ResOK(c) +} + +// @Tags AiRequestAPI +// @Security ApiKeyAuth +// @Summary Delete ai request 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/ai-requests/{id} [delete] +func (a *AiRequest) Delete(c *gin.Context) { + ctx := c.Request.Context() + err := a.AiRequestBIZ.Delete(ctx, c.Param("id")) + if err != nil { + util.ResError(c, err) + return + } + util.ResOK(c) +} diff --git a/internal/mods/ai/biz/ai_request.biz.go b/internal/mods/ai/biz/ai_request.biz.go new file mode 100644 index 0000000..d068645 --- /dev/null +++ b/internal/mods/ai/biz/ai_request.biz.go @@ -0,0 +1,104 @@ +package biz + +import ( + "context" + "time" + + "gitlab.guxuan.icu/jinshan_community/internal/mods/ai/dal" + "gitlab.guxuan.icu/jinshan_community/internal/mods/ai/schema" + "gitlab.guxuan.icu/jinshan_community/pkg/errors" + "gitlab.guxuan.icu/jinshan_community/pkg/util" +) + +// Defining the `AiRequest` business logic. +type AiRequest struct { + Trans *util.Trans + AiRequestDAL *dal.AiRequest +} + +// Query ai requests from the data access object based on the provided parameters and options. +func (a *AiRequest) Query(ctx context.Context, params schema.AiRequestQueryParam) (*schema.AiRequestQueryResult, error) { + params.Pagination = true + + result, err := a.AiRequestDAL.Query(ctx, params, schema.AiRequestQueryOptions{ + QueryOptions: util.QueryOptions{ + OrderFields: []util.OrderByParam{ + {Field: "created_at", Direction: util.DESC}, + }, + }, + }) + if err != nil { + return nil, err + } + return result, nil +} + +// Get the specified ai request from the data access object. +func (a *AiRequest) Get(ctx context.Context, id string) (*schema.AiRequest, error) { + aiRequest, err := a.AiRequestDAL.Get(ctx, id) + if err != nil { + return nil, err + } else if aiRequest == nil { + return nil, errors.NotFound("", "Ai request not found") + } + return aiRequest, nil +} + +// Create a new ai request in the data access object. +func (a *AiRequest) Create(ctx context.Context, formItem *schema.AiRequestForm) (*schema.AiRequest, error) { + aiRequest := &schema.AiRequest{} + + if err := formItem.FillTo(aiRequest); err != nil { + return nil, err + } + + err := a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.AiRequestDAL.Create(ctx, aiRequest); err != nil { + return err + } + return nil + }) + if err != nil { + return nil, err + } + return aiRequest, nil +} + +// Update the specified ai request in the data access object. +func (a *AiRequest) Update(ctx context.Context, id string, formItem *schema.AiRequestForm) error { + aiRequest, err := a.AiRequestDAL.Get(ctx, id) + if err != nil { + return err + } else if aiRequest == nil { + return errors.NotFound("", "Ai request not found") + } + + if err := formItem.FillTo(aiRequest); err != nil { + return err + } + aiRequest.UpdatedAt = time.Now() + + return a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.AiRequestDAL.Update(ctx, aiRequest); err != nil { + return err + } + return nil + }) +} + +// Delete the specified ai request from the data access object. +func (a *AiRequest) Delete(ctx context.Context, id string) error { + exists, err := a.AiRequestDAL.Exists(ctx, id) + if err != nil { + return err + } else if !exists { + return errors.NotFound("", "Ai request not found") + } + + return a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.AiRequestDAL.Delete(ctx, id); err != nil { + return err + } + return nil + }) +} diff --git a/internal/mods/ai/dal/ai_request.dal.go b/internal/mods/ai/dal/ai_request.dal.go new file mode 100644 index 0000000..935ceda --- /dev/null +++ b/internal/mods/ai/dal/ai_request.dal.go @@ -0,0 +1,83 @@ +package dal + +import ( + "context" + + "gitlab.guxuan.icu/jinshan_community/internal/mods/ai/schema" + "gitlab.guxuan.icu/jinshan_community/pkg/errors" + "gitlab.guxuan.icu/jinshan_community/pkg/util" + "gorm.io/gorm" +) + +// Get ai request storage instance +func GetAiRequestDB(ctx context.Context, defDB *gorm.DB) *gorm.DB { + return util.GetDB(ctx, defDB).Model(new(schema.AiRequest)) +} + +// Defining the `AiRequest` data access object. +type AiRequest struct { + DB *gorm.DB +} + +// Query ai requests from the database based on the provided parameters and options. +func (a *AiRequest) Query(ctx context.Context, params schema.AiRequestQueryParam, opts ...schema.AiRequestQueryOptions) (*schema.AiRequestQueryResult, error) { + var opt schema.AiRequestQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + db := GetAiRequestDB(ctx, a.DB) + + var list schema.AiRequests + pageResult, err := util.WrapPageQuery(ctx, db, params.PaginationParam, opt.QueryOptions, &list) + if err != nil { + return nil, errors.WithStack(err) + } + + queryResult := &schema.AiRequestQueryResult{ + PageResult: pageResult, + Data: list, + } + return queryResult, nil +} + +// Get the specified ai request from the database. +func (a *AiRequest) Get(ctx context.Context, id string, opts ...schema.AiRequestQueryOptions) (*schema.AiRequest, error) { + var opt schema.AiRequestQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + item := new(schema.AiRequest) + ok, err := util.FindOne(ctx, GetAiRequestDB(ctx, a.DB).Where("id=?", id), opt.QueryOptions, item) + if err != nil { + return nil, errors.WithStack(err) + } else if !ok { + return nil, nil + } + return item, nil +} + +// Exists checks if the specified ai request exists in the database. +func (a *AiRequest) Exists(ctx context.Context, id string) (bool, error) { + ok, err := util.Exists(ctx, GetAiRequestDB(ctx, a.DB).Where("id=?", id)) + return ok, errors.WithStack(err) +} + +// Create a new ai request. +func (a *AiRequest) Create(ctx context.Context, item *schema.AiRequest) error { + result := GetAiRequestDB(ctx, a.DB).Create(item) + return errors.WithStack(result.Error) +} + +// Update the specified ai request in the database. +func (a *AiRequest) Update(ctx context.Context, item *schema.AiRequest) error { + result := GetAiRequestDB(ctx, a.DB).Where("id=?", item.ID).Select("*").Omit("created_at").Updates(item) + return errors.WithStack(result.Error) +} + +// Delete the specified ai request from the database. +func (a *AiRequest) Delete(ctx context.Context, id string) error { + result := GetAiRequestDB(ctx, a.DB).Where("id=?", id).Delete(new(schema.AiRequest)) + return errors.WithStack(result.Error) +} diff --git a/internal/mods/ai/main.go b/internal/mods/ai/main.go new file mode 100644 index 0000000..35899fa --- /dev/null +++ b/internal/mods/ai/main.go @@ -0,0 +1,46 @@ +package ai + +import ( + "context" + + "gitlab.guxuan.icu/jinshan_community/internal/config" + + "github.com/gin-gonic/gin" + "gitlab.guxuan.icu/jinshan_community/internal/mods/ai/api" + "gitlab.guxuan.icu/jinshan_community/internal/mods/ai/schema" + "gorm.io/gorm" +) + +type Ai struct { + DB *gorm.DB + AiRequestAPI *api.AiRequest +} + +func (a *Ai) AutoMigrate(ctx context.Context) error { + return a.DB.AutoMigrate(new(schema.AiRequest)) +} + +func (a *Ai) Init(ctx context.Context) error { + if config.C.Storage.DB.AutoMigrate { + if err := a.AutoMigrate(ctx); err != nil { + return err + } + } + return nil +} + +func (a *Ai) RegisterV1Routers(ctx context.Context, v1 *gin.RouterGroup) error { + aiRequest := v1.Group("ai-requests") + { + aiRequest.GET("", a.AiRequestAPI.Query) + aiRequest.GET(":id", a.AiRequestAPI.Get) + aiRequest.POST("", a.AiRequestAPI.Create) + aiRequest.PUT(":id", a.AiRequestAPI.Update) + aiRequest.DELETE(":id", a.AiRequestAPI.Delete) + } + return nil +} + +func (a *Ai) Release(ctx context.Context) error { + return nil +} diff --git a/internal/mods/ai/schema/ai_request.go b/internal/mods/ai/schema/ai_request.go new file mode 100644 index 0000000..c24d9b6 --- /dev/null +++ b/internal/mods/ai/schema/ai_request.go @@ -0,0 +1,53 @@ +package schema + +import ( + "github.com/google/uuid" + "gitlab.guxuan.icu/jinshan_community/pkg/util" + "gorm.io/gorm" +) + +// Defining the `AiRequest` struct. +type AiRequest struct { + util.BaseModel +} + +func (c *AiRequest) BeforeCreate(tx *gorm.DB) (err error) { + if c.ID == "" { + c.ID = uuid.New().String() + } + return +} + +// Defining the query parameters for the `AiRequest` struct. +type AiRequestQueryParam struct { + util.PaginationParam +} + +// Defining the query options for the `AiRequest` struct. +type AiRequestQueryOptions struct { + util.QueryOptions +} + +// Defining the query result for the `AiRequest` struct. +type AiRequestQueryResult struct { + Data AiRequests + PageResult *util.PaginationResult +} + +// Defining the slice of `AiRequest` struct. +type AiRequests []*AiRequest + +// Defining the data structure for creating a `AiRequest` struct. +type AiRequestForm struct { + Msg string `json:"msg" ` +} + +// A validation function for the `AiRequestForm` struct. +func (a *AiRequestForm) Validate() error { + return nil +} + +// Convert `AiRequestForm` to `AiRequest` object. +func (a *AiRequestForm) FillTo(aiRequest *AiRequest) error { + return nil +} diff --git a/internal/mods/ai/wire.go b/internal/mods/ai/wire.go new file mode 100644 index 0000000..bec0530 --- /dev/null +++ b/internal/mods/ai/wire.go @@ -0,0 +1,15 @@ +package ai + +import ( + "github.com/google/wire" + "gitlab.guxuan.icu/jinshan_community/internal/mods/ai/api" + "gitlab.guxuan.icu/jinshan_community/internal/mods/ai/biz" + "gitlab.guxuan.icu/jinshan_community/internal/mods/ai/dal" +) + +var Set = wire.NewSet( + wire.Struct(new(Ai), "*"), + wire.Struct(new(dal.AiRequest), "*"), + wire.Struct(new(biz.AiRequest), "*"), + wire.Struct(new(api.AiRequest), "*"), +) diff --git a/internal/mods/app/api/app.api.go b/internal/mods/app/api/app.api.go new file mode 100644 index 0000000..2485ffd --- /dev/null +++ b/internal/mods/app/api/app.api.go @@ -0,0 +1,131 @@ +package api + +import ( + "github.com/gin-gonic/gin" + "gitlab.guxuan.icu/jinshan_community/internal/mods/app/biz" + "gitlab.guxuan.icu/jinshan_community/internal/mods/app/schema" + "gitlab.guxuan.icu/jinshan_community/pkg/util" +) + +// Defining the `App` api. +type App struct { + AppBIZ *biz.App +} + +// @Tags AppAPI +// @Security ApiKeyAuth +// @Summary Query app list +// @Param current query int true "pagination index" default(1) +// @Param pageSize query int true "pagination size" default(10) +// @Success 200 {object} util.ResponseResult{data=[]schema.App} +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/apps [get] +func (a *App) Query(c *gin.Context) { + ctx := c.Request.Context() + var params schema.AppQueryParam + if err := util.ParseQuery(c, ¶ms); err != nil { + util.ResError(c, err) + return + } + + result, err := a.AppBIZ.Query(ctx, params) + if err != nil { + util.ResError(c, err) + return + } + util.ResPage(c, result.Data, result.PageResult) +} + +// @Tags AppAPI +// @Security ApiKeyAuth +// @Summary Get app record by ID +// @Param id path string true "unique id" +// @Success 200 {object} util.ResponseResult{data=schema.App} +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/apps/{id} [get] +func (a *App) Get(c *gin.Context) { + ctx := c.Request.Context() + item, err := a.AppBIZ.Get(ctx, c.Param("id")) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, item) +} + +// @Tags AppAPI +// @Security ApiKeyAuth +// @Summary Create app record +// @Param body body schema.AppForm true "Request body" +// @Success 200 {object} util.ResponseResult{data=schema.App} +// @Failure 400 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/apps [post] +func (a *App) Create(c *gin.Context) { + ctx := c.Request.Context() + item := new(schema.AppForm) + 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.AppBIZ.Create(ctx, item) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, result) +} + +// @Tags AppAPI +// @Security ApiKeyAuth +// @Summary Update app record by ID +// @Param id path string true "unique id" +// @Param body body schema.AppForm 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/apps/{id} [put] +func (a *App) Update(c *gin.Context) { + ctx := c.Request.Context() + item := new(schema.AppForm) + 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.AppBIZ.Update(ctx, c.Param("id"), item) + if err != nil { + util.ResError(c, err) + return + } + util.ResOK(c) +} + +// @Tags AppAPI +// @Security ApiKeyAuth +// @Summary Delete app 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/apps/{id} [delete] +func (a *App) Delete(c *gin.Context) { + ctx := c.Request.Context() + err := a.AppBIZ.Delete(ctx, c.Param("id")) + if err != nil { + util.ResError(c, err) + return + } + util.ResOK(c) +} diff --git a/internal/mods/app/biz/app.biz.go b/internal/mods/app/biz/app.biz.go new file mode 100644 index 0000000..b4b615e --- /dev/null +++ b/internal/mods/app/biz/app.biz.go @@ -0,0 +1,104 @@ +package biz + +import ( + "context" + "time" + + "gitlab.guxuan.icu/jinshan_community/internal/mods/app/dal" + "gitlab.guxuan.icu/jinshan_community/internal/mods/app/schema" + "gitlab.guxuan.icu/jinshan_community/pkg/errors" + "gitlab.guxuan.icu/jinshan_community/pkg/util" +) + +// Defining the `App` business logic. +type App struct { + Trans *util.Trans + AppDAL *dal.App +} + +// Query apps from the data access object based on the provided parameters and options. +func (a *App) Query(ctx context.Context, params schema.AppQueryParam) (*schema.AppQueryResult, error) { + params.Pagination = true + + result, err := a.AppDAL.Query(ctx, params, schema.AppQueryOptions{ + QueryOptions: util.QueryOptions{ + OrderFields: []util.OrderByParam{ + {Field: "created_at", Direction: util.DESC}, + }, + }, + }) + if err != nil { + return nil, err + } + return result, nil +} + +// Get the specified app from the data access object. +func (a *App) Get(ctx context.Context, id string) (*schema.App, error) { + app, err := a.AppDAL.Get(ctx, id) + if err != nil { + return nil, err + } else if app == nil { + return nil, errors.NotFound("", "App not found") + } + return app, nil +} + +// Create a new app in the data access object. +func (a *App) Create(ctx context.Context, formItem *schema.AppForm) (*schema.App, error) { + app := &schema.App{} + + if err := formItem.FillTo(app); err != nil { + return nil, err + } + + err := a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.AppDAL.Create(ctx, app); err != nil { + return err + } + return nil + }) + if err != nil { + return nil, err + } + return app, nil +} + +// Update the specified app in the data access object. +func (a *App) Update(ctx context.Context, id string, formItem *schema.AppForm) error { + app, err := a.AppDAL.Get(ctx, id) + if err != nil { + return err + } else if app == nil { + return errors.NotFound("", "App not found") + } + + if err := formItem.FillTo(app); err != nil { + return err + } + app.UpdatedAt = time.Now() + + return a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.AppDAL.Update(ctx, app); err != nil { + return err + } + return nil + }) +} + +// Delete the specified app from the data access object. +func (a *App) Delete(ctx context.Context, id string) error { + exists, err := a.AppDAL.Exists(ctx, id) + if err != nil { + return err + } else if !exists { + return errors.NotFound("", "App not found") + } + + return a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.AppDAL.Delete(ctx, id); err != nil { + return err + } + return nil + }) +} diff --git a/internal/mods/app/dal/app.dal.go b/internal/mods/app/dal/app.dal.go new file mode 100644 index 0000000..dc96b6d --- /dev/null +++ b/internal/mods/app/dal/app.dal.go @@ -0,0 +1,83 @@ +package dal + +import ( + "context" + + "gitlab.guxuan.icu/jinshan_community/internal/mods/app/schema" + "gitlab.guxuan.icu/jinshan_community/pkg/errors" + "gitlab.guxuan.icu/jinshan_community/pkg/util" + "gorm.io/gorm" +) + +// Get app storage instance +func GetAppDB(ctx context.Context, defDB *gorm.DB) *gorm.DB { + return util.GetDB(ctx, defDB).Model(new(schema.App)) +} + +// Defining the `App` data access object. +type App struct { + DB *gorm.DB +} + +// Query apps from the database based on the provided parameters and options. +func (a *App) Query(ctx context.Context, params schema.AppQueryParam, opts ...schema.AppQueryOptions) (*schema.AppQueryResult, error) { + var opt schema.AppQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + db := GetAppDB(ctx, a.DB) + + var list schema.Apps + pageResult, err := util.WrapPageQuery(ctx, db, params.PaginationParam, opt.QueryOptions, &list) + if err != nil { + return nil, errors.WithStack(err) + } + + queryResult := &schema.AppQueryResult{ + PageResult: pageResult, + Data: list, + } + return queryResult, nil +} + +// Get the specified app from the database. +func (a *App) Get(ctx context.Context, id string, opts ...schema.AppQueryOptions) (*schema.App, error) { + var opt schema.AppQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + item := new(schema.App) + ok, err := util.FindOne(ctx, GetAppDB(ctx, a.DB).Where("id=?", id), opt.QueryOptions, item) + if err != nil { + return nil, errors.WithStack(err) + } else if !ok { + return nil, nil + } + return item, nil +} + +// Exists checks if the specified app exists in the database. +func (a *App) Exists(ctx context.Context, id string) (bool, error) { + ok, err := util.Exists(ctx, GetAppDB(ctx, a.DB).Where("id=?", id)) + return ok, errors.WithStack(err) +} + +// Create a new app. +func (a *App) Create(ctx context.Context, item *schema.App) error { + result := GetAppDB(ctx, a.DB).Create(item) + return errors.WithStack(result.Error) +} + +// Update the specified app in the database. +func (a *App) Update(ctx context.Context, item *schema.App) error { + result := GetAppDB(ctx, a.DB).Where("id=?", item.ID).Select("*").Omit("created_at").Updates(item) + return errors.WithStack(result.Error) +} + +// Delete the specified app from the database. +func (a *App) Delete(ctx context.Context, id string) error { + result := GetAppDB(ctx, a.DB).Where("id=?", id).Delete(new(schema.App)) + return errors.WithStack(result.Error) +} diff --git a/internal/mods/app/main.go b/internal/mods/app/main.go new file mode 100644 index 0000000..350010b --- /dev/null +++ b/internal/mods/app/main.go @@ -0,0 +1,46 @@ +package app + +import ( + "context" + + "gitlab.guxuan.icu/jinshan_community/internal/config" + + "github.com/gin-gonic/gin" + "gitlab.guxuan.icu/jinshan_community/internal/mods/app/api" + "gitlab.guxuan.icu/jinshan_community/internal/mods/app/schema" + "gorm.io/gorm" +) + +type App struct { + DB *gorm.DB + AppAPI *api.App +} + +func (a *App) AutoMigrate(ctx context.Context) error { + return a.DB.AutoMigrate(new(schema.App)) +} + +func (a *App) Init(ctx context.Context) error { + if config.C.Storage.DB.AutoMigrate { + if err := a.AutoMigrate(ctx); err != nil { + return err + } + } + return nil +} + +func (a *App) RegisterV1Routers(ctx context.Context, v1 *gin.RouterGroup) error { + app := v1.Group("apps") + { + app.GET("", a.AppAPI.Query) + app.GET(":id", a.AppAPI.Get) + app.POST("", a.AppAPI.Create) + app.PUT(":id", a.AppAPI.Update) + app.DELETE(":id", a.AppAPI.Delete) + } + return nil +} + +func (a *App) Release(ctx context.Context) error { + return nil +} diff --git a/internal/mods/app/schema/app.go b/internal/mods/app/schema/app.go new file mode 100644 index 0000000..77d1164 --- /dev/null +++ b/internal/mods/app/schema/app.go @@ -0,0 +1,47 @@ +package schema + +import ( + "time" + + "gitlab.guxuan.icu/jinshan_community/pkg/util" +) + +// Defining the `App` struct. +type App struct { + ID string `json:"id" gorm:"size:20;primaryKey;"` // Unique ID + CreatedAt time.Time `json:"created_at" gorm:"index;"` // Create time + UpdatedAt time.Time `json:"updated_at" gorm:"index;"` // Update time +} + +// Defining the query parameters for the `App` struct. +type AppQueryParam struct { + util.PaginationParam +} + +// Defining the query options for the `App` struct. +type AppQueryOptions struct { + util.QueryOptions +} + +// Defining the query result for the `App` struct. +type AppQueryResult struct { + Data Apps + PageResult *util.PaginationResult +} + +// Defining the slice of `App` struct. +type Apps []*App + +// Defining the data structure for creating a `App` struct. +type AppForm struct { +} + +// A validation function for the `AppForm` struct. +func (a *AppForm) Validate() error { + return nil +} + +// Convert `AppForm` to `App` object. +func (a *AppForm) FillTo(app *App) error { + return nil +} diff --git a/internal/mods/app/wire.go b/internal/mods/app/wire.go new file mode 100644 index 0000000..7cfd436 --- /dev/null +++ b/internal/mods/app/wire.go @@ -0,0 +1,15 @@ +package app + +import ( + "github.com/google/wire" + "gitlab.guxuan.icu/jinshan_community/internal/mods/app/api" + "gitlab.guxuan.icu/jinshan_community/internal/mods/app/biz" + "gitlab.guxuan.icu/jinshan_community/internal/mods/app/dal" +) + +var Set = wire.NewSet( + wire.Struct(new(App), "*"), + wire.Struct(new(dal.App), "*"), + wire.Struct(new(biz.App), "*"), + wire.Struct(new(api.App), "*"), +) diff --git a/internal/mods/common/api/banner.api.go b/internal/mods/common/api/banner.api.go new file mode 100644 index 0000000..78ce313 --- /dev/null +++ b/internal/mods/common/api/banner.api.go @@ -0,0 +1,131 @@ +package api + +import ( + "github.com/gin-gonic/gin" + "gitlab.guxuan.icu/jinshan_community/internal/mods/common/biz" + "gitlab.guxuan.icu/jinshan_community/internal/mods/common/schema" + "gitlab.guxuan.icu/jinshan_community/pkg/util" +) + +// Defining the `Banner` api. +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) +// @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 + } else if err := item.Validate(); err != nil { + util.ResError(c, err) + return + } + + result, err := a.BannerBIZ.Create(ctx, item) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, result) +} + +// @Tags 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 + } else if err := item.Validate(); 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/common/api/help.api.go b/internal/mods/common/api/help.api.go new file mode 100644 index 0000000..67139df --- /dev/null +++ b/internal/mods/common/api/help.api.go @@ -0,0 +1,131 @@ +package api + +import ( + "github.com/gin-gonic/gin" + "gitlab.guxuan.icu/jinshan_community/internal/mods/common/biz" + "gitlab.guxuan.icu/jinshan_community/internal/mods/common/schema" + "gitlab.guxuan.icu/jinshan_community/pkg/util" +) + +// Defining the `Help` api. +type Help struct { + HelpBIZ *biz.Help +} + +// @Tags HelpAPI +// @Security ApiKeyAuth +// @Summary Query help list +// @Param current query int true "pagination index" default(1) +// @Param pageSize query int true "pagination size" default(10) +// @Success 200 {object} util.ResponseResult{data=[]schema.Help} +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/helps [get] +func (a *Help) Query(c *gin.Context) { + ctx := c.Request.Context() + var params schema.HelpQueryParam + if err := util.ParseQuery(c, ¶ms); err != nil { + util.ResError(c, err) + return + } + + result, err := a.HelpBIZ.Query(ctx, params) + if err != nil { + util.ResError(c, err) + return + } + util.ResPage(c, result.Data, result.PageResult) +} + +// @Tags HelpAPI +// @Security ApiKeyAuth +// @Summary Get help record by ID +// @Param id path string true "unique id" +// @Success 200 {object} util.ResponseResult{data=schema.Help} +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/helps/{id} [get] +func (a *Help) Get(c *gin.Context) { + ctx := c.Request.Context() + item, err := a.HelpBIZ.Get(ctx, c.Param("id")) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, item) +} + +// @Tags HelpAPI +// @Security ApiKeyAuth +// @Summary Create help record +// @Param body body schema.HelpForm true "Request body" +// @Success 200 {object} util.ResponseResult{data=schema.Help} +// @Failure 400 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/helps [post] +func (a *Help) Create(c *gin.Context) { + ctx := c.Request.Context() + item := new(schema.HelpForm) + 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.HelpBIZ.Create(ctx, item) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, result) +} + +// @Tags HelpAPI +// @Security ApiKeyAuth +// @Summary Update help record by ID +// @Param id path string true "unique id" +// @Param body body schema.HelpForm 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/helps/{id} [put] +func (a *Help) Update(c *gin.Context) { + ctx := c.Request.Context() + item := new(schema.HelpForm) + 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.HelpBIZ.Update(ctx, c.Param("id"), item) + if err != nil { + util.ResError(c, err) + return + } + util.ResOK(c) +} + +// @Tags HelpAPI +// @Security ApiKeyAuth +// @Summary Delete help 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/helps/{id} [delete] +func (a *Help) Delete(c *gin.Context) { + ctx := c.Request.Context() + err := a.HelpBIZ.Delete(ctx, c.Param("id")) + if err != nil { + util.ResError(c, err) + return + } + util.ResOK(c) +} diff --git a/internal/mods/common/api/metting_room.api.go b/internal/mods/common/api/metting_room.api.go new file mode 100644 index 0000000..641d1ef --- /dev/null +++ b/internal/mods/common/api/metting_room.api.go @@ -0,0 +1,131 @@ +package api + +import ( + "github.com/gin-gonic/gin" + "gitlab.guxuan.icu/jinshan_community/internal/mods/common/biz" + "gitlab.guxuan.icu/jinshan_community/internal/mods/common/schema" + "gitlab.guxuan.icu/jinshan_community/pkg/util" +) + +// Defining the `MettingRoom` api. +type MettingRoom struct { + MettingRoomBIZ *biz.MettingRoom +} + +// @Tags MettingRoomAPI +// @Security ApiKeyAuth +// @Summary Query metting room list +// @Param current query int true "pagination index" default(1) +// @Param pageSize query int true "pagination size" default(10) +// @Success 200 {object} util.ResponseResult{data=[]schema.MettingRoom} +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/metting-rooms [get] +func (a *MettingRoom) Query(c *gin.Context) { + ctx := c.Request.Context() + var params schema.MettingRoomQueryParam + if err := util.ParseQuery(c, ¶ms); err != nil { + util.ResError(c, err) + return + } + + result, err := a.MettingRoomBIZ.Query(ctx, params) + if err != nil { + util.ResError(c, err) + return + } + util.ResPage(c, result.Data, result.PageResult) +} + +// @Tags MettingRoomAPI +// @Security ApiKeyAuth +// @Summary Get metting room record by ID +// @Param id path string true "unique id" +// @Success 200 {object} util.ResponseResult{data=schema.MettingRoom} +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/metting-rooms/{id} [get] +func (a *MettingRoom) Get(c *gin.Context) { + ctx := c.Request.Context() + item, err := a.MettingRoomBIZ.Get(ctx, c.Param("id")) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, item) +} + +// @Tags MettingRoomAPI +// @Security ApiKeyAuth +// @Summary Create metting room record +// @Param body body schema.MettingRoomForm true "Request body" +// @Success 200 {object} util.ResponseResult{data=schema.MettingRoom} +// @Failure 400 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/metting-rooms [post] +func (a *MettingRoom) Create(c *gin.Context) { + ctx := c.Request.Context() + item := new(schema.MettingRoomForm) + 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.MettingRoomBIZ.Create(ctx, item) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, result) +} + +// @Tags MettingRoomAPI +// @Security ApiKeyAuth +// @Summary Update metting room record by ID +// @Param id path string true "unique id" +// @Param body body schema.MettingRoomForm 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/metting-rooms/{id} [put] +func (a *MettingRoom) Update(c *gin.Context) { + ctx := c.Request.Context() + item := new(schema.MettingRoomForm) + 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.MettingRoomBIZ.Update(ctx, c.Param("id"), item) + if err != nil { + util.ResError(c, err) + return + } + util.ResOK(c) +} + +// @Tags MettingRoomAPI +// @Security ApiKeyAuth +// @Summary Delete metting room 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/metting-rooms/{id} [delete] +func (a *MettingRoom) Delete(c *gin.Context) { + ctx := c.Request.Context() + err := a.MettingRoomBIZ.Delete(ctx, c.Param("id")) + if err != nil { + util.ResError(c, err) + return + } + util.ResOK(c) +} diff --git a/internal/mods/common/api/metting_room_order.api.go b/internal/mods/common/api/metting_room_order.api.go new file mode 100644 index 0000000..756bdca --- /dev/null +++ b/internal/mods/common/api/metting_room_order.api.go @@ -0,0 +1,131 @@ +package api + +import ( + "github.com/gin-gonic/gin" + "gitlab.guxuan.icu/jinshan_community/internal/mods/common/biz" + "gitlab.guxuan.icu/jinshan_community/internal/mods/common/schema" + "gitlab.guxuan.icu/jinshan_community/pkg/util" +) + +// Defining the `MettingRoomOrder` api. +type MettingRoomOrder struct { + MettingRoomOrderBIZ *biz.MettingRoomOrder +} + +// @Tags MettingRoomOrderAPI +// @Security ApiKeyAuth +// @Summary Query metting room order list +// @Param current query int true "pagination index" default(1) +// @Param pageSize query int true "pagination size" default(10) +// @Success 200 {object} util.ResponseResult{data=[]schema.MettingRoomOrder} +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/metting-room-orders [get] +func (a *MettingRoomOrder) Query(c *gin.Context) { + ctx := c.Request.Context() + var params schema.MettingRoomOrderQueryParam + if err := util.ParseQuery(c, ¶ms); err != nil { + util.ResError(c, err) + return + } + + result, err := a.MettingRoomOrderBIZ.Query(ctx, params) + if err != nil { + util.ResError(c, err) + return + } + util.ResPage(c, result.Data, result.PageResult) +} + +// @Tags MettingRoomOrderAPI +// @Security ApiKeyAuth +// @Summary Get metting room order record by ID +// @Param id path string true "unique id" +// @Success 200 {object} util.ResponseResult{data=schema.MettingRoomOrder} +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/metting-room-orders/{id} [get] +func (a *MettingRoomOrder) Get(c *gin.Context) { + ctx := c.Request.Context() + item, err := a.MettingRoomOrderBIZ.Get(ctx, c.Param("id")) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, item) +} + +// @Tags MettingRoomOrderAPI +// @Security ApiKeyAuth +// @Summary Create metting room order record +// @Param body body schema.MettingRoomOrderForm true "Request body" +// @Success 200 {object} util.ResponseResult{data=schema.MettingRoomOrder} +// @Failure 400 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/metting-room-orders [post] +func (a *MettingRoomOrder) Create(c *gin.Context) { + ctx := c.Request.Context() + item := new(schema.MettingRoomOrderForm) + 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.MettingRoomOrderBIZ.Create(ctx, item) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, result) +} + +// @Tags MettingRoomOrderAPI +// @Security ApiKeyAuth +// @Summary Update metting room order record by ID +// @Param id path string true "unique id" +// @Param body body schema.MettingRoomOrderForm 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/metting-room-orders/{id} [put] +func (a *MettingRoomOrder) Update(c *gin.Context) { + ctx := c.Request.Context() + item := new(schema.MettingRoomOrderForm) + 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.MettingRoomOrderBIZ.Update(ctx, c.Param("id"), item) + if err != nil { + util.ResError(c, err) + return + } + util.ResOK(c) +} + +// @Tags MettingRoomOrderAPI +// @Security ApiKeyAuth +// @Summary Delete metting room order record by ID +// @Param id path string true "unique id" +// @Success 200 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/metting-room-orders/{id} [delete] +func (a *MettingRoomOrder) Delete(c *gin.Context) { + ctx := c.Request.Context() + err := a.MettingRoomOrderBIZ.Delete(ctx, c.Param("id")) + if err != nil { + util.ResError(c, err) + return + } + util.ResOK(c) +} diff --git a/internal/mods/common/api/notice.api.go b/internal/mods/common/api/notice.api.go new file mode 100644 index 0000000..450ca70 --- /dev/null +++ b/internal/mods/common/api/notice.api.go @@ -0,0 +1,131 @@ +package api + +import ( + "github.com/gin-gonic/gin" + "gitlab.guxuan.icu/jinshan_community/internal/mods/common/biz" + "gitlab.guxuan.icu/jinshan_community/internal/mods/common/schema" + "gitlab.guxuan.icu/jinshan_community/pkg/util" +) + +// Defining the `Notice` api. +type Notice struct { + NoticeBIZ *biz.Notice +} + +// @Tags NoticeAPI +// @Security ApiKeyAuth +// @Summary Query notice list +// @Param current query int true "pagination index" default(1) +// @Param pageSize query int true "pagination size" default(10) +// @Success 200 {object} util.ResponseResult{data=[]schema.Notice} +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/notices [get] +func (a *Notice) Query(c *gin.Context) { + ctx := c.Request.Context() + var params schema.NoticeQueryParam + if err := util.ParseQuery(c, ¶ms); err != nil { + util.ResError(c, err) + return + } + + result, err := a.NoticeBIZ.Query(ctx, params) + if err != nil { + util.ResError(c, err) + return + } + util.ResPage(c, result.Data, result.PageResult) +} + +// @Tags NoticeAPI +// @Security ApiKeyAuth +// @Summary Get notice record by ID +// @Param id path string true "unique id" +// @Success 200 {object} util.ResponseResult{data=schema.Notice} +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/notices/{id} [get] +func (a *Notice) Get(c *gin.Context) { + ctx := c.Request.Context() + item, err := a.NoticeBIZ.Get(ctx, c.Param("id")) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, item) +} + +// @Tags NoticeAPI +// @Security ApiKeyAuth +// @Summary Create notice record +// @Param body body schema.NoticeForm true "Request body" +// @Success 200 {object} util.ResponseResult{data=schema.Notice} +// @Failure 400 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/notices [post] +func (a *Notice) Create(c *gin.Context) { + ctx := c.Request.Context() + item := new(schema.NoticeForm) + if err := util.ParseJSON(c, item); err != nil { + util.ResError(c, err) + return + } else if err := item.Validate(); err != nil { + util.ResError(c, err) + return + } + + result, err := a.NoticeBIZ.Create(ctx, item) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, result) +} + +// @Tags NoticeAPI +// @Security ApiKeyAuth +// @Summary Update notice record by ID +// @Param id path string true "unique id" +// @Param body body schema.NoticeForm true "Request body" +// @Success 200 {object} util.ResponseResult +// @Failure 400 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/notices/{id} [put] +func (a *Notice) Update(c *gin.Context) { + ctx := c.Request.Context() + item := new(schema.NoticeForm) + if err := util.ParseJSON(c, item); err != nil { + util.ResError(c, err) + return + } else if err := item.Validate(); err != nil { + util.ResError(c, err) + return + } + + err := a.NoticeBIZ.Update(ctx, c.Param("id"), item) + if err != nil { + util.ResError(c, err) + return + } + util.ResOK(c) +} + +// @Tags NoticeAPI +// @Security ApiKeyAuth +// @Summary Delete notice record by ID +// @Param id path string true "unique id" +// @Success 200 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/notices/{id} [delete] +func (a *Notice) Delete(c *gin.Context) { + ctx := c.Request.Context() + err := a.NoticeBIZ.Delete(ctx, c.Param("id")) + if err != nil { + util.ResError(c, err) + return + } + util.ResOK(c) +} diff --git a/internal/mods/common/api/surrounding_service.api.go b/internal/mods/common/api/surrounding_service.api.go new file mode 100644 index 0000000..44475d9 --- /dev/null +++ b/internal/mods/common/api/surrounding_service.api.go @@ -0,0 +1,131 @@ +package api + +import ( + "github.com/gin-gonic/gin" + "gitlab.guxuan.icu/jinshan_community/internal/mods/common/biz" + "gitlab.guxuan.icu/jinshan_community/internal/mods/common/schema" + "gitlab.guxuan.icu/jinshan_community/pkg/util" +) + +// Defining the `SurroundingService` api. +type SurroundingService struct { + SurroundingServiceBIZ *biz.SurroundingService +} + +// @Tags SurroundingServiceAPI +// @Security ApiKeyAuth +// @Summary Query surrounding service list +// @Param current query int true "pagination index" default(1) +// @Param pageSize query int true "pagination size" default(10) +// @Success 200 {object} util.ResponseResult{data=[]schema.SurroundingService} +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/surrounding-services [get] +func (a *SurroundingService) Query(c *gin.Context) { + ctx := c.Request.Context() + var params schema.SurroundingServiceQueryParam + if err := util.ParseQuery(c, ¶ms); err != nil { + util.ResError(c, err) + return + } + + result, err := a.SurroundingServiceBIZ.Query(ctx, params) + if err != nil { + util.ResError(c, err) + return + } + util.ResPage(c, result.Data, result.PageResult) +} + +// @Tags SurroundingServiceAPI +// @Security ApiKeyAuth +// @Summary Get surrounding service record by ID +// @Param id path string true "unique id" +// @Success 200 {object} util.ResponseResult{data=schema.SurroundingService} +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/surrounding-services/{id} [get] +func (a *SurroundingService) Get(c *gin.Context) { + ctx := c.Request.Context() + item, err := a.SurroundingServiceBIZ.Get(ctx, c.Param("id")) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, item) +} + +// @Tags SurroundingServiceAPI +// @Security ApiKeyAuth +// @Summary Create surrounding service record +// @Param body body schema.SurroundingServiceForm true "Request body" +// @Success 200 {object} util.ResponseResult{data=schema.SurroundingService} +// @Failure 400 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/surrounding-services [post] +func (a *SurroundingService) Create(c *gin.Context) { + ctx := c.Request.Context() + item := new(schema.SurroundingServiceForm) + 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.SurroundingServiceBIZ.Create(ctx, item) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, result) +} + +// @Tags SurroundingServiceAPI +// @Security ApiKeyAuth +// @Summary Update surrounding service record by ID +// @Param id path string true "unique id" +// @Param body body schema.SurroundingServiceForm 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/surrounding-services/{id} [put] +func (a *SurroundingService) Update(c *gin.Context) { + ctx := c.Request.Context() + item := new(schema.SurroundingServiceForm) + 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.SurroundingServiceBIZ.Update(ctx, c.Param("id"), item) + if err != nil { + util.ResError(c, err) + return + } + util.ResOK(c) +} + +// @Tags SurroundingServiceAPI +// @Security ApiKeyAuth +// @Summary Delete surrounding service 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/surrounding-services/{id} [delete] +func (a *SurroundingService) Delete(c *gin.Context) { + ctx := c.Request.Context() + err := a.SurroundingServiceBIZ.Delete(ctx, c.Param("id")) + if err != nil { + util.ResError(c, err) + return + } + util.ResOK(c) +} diff --git a/internal/mods/common/api/surrounding_service_type.api.go b/internal/mods/common/api/surrounding_service_type.api.go new file mode 100644 index 0000000..d339034 --- /dev/null +++ b/internal/mods/common/api/surrounding_service_type.api.go @@ -0,0 +1,131 @@ +package api + +import ( + "github.com/gin-gonic/gin" + "gitlab.guxuan.icu/jinshan_community/internal/mods/common/biz" + "gitlab.guxuan.icu/jinshan_community/internal/mods/common/schema" + "gitlab.guxuan.icu/jinshan_community/pkg/util" +) + +// Defining the `SurroundingServiceType` api. +type SurroundingServiceType struct { + SurroundingServiceTypeBIZ *biz.SurroundingServiceType +} + +// @Tags SurroundingServiceTypeAPI +// @Security ApiKeyAuth +// @Summary Query surrounding service type list +// @Param current query int true "pagination index" default(1) +// @Param pageSize query int true "pagination size" default(10) +// @Success 200 {object} util.ResponseResult{data=[]schema.SurroundingServiceType} +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/surrounding-service-types [get] +func (a *SurroundingServiceType) Query(c *gin.Context) { + ctx := c.Request.Context() + var params schema.SurroundingServiceTypeQueryParam + if err := util.ParseQuery(c, ¶ms); err != nil { + util.ResError(c, err) + return + } + + result, err := a.SurroundingServiceTypeBIZ.Query(ctx, params) + if err != nil { + util.ResError(c, err) + return + } + util.ResPage(c, result.Data, result.PageResult) +} + +// @Tags SurroundingServiceTypeAPI +// @Security ApiKeyAuth +// @Summary Get surrounding service type record by ID +// @Param id path string true "unique id" +// @Success 200 {object} util.ResponseResult{data=schema.SurroundingServiceType} +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/surrounding-service-types/{id} [get] +func (a *SurroundingServiceType) Get(c *gin.Context) { + ctx := c.Request.Context() + item, err := a.SurroundingServiceTypeBIZ.Get(ctx, c.Param("id")) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, item) +} + +// @Tags SurroundingServiceTypeAPI +// @Security ApiKeyAuth +// @Summary Create surrounding service type record +// @Param body body schema.SurroundingServiceTypeForm true "Request body" +// @Success 200 {object} util.ResponseResult{data=schema.SurroundingServiceType} +// @Failure 400 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/surrounding-service-types [post] +func (a *SurroundingServiceType) Create(c *gin.Context) { + ctx := c.Request.Context() + item := new(schema.SurroundingServiceTypeForm) + 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.SurroundingServiceTypeBIZ.Create(ctx, item) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, result) +} + +// @Tags SurroundingServiceTypeAPI +// @Security ApiKeyAuth +// @Summary Update surrounding service type record by ID +// @Param id path string true "unique id" +// @Param body body schema.SurroundingServiceTypeForm 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/surrounding-service-types/{id} [put] +func (a *SurroundingServiceType) Update(c *gin.Context) { + ctx := c.Request.Context() + item := new(schema.SurroundingServiceTypeForm) + 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.SurroundingServiceTypeBIZ.Update(ctx, c.Param("id"), item) + if err != nil { + util.ResError(c, err) + return + } + util.ResOK(c) +} + +// @Tags SurroundingServiceTypeAPI +// @Security ApiKeyAuth +// @Summary Delete surrounding service type 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/surrounding-service-types/{id} [delete] +func (a *SurroundingServiceType) Delete(c *gin.Context) { + ctx := c.Request.Context() + err := a.SurroundingServiceTypeBIZ.Delete(ctx, c.Param("id")) + if err != nil { + util.ResError(c, err) + return + } + util.ResOK(c) +} diff --git a/internal/mods/common/api/web_site.api.go b/internal/mods/common/api/web_site.api.go new file mode 100644 index 0000000..eab34cf --- /dev/null +++ b/internal/mods/common/api/web_site.api.go @@ -0,0 +1,131 @@ +package api + +import ( + "github.com/gin-gonic/gin" + "gitlab.guxuan.icu/jinshan_community/internal/mods/common/biz" + "gitlab.guxuan.icu/jinshan_community/internal/mods/common/schema" + "gitlab.guxuan.icu/jinshan_community/pkg/util" +) + +// Defining the `WebSite` api. +type WebSite struct { + WebSiteBIZ *biz.WebSite +} + +// @Tags WebSiteAPI +// @Security ApiKeyAuth +// @Summary Query web site list +// @Param current query int true "pagination index" default(1) +// @Param pageSize query int true "pagination size" default(10) +// @Success 200 {object} util.ResponseResult{data=[]schema.WebSite} +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/web-sites [get] +func (a *WebSite) Query(c *gin.Context) { + ctx := c.Request.Context() + var params schema.WebSiteQueryParam + if err := util.ParseQuery(c, ¶ms); err != nil { + util.ResError(c, err) + return + } + + result, err := a.WebSiteBIZ.Query(ctx, params) + if err != nil { + util.ResError(c, err) + return + } + util.ResPage(c, result.Data, result.PageResult) +} + +// @Tags WebSiteAPI +// @Security ApiKeyAuth +// @Summary Get web site record by ID +// @Param id path string true "unique id" +// @Success 200 {object} util.ResponseResult{data=schema.WebSite} +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/web-sites/{id} [get] +func (a *WebSite) Get(c *gin.Context) { + ctx := c.Request.Context() + item, err := a.WebSiteBIZ.Get(ctx, c.Param("id")) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, item) +} + +// @Tags WebSiteAPI +// @Security ApiKeyAuth +// @Summary Create web site 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-sites [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 + } else if err := item.Validate(); 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 WebSiteAPI +// @Security ApiKeyAuth +// @Summary Update web site 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-sites/{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 + } else if err := item.Validate(); 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) +} + +// @Tags WebSiteAPI +// @Security ApiKeyAuth +// @Summary Delete web site 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/web-sites/{id} [delete] +func (a *WebSite) Delete(c *gin.Context) { + ctx := c.Request.Context() + err := a.WebSiteBIZ.Delete(ctx, c.Param("id")) + if err != nil { + util.ResError(c, err) + return + } + util.ResOK(c) +} diff --git a/internal/mods/common/api/work_order.api.go b/internal/mods/common/api/work_order.api.go new file mode 100644 index 0000000..d0792b3 --- /dev/null +++ b/internal/mods/common/api/work_order.api.go @@ -0,0 +1,131 @@ +package api + +import ( + "github.com/gin-gonic/gin" + "gitlab.guxuan.icu/jinshan_community/internal/mods/common/biz" + "gitlab.guxuan.icu/jinshan_community/internal/mods/common/schema" + "gitlab.guxuan.icu/jinshan_community/pkg/util" +) + +// Defining the `WorkOrder` api. +type WorkOrder struct { + WorkOrderBIZ *biz.WorkOrder +} + +// @Tags WorkOrderAPI +// @Security ApiKeyAuth +// @Summary Query work order list +// @Param current query int true "pagination index" default(1) +// @Param pageSize query int true "pagination size" default(10) +// @Success 200 {object} util.ResponseResult{data=[]schema.WorkOrder} +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/work-orders [get] +func (a *WorkOrder) Query(c *gin.Context) { + ctx := c.Request.Context() + var params schema.WorkOrderQueryParam + if err := util.ParseQuery(c, ¶ms); err != nil { + util.ResError(c, err) + return + } + + result, err := a.WorkOrderBIZ.Query(ctx, params) + if err != nil { + util.ResError(c, err) + return + } + util.ResPage(c, result.Data, result.PageResult) +} + +// @Tags WorkOrderAPI +// @Security ApiKeyAuth +// @Summary Get work order record by ID +// @Param id path string true "unique id" +// @Success 200 {object} util.ResponseResult{data=schema.WorkOrder} +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/work-orders/{id} [get] +func (a *WorkOrder) Get(c *gin.Context) { + ctx := c.Request.Context() + item, err := a.WorkOrderBIZ.Get(ctx, c.Param("id")) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, item) +} + +// @Tags WorkOrderAPI +// @Security ApiKeyAuth +// @Summary Create work order record +// @Param body body schema.WorkOrderForm true "Request body" +// @Success 200 {object} util.ResponseResult{data=schema.WorkOrder} +// @Failure 400 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/work-orders [post] +func (a *WorkOrder) Create(c *gin.Context) { + ctx := c.Request.Context() + item := new(schema.WorkOrderForm) + 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.WorkOrderBIZ.Create(ctx, item) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, result) +} + +// @Tags WorkOrderAPI +// @Security ApiKeyAuth +// @Summary Update work order record by ID +// @Param id path string true "unique id" +// @Param body body schema.WorkOrderForm 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/work-orders/{id} [put] +func (a *WorkOrder) Update(c *gin.Context) { + ctx := c.Request.Context() + item := new(schema.WorkOrderForm) + 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.WorkOrderBIZ.Update(ctx, c.Param("id"), item) + if err != nil { + util.ResError(c, err) + return + } + util.ResOK(c) +} + +// @Tags WorkOrderAPI +// @Security ApiKeyAuth +// @Summary Delete work order record by ID +// @Param id path string true "unique id" +// @Success 200 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/work-orders/{id} [delete] +func (a *WorkOrder) Delete(c *gin.Context) { + ctx := c.Request.Context() + err := a.WorkOrderBIZ.Delete(ctx, c.Param("id")) + if err != nil { + util.ResError(c, err) + return + } + util.ResOK(c) +} diff --git a/internal/mods/common/api/work_order_type.api.go b/internal/mods/common/api/work_order_type.api.go new file mode 100644 index 0000000..d39a05b --- /dev/null +++ b/internal/mods/common/api/work_order_type.api.go @@ -0,0 +1,131 @@ +package api + +import ( + "github.com/gin-gonic/gin" + "gitlab.guxuan.icu/jinshan_community/internal/mods/common/biz" + "gitlab.guxuan.icu/jinshan_community/internal/mods/common/schema" + "gitlab.guxuan.icu/jinshan_community/pkg/util" +) + +// Defining the `WorkOrderType` api. +type WorkOrderType struct { + WorkOrderTypeBIZ *biz.WorkOrderType +} + +// @Tags WorkOrderTypeAPI +// @Security ApiKeyAuth +// @Summary Query work order type list +// @Param current query int true "pagination index" default(1) +// @Param pageSize query int true "pagination size" default(10) +// @Success 200 {object} util.ResponseResult{data=[]schema.WorkOrderType} +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/work-order-types [get] +func (a *WorkOrderType) Query(c *gin.Context) { + ctx := c.Request.Context() + var params schema.WorkOrderTypeQueryParam + if err := util.ParseQuery(c, ¶ms); err != nil { + util.ResError(c, err) + return + } + + result, err := a.WorkOrderTypeBIZ.Query(ctx, params) + if err != nil { + util.ResError(c, err) + return + } + util.ResPage(c, result.Data, result.PageResult) +} + +// @Tags WorkOrderTypeAPI +// @Security ApiKeyAuth +// @Summary Get work order type record by ID +// @Param id path string true "unique id" +// @Success 200 {object} util.ResponseResult{data=schema.WorkOrderType} +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/work-order-types/{id} [get] +func (a *WorkOrderType) Get(c *gin.Context) { + ctx := c.Request.Context() + item, err := a.WorkOrderTypeBIZ.Get(ctx, c.Param("id")) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, item) +} + +// @Tags WorkOrderTypeAPI +// @Security ApiKeyAuth +// @Summary Create work order type record +// @Param body body schema.WorkOrderTypeForm true "Request body" +// @Success 200 {object} util.ResponseResult{data=schema.WorkOrderType} +// @Failure 400 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/work-order-types [post] +func (a *WorkOrderType) Create(c *gin.Context) { + ctx := c.Request.Context() + item := new(schema.WorkOrderTypeForm) + 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.WorkOrderTypeBIZ.Create(ctx, item) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, result) +} + +// @Tags WorkOrderTypeAPI +// @Security ApiKeyAuth +// @Summary Update work order type record by ID +// @Param id path string true "unique id" +// @Param body body schema.WorkOrderTypeForm 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/work-order-types/{id} [put] +func (a *WorkOrderType) Update(c *gin.Context) { + ctx := c.Request.Context() + item := new(schema.WorkOrderTypeForm) + 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.WorkOrderTypeBIZ.Update(ctx, c.Param("id"), item) + if err != nil { + util.ResError(c, err) + return + } + util.ResOK(c) +} + +// @Tags WorkOrderTypeAPI +// @Security ApiKeyAuth +// @Summary Delete work order type 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/work-order-types/{id} [delete] +func (a *WorkOrderType) Delete(c *gin.Context) { + ctx := c.Request.Context() + err := a.WorkOrderTypeBIZ.Delete(ctx, c.Param("id")) + if err != nil { + util.ResError(c, err) + return + } + util.ResOK(c) +} diff --git a/internal/mods/common/biz/banner.biz.go b/internal/mods/common/biz/banner.biz.go new file mode 100644 index 0000000..33de68c --- /dev/null +++ b/internal/mods/common/biz/banner.biz.go @@ -0,0 +1,104 @@ +package biz + +import ( + "context" + "time" + + "gitlab.guxuan.icu/jinshan_community/internal/mods/common/dal" + "gitlab.guxuan.icu/jinshan_community/internal/mods/common/schema" + "gitlab.guxuan.icu/jinshan_community/pkg/errors" + "gitlab.guxuan.icu/jinshan_community/pkg/util" +) + +// Defining the `Banner` business logic. +type Banner struct { + Trans *util.Trans + BannerDAL *dal.Banner +} + +// Query banners from the data access object based on the provided parameters and options. +func (a *Banner) Query(ctx context.Context, params schema.BannerQueryParam) (*schema.BannerQueryResult, error) { + params.Pagination = true + + result, err := a.BannerDAL.Query(ctx, params, schema.BannerQueryOptions{ + QueryOptions: util.QueryOptions{ + OrderFields: []util.OrderByParam{ + {Field: "created_at", Direction: util.DESC}, + }, + }, + }) + if err != nil { + return nil, err + } + return result, nil +} + +// Get the specified banner from the data access object. +func (a *Banner) Get(ctx context.Context, id 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 banner in the data access object. +func (a *Banner) Create(ctx context.Context, formItem *schema.BannerForm) (*schema.Banner, error) { + banner := &schema.Banner{} + + if err := formItem.FillTo(banner); err != nil { + return nil, err + } + + err := a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.BannerDAL.Create(ctx, banner); err != nil { + return err + } + return nil + }) + if err != nil { + return nil, err + } + return banner, nil +} + +// Update the specified banner in the data access object. +func (a *Banner) Update(ctx context.Context, id string, formItem *schema.BannerForm) error { + banner, err := a.BannerDAL.Get(ctx, id) + if err != nil { + return err + } else if banner == nil { + return errors.NotFound("", "Banner not found") + } + + if err := formItem.FillTo(banner); err != nil { + return err + } + banner.UpdatedAt = time.Now() + + return a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.BannerDAL.Update(ctx, banner); err != nil { + return err + } + return nil + }) +} + +// Delete the specified banner from the data access object. +func (a *Banner) Delete(ctx context.Context, id string) error { + exists, err := a.BannerDAL.Exists(ctx, id) + if err != nil { + return err + } else if !exists { + return errors.NotFound("", "Banner not found") + } + + return a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.BannerDAL.Delete(ctx, id); err != nil { + return err + } + return nil + }) +} diff --git a/internal/mods/common/biz/help.biz.go b/internal/mods/common/biz/help.biz.go new file mode 100644 index 0000000..cc9b83a --- /dev/null +++ b/internal/mods/common/biz/help.biz.go @@ -0,0 +1,104 @@ +package biz + +import ( + "context" + "time" + + "gitlab.guxuan.icu/jinshan_community/internal/mods/common/dal" + "gitlab.guxuan.icu/jinshan_community/internal/mods/common/schema" + "gitlab.guxuan.icu/jinshan_community/pkg/errors" + "gitlab.guxuan.icu/jinshan_community/pkg/util" +) + +// Defining the `Help` business logic. +type Help struct { + Trans *util.Trans + HelpDAL *dal.Help +} + +// Query helps from the data access object based on the provided parameters and options. +func (a *Help) Query(ctx context.Context, params schema.HelpQueryParam) (*schema.HelpQueryResult, error) { + params.Pagination = true + + result, err := a.HelpDAL.Query(ctx, params, schema.HelpQueryOptions{ + QueryOptions: util.QueryOptions{ + OrderFields: []util.OrderByParam{ + {Field: "created_at", Direction: util.DESC}, + }, + }, + }) + if err != nil { + return nil, err + } + return result, nil +} + +// Get the specified help from the data access object. +func (a *Help) Get(ctx context.Context, id string) (*schema.Help, error) { + help, err := a.HelpDAL.Get(ctx, id) + if err != nil { + return nil, err + } else if help == nil { + return nil, errors.NotFound("", "Help not found") + } + return help, nil +} + +// Create a new help in the data access object. +func (a *Help) Create(ctx context.Context, formItem *schema.HelpForm) (*schema.Help, error) { + help := &schema.Help{} + + if err := formItem.FillTo(help); err != nil { + return nil, err + } + + err := a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.HelpDAL.Create(ctx, help); err != nil { + return err + } + return nil + }) + if err != nil { + return nil, err + } + return help, nil +} + +// Update the specified help in the data access object. +func (a *Help) Update(ctx context.Context, id string, formItem *schema.HelpForm) error { + help, err := a.HelpDAL.Get(ctx, id) + if err != nil { + return err + } else if help == nil { + return errors.NotFound("", "Help not found") + } + + if err := formItem.FillTo(help); err != nil { + return err + } + help.UpdatedAt = time.Now() + + return a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.HelpDAL.Update(ctx, help); err != nil { + return err + } + return nil + }) +} + +// Delete the specified help from the data access object. +func (a *Help) Delete(ctx context.Context, id string) error { + exists, err := a.HelpDAL.Exists(ctx, id) + if err != nil { + return err + } else if !exists { + return errors.NotFound("", "Help not found") + } + + return a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.HelpDAL.Delete(ctx, id); err != nil { + return err + } + return nil + }) +} diff --git a/internal/mods/common/biz/metting_room.biz.go b/internal/mods/common/biz/metting_room.biz.go new file mode 100644 index 0000000..f07fe4b --- /dev/null +++ b/internal/mods/common/biz/metting_room.biz.go @@ -0,0 +1,104 @@ +package biz + +import ( + "context" + "time" + + "gitlab.guxuan.icu/jinshan_community/internal/mods/common/dal" + "gitlab.guxuan.icu/jinshan_community/internal/mods/common/schema" + "gitlab.guxuan.icu/jinshan_community/pkg/errors" + "gitlab.guxuan.icu/jinshan_community/pkg/util" +) + +// Defining the `MettingRoom` business logic. +type MettingRoom struct { + Trans *util.Trans + MettingRoomDAL *dal.MettingRoom +} + +// Query metting rooms from the data access object based on the provided parameters and options. +func (a *MettingRoom) Query(ctx context.Context, params schema.MettingRoomQueryParam) (*schema.MettingRoomQueryResult, error) { + params.Pagination = true + + result, err := a.MettingRoomDAL.Query(ctx, params, schema.MettingRoomQueryOptions{ + QueryOptions: util.QueryOptions{ + OrderFields: []util.OrderByParam{ + {Field: "created_at", Direction: util.DESC}, + }, + }, + }) + if err != nil { + return nil, err + } + return result, nil +} + +// Get the specified metting room from the data access object. +func (a *MettingRoom) Get(ctx context.Context, id string) (*schema.MettingRoom, error) { + mettingRoom, err := a.MettingRoomDAL.Get(ctx, id) + if err != nil { + return nil, err + } else if mettingRoom == nil { + return nil, errors.NotFound("", "Metting room not found") + } + return mettingRoom, nil +} + +// Create a new metting room in the data access object. +func (a *MettingRoom) Create(ctx context.Context, formItem *schema.MettingRoomForm) (*schema.MettingRoom, error) { + mettingRoom := &schema.MettingRoom{} + + if err := formItem.FillTo(mettingRoom); err != nil { + return nil, err + } + + err := a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.MettingRoomDAL.Create(ctx, mettingRoom); err != nil { + return err + } + return nil + }) + if err != nil { + return nil, err + } + return mettingRoom, nil +} + +// Update the specified metting room in the data access object. +func (a *MettingRoom) Update(ctx context.Context, id string, formItem *schema.MettingRoomForm) error { + mettingRoom, err := a.MettingRoomDAL.Get(ctx, id) + if err != nil { + return err + } else if mettingRoom == nil { + return errors.NotFound("", "Metting room not found") + } + + if err := formItem.FillTo(mettingRoom); err != nil { + return err + } + mettingRoom.UpdatedAt = time.Now() + + return a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.MettingRoomDAL.Update(ctx, mettingRoom); err != nil { + return err + } + return nil + }) +} + +// Delete the specified metting room from the data access object. +func (a *MettingRoom) Delete(ctx context.Context, id string) error { + exists, err := a.MettingRoomDAL.Exists(ctx, id) + if err != nil { + return err + } else if !exists { + return errors.NotFound("", "Metting room not found") + } + + return a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.MettingRoomDAL.Delete(ctx, id); err != nil { + return err + } + return nil + }) +} diff --git a/internal/mods/common/biz/metting_room_order.biz.go b/internal/mods/common/biz/metting_room_order.biz.go new file mode 100644 index 0000000..27f1102 --- /dev/null +++ b/internal/mods/common/biz/metting_room_order.biz.go @@ -0,0 +1,104 @@ +package biz + +import ( + "context" + "time" + + "gitlab.guxuan.icu/jinshan_community/internal/mods/common/dal" + "gitlab.guxuan.icu/jinshan_community/internal/mods/common/schema" + "gitlab.guxuan.icu/jinshan_community/pkg/errors" + "gitlab.guxuan.icu/jinshan_community/pkg/util" +) + +// Defining the `MettingRoomOrder` business logic. +type MettingRoomOrder struct { + Trans *util.Trans + MettingRoomOrderDAL *dal.MettingRoomOrder +} + +// Query metting room orders from the data access object based on the provided parameters and options. +func (a *MettingRoomOrder) Query(ctx context.Context, params schema.MettingRoomOrderQueryParam) (*schema.MettingRoomOrderQueryResult, error) { + params.Pagination = true + + result, err := a.MettingRoomOrderDAL.Query(ctx, params, schema.MettingRoomOrderQueryOptions{ + QueryOptions: util.QueryOptions{ + OrderFields: []util.OrderByParam{ + {Field: "created_at", Direction: util.DESC}, + }, + }, + }) + if err != nil { + return nil, err + } + return result, nil +} + +// Get the specified metting room order from the data access object. +func (a *MettingRoomOrder) Get(ctx context.Context, id string) (*schema.MettingRoomOrder, error) { + mettingRoomOrder, err := a.MettingRoomOrderDAL.Get(ctx, id) + if err != nil { + return nil, err + } else if mettingRoomOrder == nil { + return nil, errors.NotFound("", "Metting room order not found") + } + return mettingRoomOrder, nil +} + +// Create a new metting room order in the data access object. +func (a *MettingRoomOrder) Create(ctx context.Context, formItem *schema.MettingRoomOrderForm) (*schema.MettingRoomOrder, error) { + mettingRoomOrder := &schema.MettingRoomOrder{} + + if err := formItem.FillTo(mettingRoomOrder); err != nil { + return nil, err + } + + err := a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.MettingRoomOrderDAL.Create(ctx, mettingRoomOrder); err != nil { + return err + } + return nil + }) + if err != nil { + return nil, err + } + return mettingRoomOrder, nil +} + +// Update the specified metting room order in the data access object. +func (a *MettingRoomOrder) Update(ctx context.Context, id string, formItem *schema.MettingRoomOrderForm) error { + mettingRoomOrder, err := a.MettingRoomOrderDAL.Get(ctx, id) + if err != nil { + return err + } else if mettingRoomOrder == nil { + return errors.NotFound("", "Metting room order not found") + } + + if err := formItem.FillTo(mettingRoomOrder); err != nil { + return err + } + mettingRoomOrder.UpdatedAt = time.Now() + + return a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.MettingRoomOrderDAL.Update(ctx, mettingRoomOrder); err != nil { + return err + } + return nil + }) +} + +// Delete the specified metting room order from the data access object. +func (a *MettingRoomOrder) Delete(ctx context.Context, id string) error { + exists, err := a.MettingRoomOrderDAL.Exists(ctx, id) + if err != nil { + return err + } else if !exists { + return errors.NotFound("", "Metting room order not found") + } + + return a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.MettingRoomOrderDAL.Delete(ctx, id); err != nil { + return err + } + return nil + }) +} diff --git a/internal/mods/common/biz/notice.biz.go b/internal/mods/common/biz/notice.biz.go new file mode 100644 index 0000000..961c29a --- /dev/null +++ b/internal/mods/common/biz/notice.biz.go @@ -0,0 +1,104 @@ +package biz + +import ( + "context" + "time" + + "gitlab.guxuan.icu/jinshan_community/internal/mods/common/dal" + "gitlab.guxuan.icu/jinshan_community/internal/mods/common/schema" + "gitlab.guxuan.icu/jinshan_community/pkg/errors" + "gitlab.guxuan.icu/jinshan_community/pkg/util" +) + +// Defining the `Notice` business logic. +type Notice struct { + Trans *util.Trans + NoticeDAL *dal.Notice +} + +// Query notices from the data access object based on the provided parameters and options. +func (a *Notice) Query(ctx context.Context, params schema.NoticeQueryParam) (*schema.NoticeQueryResult, error) { + params.Pagination = true + + result, err := a.NoticeDAL.Query(ctx, params, schema.NoticeQueryOptions{ + QueryOptions: util.QueryOptions{ + OrderFields: []util.OrderByParam{ + {Field: "created_at", Direction: util.DESC}, + }, + }, + }) + if err != nil { + return nil, err + } + return result, nil +} + +// Get the specified notice from the data access object. +func (a *Notice) Get(ctx context.Context, id string) (*schema.Notice, error) { + notice, err := a.NoticeDAL.Get(ctx, id) + if err != nil { + return nil, err + } else if notice == nil { + return nil, errors.NotFound("", "Notice not found") + } + return notice, nil +} + +// Create a new notice in the data access object. +func (a *Notice) Create(ctx context.Context, formItem *schema.NoticeForm) (*schema.Notice, error) { + notice := &schema.Notice{} + + if err := formItem.FillTo(notice); err != nil { + return nil, err + } + + err := a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.NoticeDAL.Create(ctx, notice); err != nil { + return err + } + return nil + }) + if err != nil { + return nil, err + } + return notice, nil +} + +// Update the specified notice in the data access object. +func (a *Notice) Update(ctx context.Context, id string, formItem *schema.NoticeForm) error { + notice, err := a.NoticeDAL.Get(ctx, id) + if err != nil { + return err + } else if notice == nil { + return errors.NotFound("", "Notice not found") + } + + if err := formItem.FillTo(notice); err != nil { + return err + } + notice.UpdatedAt = time.Now() + + return a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.NoticeDAL.Update(ctx, notice); err != nil { + return err + } + return nil + }) +} + +// Delete the specified notice from the data access object. +func (a *Notice) Delete(ctx context.Context, id string) error { + exists, err := a.NoticeDAL.Exists(ctx, id) + if err != nil { + return err + } else if !exists { + return errors.NotFound("", "Notice not found") + } + + return a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.NoticeDAL.Delete(ctx, id); err != nil { + return err + } + return nil + }) +} diff --git a/internal/mods/common/biz/surrounding_service.biz.go b/internal/mods/common/biz/surrounding_service.biz.go new file mode 100644 index 0000000..bd1fd4e --- /dev/null +++ b/internal/mods/common/biz/surrounding_service.biz.go @@ -0,0 +1,104 @@ +package biz + +import ( + "context" + "time" + + "gitlab.guxuan.icu/jinshan_community/internal/mods/common/dal" + "gitlab.guxuan.icu/jinshan_community/internal/mods/common/schema" + "gitlab.guxuan.icu/jinshan_community/pkg/errors" + "gitlab.guxuan.icu/jinshan_community/pkg/util" +) + +// Defining the `SurroundingService` business logic. +type SurroundingService struct { + Trans *util.Trans + SurroundingServiceDAL *dal.SurroundingService +} + +// Query surrounding services from the data access object based on the provided parameters and options. +func (a *SurroundingService) Query(ctx context.Context, params schema.SurroundingServiceQueryParam) (*schema.SurroundingServiceQueryResult, error) { + params.Pagination = true + + result, err := a.SurroundingServiceDAL.Query(ctx, params, schema.SurroundingServiceQueryOptions{ + QueryOptions: util.QueryOptions{ + OrderFields: []util.OrderByParam{ + {Field: "created_at", Direction: util.DESC}, + }, + }, + }) + if err != nil { + return nil, err + } + return result, nil +} + +// Get the specified surrounding service from the data access object. +func (a *SurroundingService) Get(ctx context.Context, id string) (*schema.SurroundingService, error) { + surroundingService, err := a.SurroundingServiceDAL.Get(ctx, id) + if err != nil { + return nil, err + } else if surroundingService == nil { + return nil, errors.NotFound("", "Surrounding service not found") + } + return surroundingService, nil +} + +// Create a new surrounding service in the data access object. +func (a *SurroundingService) Create(ctx context.Context, formItem *schema.SurroundingServiceForm) (*schema.SurroundingService, error) { + surroundingService := &schema.SurroundingService{} + + if err := formItem.FillTo(surroundingService); err != nil { + return nil, err + } + + err := a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.SurroundingServiceDAL.Create(ctx, surroundingService); err != nil { + return err + } + return nil + }) + if err != nil { + return nil, err + } + return surroundingService, nil +} + +// Update the specified surrounding service in the data access object. +func (a *SurroundingService) Update(ctx context.Context, id string, formItem *schema.SurroundingServiceForm) error { + surroundingService, err := a.SurroundingServiceDAL.Get(ctx, id) + if err != nil { + return err + } else if surroundingService == nil { + return errors.NotFound("", "Surrounding service not found") + } + + if err := formItem.FillTo(surroundingService); err != nil { + return err + } + surroundingService.UpdatedAt = time.Now() + + return a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.SurroundingServiceDAL.Update(ctx, surroundingService); err != nil { + return err + } + return nil + }) +} + +// Delete the specified surrounding service from the data access object. +func (a *SurroundingService) Delete(ctx context.Context, id string) error { + exists, err := a.SurroundingServiceDAL.Exists(ctx, id) + if err != nil { + return err + } else if !exists { + return errors.NotFound("", "Surrounding service not found") + } + + return a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.SurroundingServiceDAL.Delete(ctx, id); err != nil { + return err + } + return nil + }) +} diff --git a/internal/mods/common/biz/surrounding_service_type.biz.go b/internal/mods/common/biz/surrounding_service_type.biz.go new file mode 100644 index 0000000..04d584f --- /dev/null +++ b/internal/mods/common/biz/surrounding_service_type.biz.go @@ -0,0 +1,104 @@ +package biz + +import ( + "context" + "time" + + "gitlab.guxuan.icu/jinshan_community/internal/mods/common/dal" + "gitlab.guxuan.icu/jinshan_community/internal/mods/common/schema" + "gitlab.guxuan.icu/jinshan_community/pkg/errors" + "gitlab.guxuan.icu/jinshan_community/pkg/util" +) + +// Defining the `SurroundingServiceType` business logic. +type SurroundingServiceType struct { + Trans *util.Trans + SurroundingServiceTypeDAL *dal.SurroundingServiceType +} + +// Query surrounding service types from the data access object based on the provided parameters and options. +func (a *SurroundingServiceType) Query(ctx context.Context, params schema.SurroundingServiceTypeQueryParam) (*schema.SurroundingServiceTypeQueryResult, error) { + params.Pagination = true + + result, err := a.SurroundingServiceTypeDAL.Query(ctx, params, schema.SurroundingServiceTypeQueryOptions{ + QueryOptions: util.QueryOptions{ + OrderFields: []util.OrderByParam{ + {Field: "created_at", Direction: util.DESC}, + }, + }, + }) + if err != nil { + return nil, err + } + return result, nil +} + +// Get the specified surrounding service type from the data access object. +func (a *SurroundingServiceType) Get(ctx context.Context, id string) (*schema.SurroundingServiceType, error) { + surroundingServiceType, err := a.SurroundingServiceTypeDAL.Get(ctx, id) + if err != nil { + return nil, err + } else if surroundingServiceType == nil { + return nil, errors.NotFound("", "Surrounding service type not found") + } + return surroundingServiceType, nil +} + +// Create a new surrounding service type in the data access object. +func (a *SurroundingServiceType) Create(ctx context.Context, formItem *schema.SurroundingServiceTypeForm) (*schema.SurroundingServiceType, error) { + surroundingServiceType := &schema.SurroundingServiceType{} + + if err := formItem.FillTo(surroundingServiceType); err != nil { + return nil, err + } + + err := a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.SurroundingServiceTypeDAL.Create(ctx, surroundingServiceType); err != nil { + return err + } + return nil + }) + if err != nil { + return nil, err + } + return surroundingServiceType, nil +} + +// Update the specified surrounding service type in the data access object. +func (a *SurroundingServiceType) Update(ctx context.Context, id string, formItem *schema.SurroundingServiceTypeForm) error { + surroundingServiceType, err := a.SurroundingServiceTypeDAL.Get(ctx, id) + if err != nil { + return err + } else if surroundingServiceType == nil { + return errors.NotFound("", "Surrounding service type not found") + } + + if err := formItem.FillTo(surroundingServiceType); err != nil { + return err + } + surroundingServiceType.UpdatedAt = time.Now() + + return a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.SurroundingServiceTypeDAL.Update(ctx, surroundingServiceType); err != nil { + return err + } + return nil + }) +} + +// Delete the specified surrounding service type from the data access object. +func (a *SurroundingServiceType) Delete(ctx context.Context, id string) error { + exists, err := a.SurroundingServiceTypeDAL.Exists(ctx, id) + if err != nil { + return err + } else if !exists { + return errors.NotFound("", "Surrounding service type not found") + } + + return a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.SurroundingServiceTypeDAL.Delete(ctx, id); err != nil { + return err + } + return nil + }) +} diff --git a/internal/mods/common/biz/web_site.biz.go b/internal/mods/common/biz/web_site.biz.go new file mode 100644 index 0000000..d457c3d --- /dev/null +++ b/internal/mods/common/biz/web_site.biz.go @@ -0,0 +1,104 @@ +package biz + +import ( + "context" + "time" + + "gitlab.guxuan.icu/jinshan_community/internal/mods/common/dal" + "gitlab.guxuan.icu/jinshan_community/internal/mods/common/schema" + "gitlab.guxuan.icu/jinshan_community/pkg/errors" + "gitlab.guxuan.icu/jinshan_community/pkg/util" +) + +// Defining the `WebSite` business logic. +type WebSite struct { + Trans *util.Trans + WebSiteDAL *dal.WebSite +} + +// Query web sites from the data access object based on the provided parameters and options. +func (a *WebSite) Query(ctx context.Context, params schema.WebSiteQueryParam) (*schema.WebSiteQueryResult, error) { + params.Pagination = true + + result, err := a.WebSiteDAL.Query(ctx, params, schema.WebSiteQueryOptions{ + QueryOptions: util.QueryOptions{ + OrderFields: []util.OrderByParam{ + {Field: "created_at", Direction: util.DESC}, + }, + }, + }) + if err != nil { + return nil, err + } + return result, nil +} + +// Get the specified web site from the data access object. +func (a *WebSite) Get(ctx context.Context, id string) (*schema.WebSite, error) { + webSite, err := a.WebSiteDAL.Get(ctx, id) + if err != nil { + return nil, err + } else if webSite == nil { + return nil, errors.NotFound("", "Web site not found") + } + return webSite, nil +} + +// Create a new web site in the data access object. +func (a *WebSite) Create(ctx context.Context, formItem *schema.WebSiteForm) (*schema.WebSite, error) { + webSite := &schema.WebSite{} + + if err := formItem.FillTo(webSite); err != nil { + return nil, err + } + + err := a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.WebSiteDAL.Create(ctx, webSite); err != nil { + return err + } + return nil + }) + if err != nil { + return nil, err + } + return webSite, nil +} + +// Update the specified web site in the data access object. +func (a *WebSite) Update(ctx context.Context, id string, formItem *schema.WebSiteForm) error { + webSite, err := a.WebSiteDAL.Get(ctx, id) + if err != nil { + return err + } else if webSite == nil { + return errors.NotFound("", "Web site not found") + } + + if err := formItem.FillTo(webSite); err != nil { + return err + } + webSite.UpdatedAt = time.Now() + + return a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.WebSiteDAL.Update(ctx, webSite); err != nil { + return err + } + return nil + }) +} + +// Delete the specified web site from the data access object. +func (a *WebSite) Delete(ctx context.Context, id string) error { + exists, err := a.WebSiteDAL.Exists(ctx, id) + if err != nil { + return err + } else if !exists { + return errors.NotFound("", "Web site not found") + } + + return a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.WebSiteDAL.Delete(ctx, id); err != nil { + return err + } + return nil + }) +} diff --git a/internal/mods/common/biz/work_order.biz.go b/internal/mods/common/biz/work_order.biz.go new file mode 100644 index 0000000..a3cb2f5 --- /dev/null +++ b/internal/mods/common/biz/work_order.biz.go @@ -0,0 +1,104 @@ +package biz + +import ( + "context" + "time" + + "gitlab.guxuan.icu/jinshan_community/internal/mods/common/dal" + "gitlab.guxuan.icu/jinshan_community/internal/mods/common/schema" + "gitlab.guxuan.icu/jinshan_community/pkg/errors" + "gitlab.guxuan.icu/jinshan_community/pkg/util" +) + +// Defining the `WorkOrder` business logic. +type WorkOrder struct { + Trans *util.Trans + WorkOrderDAL *dal.WorkOrder +} + +// Query work orders from the data access object based on the provided parameters and options. +func (a *WorkOrder) Query(ctx context.Context, params schema.WorkOrderQueryParam) (*schema.WorkOrderQueryResult, error) { + params.Pagination = true + + result, err := a.WorkOrderDAL.Query(ctx, params, schema.WorkOrderQueryOptions{ + QueryOptions: util.QueryOptions{ + OrderFields: []util.OrderByParam{ + {Field: "created_at", Direction: util.DESC}, + }, + }, + }) + if err != nil { + return nil, err + } + return result, nil +} + +// Get the specified work order from the data access object. +func (a *WorkOrder) Get(ctx context.Context, id string) (*schema.WorkOrder, error) { + workOrder, err := a.WorkOrderDAL.Get(ctx, id) + if err != nil { + return nil, err + } else if workOrder == nil { + return nil, errors.NotFound("", "Work order not found") + } + return workOrder, nil +} + +// Create a new work order in the data access object. +func (a *WorkOrder) Create(ctx context.Context, formItem *schema.WorkOrderForm) (*schema.WorkOrder, error) { + workOrder := &schema.WorkOrder{} + + if err := formItem.FillTo(workOrder); err != nil { + return nil, err + } + + err := a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.WorkOrderDAL.Create(ctx, workOrder); err != nil { + return err + } + return nil + }) + if err != nil { + return nil, err + } + return workOrder, nil +} + +// Update the specified work order in the data access object. +func (a *WorkOrder) Update(ctx context.Context, id string, formItem *schema.WorkOrderForm) error { + workOrder, err := a.WorkOrderDAL.Get(ctx, id) + if err != nil { + return err + } else if workOrder == nil { + return errors.NotFound("", "Work order not found") + } + + if err := formItem.FillTo(workOrder); err != nil { + return err + } + workOrder.UpdatedAt = time.Now() + + return a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.WorkOrderDAL.Update(ctx, workOrder); err != nil { + return err + } + return nil + }) +} + +// Delete the specified work order from the data access object. +func (a *WorkOrder) Delete(ctx context.Context, id string) error { + exists, err := a.WorkOrderDAL.Exists(ctx, id) + if err != nil { + return err + } else if !exists { + return errors.NotFound("", "Work order not found") + } + + return a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.WorkOrderDAL.Delete(ctx, id); err != nil { + return err + } + return nil + }) +} diff --git a/internal/mods/common/biz/work_order_type.biz.go b/internal/mods/common/biz/work_order_type.biz.go new file mode 100644 index 0000000..fe376dd --- /dev/null +++ b/internal/mods/common/biz/work_order_type.biz.go @@ -0,0 +1,104 @@ +package biz + +import ( + "context" + "time" + + "gitlab.guxuan.icu/jinshan_community/internal/mods/common/dal" + "gitlab.guxuan.icu/jinshan_community/internal/mods/common/schema" + "gitlab.guxuan.icu/jinshan_community/pkg/errors" + "gitlab.guxuan.icu/jinshan_community/pkg/util" +) + +// Defining the `WorkOrderType` business logic. +type WorkOrderType struct { + Trans *util.Trans + WorkOrderTypeDAL *dal.WorkOrderType +} + +// Query work order types from the data access object based on the provided parameters and options. +func (a *WorkOrderType) Query(ctx context.Context, params schema.WorkOrderTypeQueryParam) (*schema.WorkOrderTypeQueryResult, error) { + params.Pagination = true + + result, err := a.WorkOrderTypeDAL.Query(ctx, params, schema.WorkOrderTypeQueryOptions{ + QueryOptions: util.QueryOptions{ + OrderFields: []util.OrderByParam{ + {Field: "created_at", Direction: util.DESC}, + }, + }, + }) + if err != nil { + return nil, err + } + return result, nil +} + +// Get the specified work order type from the data access object. +func (a *WorkOrderType) Get(ctx context.Context, id string) (*schema.WorkOrderType, error) { + workOrderType, err := a.WorkOrderTypeDAL.Get(ctx, id) + if err != nil { + return nil, err + } else if workOrderType == nil { + return nil, errors.NotFound("", "Work order type not found") + } + return workOrderType, nil +} + +// Create a new work order type in the data access object. +func (a *WorkOrderType) Create(ctx context.Context, formItem *schema.WorkOrderTypeForm) (*schema.WorkOrderType, error) { + workOrderType := &schema.WorkOrderType{} + + if err := formItem.FillTo(workOrderType); err != nil { + return nil, err + } + + err := a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.WorkOrderTypeDAL.Create(ctx, workOrderType); err != nil { + return err + } + return nil + }) + if err != nil { + return nil, err + } + return workOrderType, nil +} + +// Update the specified work order type in the data access object. +func (a *WorkOrderType) Update(ctx context.Context, id string, formItem *schema.WorkOrderTypeForm) error { + workOrderType, err := a.WorkOrderTypeDAL.Get(ctx, id) + if err != nil { + return err + } else if workOrderType == nil { + return errors.NotFound("", "Work order type not found") + } + + if err := formItem.FillTo(workOrderType); err != nil { + return err + } + workOrderType.UpdatedAt = time.Now() + + return a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.WorkOrderTypeDAL.Update(ctx, workOrderType); err != nil { + return err + } + return nil + }) +} + +// Delete the specified work order type from the data access object. +func (a *WorkOrderType) Delete(ctx context.Context, id string) error { + exists, err := a.WorkOrderTypeDAL.Exists(ctx, id) + if err != nil { + return err + } else if !exists { + return errors.NotFound("", "Work order type not found") + } + + return a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.WorkOrderTypeDAL.Delete(ctx, id); err != nil { + return err + } + return nil + }) +} diff --git a/internal/mods/common/dal/banner.dal.go b/internal/mods/common/dal/banner.dal.go new file mode 100644 index 0000000..287b27c --- /dev/null +++ b/internal/mods/common/dal/banner.dal.go @@ -0,0 +1,83 @@ +package dal + +import ( + "context" + + "gitlab.guxuan.icu/jinshan_community/internal/mods/common/schema" + "gitlab.guxuan.icu/jinshan_community/pkg/errors" + "gitlab.guxuan.icu/jinshan_community/pkg/util" + "gorm.io/gorm" +) + +// Get banner storage instance +func GetBannerDB(ctx context.Context, defDB *gorm.DB) *gorm.DB { + return util.GetDB(ctx, defDB).Model(new(schema.Banner)) +} + +// Defining the `Banner` data access object. +type Banner struct { + DB *gorm.DB +} + +// Query banners from the database based on the provided parameters and options. +func (a *Banner) Query(ctx context.Context, params schema.BannerQueryParam, opts ...schema.BannerQueryOptions) (*schema.BannerQueryResult, error) { + var opt schema.BannerQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + db := GetBannerDB(ctx, a.DB) + + var list schema.Banners + pageResult, err := util.WrapPageQuery(ctx, db, params.PaginationParam, opt.QueryOptions, &list) + if err != nil { + return nil, errors.WithStack(err) + } + + queryResult := &schema.BannerQueryResult{ + PageResult: pageResult, + Data: list, + } + return queryResult, nil +} + +// Get the specified banner from the database. +func (a *Banner) Get(ctx context.Context, id 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 +} + +// Exists checks if the specified banner 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 banner. +func (a *Banner) Create(ctx context.Context, item *schema.Banner) error { + result := GetBannerDB(ctx, a.DB).Create(item) + return errors.WithStack(result.Error) +} + +// Update the specified banner in the database. +func (a *Banner) Update(ctx context.Context, item *schema.Banner) error { + result := GetBannerDB(ctx, a.DB).Where("id=?", item.ID).Select("*").Omit("created_at").Updates(item) + return errors.WithStack(result.Error) +} + +// Delete the specified banner from the database. +func (a *Banner) Delete(ctx context.Context, id string) error { + result := GetBannerDB(ctx, a.DB).Where("id=?", id).Delete(new(schema.Banner)) + return errors.WithStack(result.Error) +} diff --git a/internal/mods/common/dal/help.dal.go b/internal/mods/common/dal/help.dal.go new file mode 100644 index 0000000..0fa5ade --- /dev/null +++ b/internal/mods/common/dal/help.dal.go @@ -0,0 +1,83 @@ +package dal + +import ( + "context" + + "gitlab.guxuan.icu/jinshan_community/internal/mods/common/schema" + "gitlab.guxuan.icu/jinshan_community/pkg/errors" + "gitlab.guxuan.icu/jinshan_community/pkg/util" + "gorm.io/gorm" +) + +// Get help storage instance +func GetHelpDB(ctx context.Context, defDB *gorm.DB) *gorm.DB { + return util.GetDB(ctx, defDB).Model(new(schema.Help)) +} + +// Defining the `Help` data access object. +type Help struct { + DB *gorm.DB +} + +// Query helps from the database based on the provided parameters and options. +func (a *Help) Query(ctx context.Context, params schema.HelpQueryParam, opts ...schema.HelpQueryOptions) (*schema.HelpQueryResult, error) { + var opt schema.HelpQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + db := GetHelpDB(ctx, a.DB) + + var list schema.Helps + pageResult, err := util.WrapPageQuery(ctx, db, params.PaginationParam, opt.QueryOptions, &list) + if err != nil { + return nil, errors.WithStack(err) + } + + queryResult := &schema.HelpQueryResult{ + PageResult: pageResult, + Data: list, + } + return queryResult, nil +} + +// Get the specified help from the database. +func (a *Help) Get(ctx context.Context, id string, opts ...schema.HelpQueryOptions) (*schema.Help, error) { + var opt schema.HelpQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + item := new(schema.Help) + ok, err := util.FindOne(ctx, GetHelpDB(ctx, a.DB).Where("id=?", id), opt.QueryOptions, item) + if err != nil { + return nil, errors.WithStack(err) + } else if !ok { + return nil, nil + } + return item, nil +} + +// Exists checks if the specified help exists in the database. +func (a *Help) Exists(ctx context.Context, id string) (bool, error) { + ok, err := util.Exists(ctx, GetHelpDB(ctx, a.DB).Where("id=?", id)) + return ok, errors.WithStack(err) +} + +// Create a new help. +func (a *Help) Create(ctx context.Context, item *schema.Help) error { + result := GetHelpDB(ctx, a.DB).Create(item) + return errors.WithStack(result.Error) +} + +// Update the specified help in the database. +func (a *Help) Update(ctx context.Context, item *schema.Help) error { + result := GetHelpDB(ctx, a.DB).Where("id=?", item.ID).Select("*").Omit("created_at").Updates(item) + return errors.WithStack(result.Error) +} + +// Delete the specified help from the database. +func (a *Help) Delete(ctx context.Context, id string) error { + result := GetHelpDB(ctx, a.DB).Where("id=?", id).Delete(new(schema.Help)) + return errors.WithStack(result.Error) +} diff --git a/internal/mods/common/dal/metting_room.dal.go b/internal/mods/common/dal/metting_room.dal.go new file mode 100644 index 0000000..8ca80c6 --- /dev/null +++ b/internal/mods/common/dal/metting_room.dal.go @@ -0,0 +1,83 @@ +package dal + +import ( + "context" + + "gitlab.guxuan.icu/jinshan_community/internal/mods/common/schema" + "gitlab.guxuan.icu/jinshan_community/pkg/errors" + "gitlab.guxuan.icu/jinshan_community/pkg/util" + "gorm.io/gorm" +) + +// Get metting room storage instance +func GetMettingRoomDB(ctx context.Context, defDB *gorm.DB) *gorm.DB { + return util.GetDB(ctx, defDB).Model(new(schema.MettingRoom)) +} + +// Defining the `MettingRoom` data access object. +type MettingRoom struct { + DB *gorm.DB +} + +// Query metting rooms from the database based on the provided parameters and options. +func (a *MettingRoom) Query(ctx context.Context, params schema.MettingRoomQueryParam, opts ...schema.MettingRoomQueryOptions) (*schema.MettingRoomQueryResult, error) { + var opt schema.MettingRoomQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + db := GetMettingRoomDB(ctx, a.DB) + + var list schema.MettingRooms + pageResult, err := util.WrapPageQuery(ctx, db, params.PaginationParam, opt.QueryOptions, &list) + if err != nil { + return nil, errors.WithStack(err) + } + + queryResult := &schema.MettingRoomQueryResult{ + PageResult: pageResult, + Data: list, + } + return queryResult, nil +} + +// Get the specified metting room from the database. +func (a *MettingRoom) Get(ctx context.Context, id string, opts ...schema.MettingRoomQueryOptions) (*schema.MettingRoom, error) { + var opt schema.MettingRoomQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + item := new(schema.MettingRoom) + ok, err := util.FindOne(ctx, GetMettingRoomDB(ctx, a.DB).Where("id=?", id), opt.QueryOptions, item) + if err != nil { + return nil, errors.WithStack(err) + } else if !ok { + return nil, nil + } + return item, nil +} + +// Exists checks if the specified metting room exists in the database. +func (a *MettingRoom) Exists(ctx context.Context, id string) (bool, error) { + ok, err := util.Exists(ctx, GetMettingRoomDB(ctx, a.DB).Where("id=?", id)) + return ok, errors.WithStack(err) +} + +// Create a new metting room. +func (a *MettingRoom) Create(ctx context.Context, item *schema.MettingRoom) error { + result := GetMettingRoomDB(ctx, a.DB).Create(item) + return errors.WithStack(result.Error) +} + +// Update the specified metting room in the database. +func (a *MettingRoom) Update(ctx context.Context, item *schema.MettingRoom) error { + result := GetMettingRoomDB(ctx, a.DB).Where("id=?", item.ID).Select("*").Omit("created_at").Updates(item) + return errors.WithStack(result.Error) +} + +// Delete the specified metting room from the database. +func (a *MettingRoom) Delete(ctx context.Context, id string) error { + result := GetMettingRoomDB(ctx, a.DB).Where("id=?", id).Delete(new(schema.MettingRoom)) + return errors.WithStack(result.Error) +} diff --git a/internal/mods/common/dal/metting_room_order.dal.go b/internal/mods/common/dal/metting_room_order.dal.go new file mode 100644 index 0000000..f9b95c4 --- /dev/null +++ b/internal/mods/common/dal/metting_room_order.dal.go @@ -0,0 +1,83 @@ +package dal + +import ( + "context" + + "gitlab.guxuan.icu/jinshan_community/internal/mods/common/schema" + "gitlab.guxuan.icu/jinshan_community/pkg/errors" + "gitlab.guxuan.icu/jinshan_community/pkg/util" + "gorm.io/gorm" +) + +// Get metting room order storage instance +func GetMettingRoomOrderDB(ctx context.Context, defDB *gorm.DB) *gorm.DB { + return util.GetDB(ctx, defDB).Model(new(schema.MettingRoomOrder)) +} + +// Defining the `MettingRoomOrder` data access object. +type MettingRoomOrder struct { + DB *gorm.DB +} + +// Query metting room orders from the database based on the provided parameters and options. +func (a *MettingRoomOrder) Query(ctx context.Context, params schema.MettingRoomOrderQueryParam, opts ...schema.MettingRoomOrderQueryOptions) (*schema.MettingRoomOrderQueryResult, error) { + var opt schema.MettingRoomOrderQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + db := GetMettingRoomOrderDB(ctx, a.DB) + + var list schema.MettingRoomOrders + pageResult, err := util.WrapPageQuery(ctx, db, params.PaginationParam, opt.QueryOptions, &list) + if err != nil { + return nil, errors.WithStack(err) + } + + queryResult := &schema.MettingRoomOrderQueryResult{ + PageResult: pageResult, + Data: list, + } + return queryResult, nil +} + +// Get the specified metting room order from the database. +func (a *MettingRoomOrder) Get(ctx context.Context, id string, opts ...schema.MettingRoomOrderQueryOptions) (*schema.MettingRoomOrder, error) { + var opt schema.MettingRoomOrderQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + item := new(schema.MettingRoomOrder) + ok, err := util.FindOne(ctx, GetMettingRoomOrderDB(ctx, a.DB).Where("id=?", id), opt.QueryOptions, item) + if err != nil { + return nil, errors.WithStack(err) + } else if !ok { + return nil, nil + } + return item, nil +} + +// Exists checks if the specified metting room order exists in the database. +func (a *MettingRoomOrder) Exists(ctx context.Context, id string) (bool, error) { + ok, err := util.Exists(ctx, GetMettingRoomOrderDB(ctx, a.DB).Where("id=?", id)) + return ok, errors.WithStack(err) +} + +// Create a new metting room order. +func (a *MettingRoomOrder) Create(ctx context.Context, item *schema.MettingRoomOrder) error { + result := GetMettingRoomOrderDB(ctx, a.DB).Create(item) + return errors.WithStack(result.Error) +} + +// Update the specified metting room order in the database. +func (a *MettingRoomOrder) Update(ctx context.Context, item *schema.MettingRoomOrder) error { + result := GetMettingRoomOrderDB(ctx, a.DB).Where("id=?", item.ID).Select("*").Omit("created_at").Updates(item) + return errors.WithStack(result.Error) +} + +// Delete the specified metting room order from the database. +func (a *MettingRoomOrder) Delete(ctx context.Context, id string) error { + result := GetMettingRoomOrderDB(ctx, a.DB).Where("id=?", id).Delete(new(schema.MettingRoomOrder)) + return errors.WithStack(result.Error) +} diff --git a/internal/mods/common/dal/notice.dal.go b/internal/mods/common/dal/notice.dal.go new file mode 100644 index 0000000..f9c38aa --- /dev/null +++ b/internal/mods/common/dal/notice.dal.go @@ -0,0 +1,83 @@ +package dal + +import ( + "context" + + "gitlab.guxuan.icu/jinshan_community/internal/mods/common/schema" + "gitlab.guxuan.icu/jinshan_community/pkg/errors" + "gitlab.guxuan.icu/jinshan_community/pkg/util" + "gorm.io/gorm" +) + +// Get notice storage instance +func GetNoticeDB(ctx context.Context, defDB *gorm.DB) *gorm.DB { + return util.GetDB(ctx, defDB).Model(new(schema.Notice)) +} + +// Defining the `Notice` data access object. +type Notice struct { + DB *gorm.DB +} + +// Query notices from the database based on the provided parameters and options. +func (a *Notice) Query(ctx context.Context, params schema.NoticeQueryParam, opts ...schema.NoticeQueryOptions) (*schema.NoticeQueryResult, error) { + var opt schema.NoticeQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + db := GetNoticeDB(ctx, a.DB) + + var list schema.Notices + pageResult, err := util.WrapPageQuery(ctx, db, params.PaginationParam, opt.QueryOptions, &list) + if err != nil { + return nil, errors.WithStack(err) + } + + queryResult := &schema.NoticeQueryResult{ + PageResult: pageResult, + Data: list, + } + return queryResult, nil +} + +// Get the specified notice from the database. +func (a *Notice) Get(ctx context.Context, id string, opts ...schema.NoticeQueryOptions) (*schema.Notice, error) { + var opt schema.NoticeQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + item := new(schema.Notice) + ok, err := util.FindOne(ctx, GetNoticeDB(ctx, a.DB).Where("id=?", id), opt.QueryOptions, item) + if err != nil { + return nil, errors.WithStack(err) + } else if !ok { + return nil, nil + } + return item, nil +} + +// Exists checks if the specified notice exists in the database. +func (a *Notice) Exists(ctx context.Context, id string) (bool, error) { + ok, err := util.Exists(ctx, GetNoticeDB(ctx, a.DB).Where("id=?", id)) + return ok, errors.WithStack(err) +} + +// Create a new notice. +func (a *Notice) Create(ctx context.Context, item *schema.Notice) error { + result := GetNoticeDB(ctx, a.DB).Create(item) + return errors.WithStack(result.Error) +} + +// Update the specified notice in the database. +func (a *Notice) Update(ctx context.Context, item *schema.Notice) error { + result := GetNoticeDB(ctx, a.DB).Where("id=?", item.ID).Select("*").Omit("created_at").Updates(item) + return errors.WithStack(result.Error) +} + +// Delete the specified notice from the database. +func (a *Notice) Delete(ctx context.Context, id string) error { + result := GetNoticeDB(ctx, a.DB).Where("id=?", id).Delete(new(schema.Notice)) + return errors.WithStack(result.Error) +} diff --git a/internal/mods/common/dal/surrounding_service.dal.go b/internal/mods/common/dal/surrounding_service.dal.go new file mode 100644 index 0000000..f6896b7 --- /dev/null +++ b/internal/mods/common/dal/surrounding_service.dal.go @@ -0,0 +1,83 @@ +package dal + +import ( + "context" + + "gitlab.guxuan.icu/jinshan_community/internal/mods/common/schema" + "gitlab.guxuan.icu/jinshan_community/pkg/errors" + "gitlab.guxuan.icu/jinshan_community/pkg/util" + "gorm.io/gorm" +) + +// Get surrounding service storage instance +func GetSurroundingServiceDB(ctx context.Context, defDB *gorm.DB) *gorm.DB { + return util.GetDB(ctx, defDB).Model(new(schema.SurroundingService)) +} + +// Defining the `SurroundingService` data access object. +type SurroundingService struct { + DB *gorm.DB +} + +// Query surrounding services from the database based on the provided parameters and options. +func (a *SurroundingService) Query(ctx context.Context, params schema.SurroundingServiceQueryParam, opts ...schema.SurroundingServiceQueryOptions) (*schema.SurroundingServiceQueryResult, error) { + var opt schema.SurroundingServiceQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + db := GetSurroundingServiceDB(ctx, a.DB) + + var list schema.SurroundingServices + pageResult, err := util.WrapPageQuery(ctx, db, params.PaginationParam, opt.QueryOptions, &list) + if err != nil { + return nil, errors.WithStack(err) + } + + queryResult := &schema.SurroundingServiceQueryResult{ + PageResult: pageResult, + Data: list, + } + return queryResult, nil +} + +// Get the specified surrounding service from the database. +func (a *SurroundingService) Get(ctx context.Context, id string, opts ...schema.SurroundingServiceQueryOptions) (*schema.SurroundingService, error) { + var opt schema.SurroundingServiceQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + item := new(schema.SurroundingService) + ok, err := util.FindOne(ctx, GetSurroundingServiceDB(ctx, a.DB).Where("id=?", id), opt.QueryOptions, item) + if err != nil { + return nil, errors.WithStack(err) + } else if !ok { + return nil, nil + } + return item, nil +} + +// Exists checks if the specified surrounding service exists in the database. +func (a *SurroundingService) Exists(ctx context.Context, id string) (bool, error) { + ok, err := util.Exists(ctx, GetSurroundingServiceDB(ctx, a.DB).Where("id=?", id)) + return ok, errors.WithStack(err) +} + +// Create a new surrounding service. +func (a *SurroundingService) Create(ctx context.Context, item *schema.SurroundingService) error { + result := GetSurroundingServiceDB(ctx, a.DB).Create(item) + return errors.WithStack(result.Error) +} + +// Update the specified surrounding service in the database. +func (a *SurroundingService) Update(ctx context.Context, item *schema.SurroundingService) error { + result := GetSurroundingServiceDB(ctx, a.DB).Where("id=?", item.ID).Select("*").Omit("created_at").Updates(item) + return errors.WithStack(result.Error) +} + +// Delete the specified surrounding service from the database. +func (a *SurroundingService) Delete(ctx context.Context, id string) error { + result := GetSurroundingServiceDB(ctx, a.DB).Where("id=?", id).Delete(new(schema.SurroundingService)) + return errors.WithStack(result.Error) +} diff --git a/internal/mods/common/dal/surrounding_service_type.dal.go b/internal/mods/common/dal/surrounding_service_type.dal.go new file mode 100644 index 0000000..151b16b --- /dev/null +++ b/internal/mods/common/dal/surrounding_service_type.dal.go @@ -0,0 +1,83 @@ +package dal + +import ( + "context" + + "gitlab.guxuan.icu/jinshan_community/internal/mods/common/schema" + "gitlab.guxuan.icu/jinshan_community/pkg/errors" + "gitlab.guxuan.icu/jinshan_community/pkg/util" + "gorm.io/gorm" +) + +// Get surrounding service type storage instance +func GetSurroundingServiceTypeDB(ctx context.Context, defDB *gorm.DB) *gorm.DB { + return util.GetDB(ctx, defDB).Model(new(schema.SurroundingServiceType)) +} + +// Defining the `SurroundingServiceType` data access object. +type SurroundingServiceType struct { + DB *gorm.DB +} + +// Query surrounding service types from the database based on the provided parameters and options. +func (a *SurroundingServiceType) Query(ctx context.Context, params schema.SurroundingServiceTypeQueryParam, opts ...schema.SurroundingServiceTypeQueryOptions) (*schema.SurroundingServiceTypeQueryResult, error) { + var opt schema.SurroundingServiceTypeQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + db := GetSurroundingServiceTypeDB(ctx, a.DB) + + var list schema.SurroundingServiceTypes + pageResult, err := util.WrapPageQuery(ctx, db, params.PaginationParam, opt.QueryOptions, &list) + if err != nil { + return nil, errors.WithStack(err) + } + + queryResult := &schema.SurroundingServiceTypeQueryResult{ + PageResult: pageResult, + Data: list, + } + return queryResult, nil +} + +// Get the specified surrounding service type from the database. +func (a *SurroundingServiceType) Get(ctx context.Context, id string, opts ...schema.SurroundingServiceTypeQueryOptions) (*schema.SurroundingServiceType, error) { + var opt schema.SurroundingServiceTypeQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + item := new(schema.SurroundingServiceType) + ok, err := util.FindOne(ctx, GetSurroundingServiceTypeDB(ctx, a.DB).Where("id=?", id), opt.QueryOptions, item) + if err != nil { + return nil, errors.WithStack(err) + } else if !ok { + return nil, nil + } + return item, nil +} + +// Exists checks if the specified surrounding service type exists in the database. +func (a *SurroundingServiceType) Exists(ctx context.Context, id string) (bool, error) { + ok, err := util.Exists(ctx, GetSurroundingServiceTypeDB(ctx, a.DB).Where("id=?", id)) + return ok, errors.WithStack(err) +} + +// Create a new surrounding service type. +func (a *SurroundingServiceType) Create(ctx context.Context, item *schema.SurroundingServiceType) error { + result := GetSurroundingServiceTypeDB(ctx, a.DB).Create(item) + return errors.WithStack(result.Error) +} + +// Update the specified surrounding service type in the database. +func (a *SurroundingServiceType) Update(ctx context.Context, item *schema.SurroundingServiceType) error { + result := GetSurroundingServiceTypeDB(ctx, a.DB).Where("id=?", item.ID).Select("*").Omit("created_at").Updates(item) + return errors.WithStack(result.Error) +} + +// Delete the specified surrounding service type from the database. +func (a *SurroundingServiceType) Delete(ctx context.Context, id string) error { + result := GetSurroundingServiceTypeDB(ctx, a.DB).Where("id=?", id).Delete(new(schema.SurroundingServiceType)) + return errors.WithStack(result.Error) +} diff --git a/internal/mods/common/dal/web_site.dal.go b/internal/mods/common/dal/web_site.dal.go new file mode 100644 index 0000000..2033b57 --- /dev/null +++ b/internal/mods/common/dal/web_site.dal.go @@ -0,0 +1,83 @@ +package dal + +import ( + "context" + + "gitlab.guxuan.icu/jinshan_community/internal/mods/common/schema" + "gitlab.guxuan.icu/jinshan_community/pkg/errors" + "gitlab.guxuan.icu/jinshan_community/pkg/util" + "gorm.io/gorm" +) + +// Get web site storage instance +func GetWebSiteDB(ctx context.Context, defDB *gorm.DB) *gorm.DB { + return util.GetDB(ctx, defDB).Model(new(schema.WebSite)) +} + +// Defining the `WebSite` data access object. +type WebSite struct { + DB *gorm.DB +} + +// Query web sites from the database based on the provided parameters and options. +func (a *WebSite) Query(ctx context.Context, params schema.WebSiteQueryParam, opts ...schema.WebSiteQueryOptions) (*schema.WebSiteQueryResult, error) { + var opt schema.WebSiteQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + db := GetWebSiteDB(ctx, a.DB) + + var list schema.WebSites + pageResult, err := util.WrapPageQuery(ctx, db, params.PaginationParam, opt.QueryOptions, &list) + if err != nil { + return nil, errors.WithStack(err) + } + + queryResult := &schema.WebSiteQueryResult{ + PageResult: pageResult, + Data: list, + } + return queryResult, nil +} + +// Get the specified web site from the database. +func (a *WebSite) Get(ctx context.Context, id string, opts ...schema.WebSiteQueryOptions) (*schema.WebSite, error) { + var opt schema.WebSiteQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + item := new(schema.WebSite) + ok, err := util.FindOne(ctx, GetWebSiteDB(ctx, a.DB).Where("id=?", id), opt.QueryOptions, item) + if err != nil { + return nil, errors.WithStack(err) + } else if !ok { + return nil, nil + } + return item, nil +} + +// Exists checks if the specified web site exists in the database. +func (a *WebSite) Exists(ctx context.Context, id string) (bool, error) { + ok, err := util.Exists(ctx, GetWebSiteDB(ctx, a.DB).Where("id=?", id)) + return ok, errors.WithStack(err) +} + +// Create a new web site. +func (a *WebSite) Create(ctx context.Context, item *schema.WebSite) error { + result := GetWebSiteDB(ctx, a.DB).Create(item) + return errors.WithStack(result.Error) +} + +// Update the specified web site in the database. +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) +} + +// Delete the specified web site from the database. +func (a *WebSite) Delete(ctx context.Context, id string) error { + result := GetWebSiteDB(ctx, a.DB).Where("id=?", id).Delete(new(schema.WebSite)) + return errors.WithStack(result.Error) +} diff --git a/internal/mods/common/dal/work_order.dal.go b/internal/mods/common/dal/work_order.dal.go new file mode 100644 index 0000000..2e81300 --- /dev/null +++ b/internal/mods/common/dal/work_order.dal.go @@ -0,0 +1,83 @@ +package dal + +import ( + "context" + + "gitlab.guxuan.icu/jinshan_community/internal/mods/common/schema" + "gitlab.guxuan.icu/jinshan_community/pkg/errors" + "gitlab.guxuan.icu/jinshan_community/pkg/util" + "gorm.io/gorm" +) + +// Get work order storage instance +func GetWorkOrderDB(ctx context.Context, defDB *gorm.DB) *gorm.DB { + return util.GetDB(ctx, defDB).Model(new(schema.WorkOrder)) +} + +// Defining the `WorkOrder` data access object. +type WorkOrder struct { + DB *gorm.DB +} + +// Query work orders from the database based on the provided parameters and options. +func (a *WorkOrder) Query(ctx context.Context, params schema.WorkOrderQueryParam, opts ...schema.WorkOrderQueryOptions) (*schema.WorkOrderQueryResult, error) { + var opt schema.WorkOrderQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + db := GetWorkOrderDB(ctx, a.DB) + + var list schema.WorkOrders + pageResult, err := util.WrapPageQuery(ctx, db, params.PaginationParam, opt.QueryOptions, &list) + if err != nil { + return nil, errors.WithStack(err) + } + + queryResult := &schema.WorkOrderQueryResult{ + PageResult: pageResult, + Data: list, + } + return queryResult, nil +} + +// Get the specified work order from the database. +func (a *WorkOrder) Get(ctx context.Context, id string, opts ...schema.WorkOrderQueryOptions) (*schema.WorkOrder, error) { + var opt schema.WorkOrderQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + item := new(schema.WorkOrder) + ok, err := util.FindOne(ctx, GetWorkOrderDB(ctx, a.DB).Where("id=?", id), opt.QueryOptions, item) + if err != nil { + return nil, errors.WithStack(err) + } else if !ok { + return nil, nil + } + return item, nil +} + +// Exists checks if the specified work order exists in the database. +func (a *WorkOrder) Exists(ctx context.Context, id string) (bool, error) { + ok, err := util.Exists(ctx, GetWorkOrderDB(ctx, a.DB).Where("id=?", id)) + return ok, errors.WithStack(err) +} + +// Create a new work order. +func (a *WorkOrder) Create(ctx context.Context, item *schema.WorkOrder) error { + result := GetWorkOrderDB(ctx, a.DB).Create(item) + return errors.WithStack(result.Error) +} + +// Update the specified work order in the database. +func (a *WorkOrder) Update(ctx context.Context, item *schema.WorkOrder) error { + result := GetWorkOrderDB(ctx, a.DB).Where("id=?", item.ID).Select("*").Omit("created_at").Updates(item) + return errors.WithStack(result.Error) +} + +// Delete the specified work order from the database. +func (a *WorkOrder) Delete(ctx context.Context, id string) error { + result := GetWorkOrderDB(ctx, a.DB).Where("id=?", id).Delete(new(schema.WorkOrder)) + return errors.WithStack(result.Error) +} diff --git a/internal/mods/common/dal/work_order_type.dal.go b/internal/mods/common/dal/work_order_type.dal.go new file mode 100644 index 0000000..1e5ddca --- /dev/null +++ b/internal/mods/common/dal/work_order_type.dal.go @@ -0,0 +1,83 @@ +package dal + +import ( + "context" + + "gitlab.guxuan.icu/jinshan_community/internal/mods/common/schema" + "gitlab.guxuan.icu/jinshan_community/pkg/errors" + "gitlab.guxuan.icu/jinshan_community/pkg/util" + "gorm.io/gorm" +) + +// Get work order type storage instance +func GetWorkOrderTypeDB(ctx context.Context, defDB *gorm.DB) *gorm.DB { + return util.GetDB(ctx, defDB).Model(new(schema.WorkOrderType)) +} + +// Defining the `WorkOrderType` data access object. +type WorkOrderType struct { + DB *gorm.DB +} + +// Query work order types from the database based on the provided parameters and options. +func (a *WorkOrderType) Query(ctx context.Context, params schema.WorkOrderTypeQueryParam, opts ...schema.WorkOrderTypeQueryOptions) (*schema.WorkOrderTypeQueryResult, error) { + var opt schema.WorkOrderTypeQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + db := GetWorkOrderTypeDB(ctx, a.DB) + + var list schema.WorkOrderTypes + pageResult, err := util.WrapPageQuery(ctx, db, params.PaginationParam, opt.QueryOptions, &list) + if err != nil { + return nil, errors.WithStack(err) + } + + queryResult := &schema.WorkOrderTypeQueryResult{ + PageResult: pageResult, + Data: list, + } + return queryResult, nil +} + +// Get the specified work order type from the database. +func (a *WorkOrderType) Get(ctx context.Context, id string, opts ...schema.WorkOrderTypeQueryOptions) (*schema.WorkOrderType, error) { + var opt schema.WorkOrderTypeQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + item := new(schema.WorkOrderType) + ok, err := util.FindOne(ctx, GetWorkOrderTypeDB(ctx, a.DB).Where("id=?", id), opt.QueryOptions, item) + if err != nil { + return nil, errors.WithStack(err) + } else if !ok { + return nil, nil + } + return item, nil +} + +// Exists checks if the specified work order type exists in the database. +func (a *WorkOrderType) Exists(ctx context.Context, id string) (bool, error) { + ok, err := util.Exists(ctx, GetWorkOrderTypeDB(ctx, a.DB).Where("id=?", id)) + return ok, errors.WithStack(err) +} + +// Create a new work order type. +func (a *WorkOrderType) Create(ctx context.Context, item *schema.WorkOrderType) error { + result := GetWorkOrderTypeDB(ctx, a.DB).Create(item) + return errors.WithStack(result.Error) +} + +// Update the specified work order type in the database. +func (a *WorkOrderType) Update(ctx context.Context, item *schema.WorkOrderType) error { + result := GetWorkOrderTypeDB(ctx, a.DB).Where("id=?", item.ID).Select("*").Omit("created_at").Updates(item) + return errors.WithStack(result.Error) +} + +// Delete the specified work order type from the database. +func (a *WorkOrderType) Delete(ctx context.Context, id string) error { + result := GetWorkOrderTypeDB(ctx, a.DB).Where("id=?", id).Delete(new(schema.WorkOrderType)) + return errors.WithStack(result.Error) +} diff --git a/internal/mods/common/main.go b/internal/mods/common/main.go new file mode 100644 index 0000000..7ac3bb0 --- /dev/null +++ b/internal/mods/common/main.go @@ -0,0 +1,127 @@ +package common + +import ( + "context" + + "gitlab.guxuan.icu/jinshan_community/internal/config" + + "github.com/gin-gonic/gin" + "gitlab.guxuan.icu/jinshan_community/internal/mods/common/api" + "gitlab.guxuan.icu/jinshan_community/internal/mods/common/schema" + "gorm.io/gorm" +) + +type Common struct { + DB *gorm.DB + BannerAPI *api.Banner + NoticeAPI *api.Notice + WorkOrderAPI *api.WorkOrder + WorkOrderTypeAPI *api.WorkOrderType + SurroundingServiceAPI *api.SurroundingService + SurroundingServiceTypeAPI *api.SurroundingServiceType + WebSiteAPI *api.WebSite + HelpAPI *api.Help + MettingRoomAPI *api.MettingRoom + MettingRoomOrderAPI *api.MettingRoomOrder +} + +func (a *Common) AutoMigrate(ctx context.Context) error { + return a.DB.AutoMigrate(new(schema.Banner), new(schema.Notice), new(schema.WorkOrder), new(schema.WorkOrderType), new(schema.SurroundingService), new(schema.SurroundingServiceType), new(schema.WebSite), new(schema.Help), new(schema.MettingRoom), new(schema.MettingRoomOrder)) +} + +func (a *Common) Init(ctx context.Context) error { + if config.C.Storage.DB.AutoMigrate { + if err := a.AutoMigrate(ctx); err != nil { + return err + } + } + return nil +} + +func (a *Common) RegisterV1Routers(ctx context.Context, v1 *gin.RouterGroup) error { + banner := v1.Group("banners") + { + banner.GET("", a.BannerAPI.Query) + banner.GET(":id", a.BannerAPI.Get) + banner.POST("", a.BannerAPI.Create) + banner.PUT(":id", a.BannerAPI.Update) + banner.DELETE(":id", a.BannerAPI.Delete) + } + notice := v1.Group("notices") + { + notice.GET("", a.NoticeAPI.Query) + notice.GET(":id", a.NoticeAPI.Get) + notice.POST("", a.NoticeAPI.Create) + notice.PUT(":id", a.NoticeAPI.Update) + notice.DELETE(":id", a.NoticeAPI.Delete) + } + workOrder := v1.Group("work-orders") + { + workOrder.GET("", a.WorkOrderAPI.Query) + workOrder.GET(":id", a.WorkOrderAPI.Get) + workOrder.POST("", a.WorkOrderAPI.Create) + workOrder.PUT(":id", a.WorkOrderAPI.Update) + workOrder.DELETE(":id", a.WorkOrderAPI.Delete) + } + workOrderType := v1.Group("work-order-types") + { + workOrderType.GET("", a.WorkOrderTypeAPI.Query) + workOrderType.GET(":id", a.WorkOrderTypeAPI.Get) + workOrderType.POST("", a.WorkOrderTypeAPI.Create) + workOrderType.PUT(":id", a.WorkOrderTypeAPI.Update) + workOrderType.DELETE(":id", a.WorkOrderTypeAPI.Delete) + } + surroundingService := v1.Group("surrounding-services") + { + surroundingService.GET("", a.SurroundingServiceAPI.Query) + surroundingService.GET(":id", a.SurroundingServiceAPI.Get) + surroundingService.POST("", a.SurroundingServiceAPI.Create) + surroundingService.PUT(":id", a.SurroundingServiceAPI.Update) + surroundingService.DELETE(":id", a.SurroundingServiceAPI.Delete) + } + surroundingServiceType := v1.Group("surrounding-service-types") + { + surroundingServiceType.GET("", a.SurroundingServiceTypeAPI.Query) + surroundingServiceType.GET(":id", a.SurroundingServiceTypeAPI.Get) + surroundingServiceType.POST("", a.SurroundingServiceTypeAPI.Create) + surroundingServiceType.PUT(":id", a.SurroundingServiceTypeAPI.Update) + surroundingServiceType.DELETE(":id", a.SurroundingServiceTypeAPI.Delete) + } + webSite := v1.Group("web-sites") + { + webSite.GET("", a.WebSiteAPI.Query) + webSite.GET(":id", a.WebSiteAPI.Get) + webSite.POST("", a.WebSiteAPI.Create) + webSite.PUT(":id", a.WebSiteAPI.Update) + webSite.DELETE(":id", a.WebSiteAPI.Delete) + } + help := v1.Group("helps") + { + help.GET("", a.HelpAPI.Query) + help.GET(":id", a.HelpAPI.Get) + help.POST("", a.HelpAPI.Create) + help.PUT(":id", a.HelpAPI.Update) + help.DELETE(":id", a.HelpAPI.Delete) + } + mettingRoom := v1.Group("metting-rooms") + { + mettingRoom.GET("", a.MettingRoomAPI.Query) + mettingRoom.GET(":id", a.MettingRoomAPI.Get) + mettingRoom.POST("", a.MettingRoomAPI.Create) + mettingRoom.PUT(":id", a.MettingRoomAPI.Update) + mettingRoom.DELETE(":id", a.MettingRoomAPI.Delete) + } + mettingRoomOrder := v1.Group("metting-room-orders") + { + mettingRoomOrder.GET("", a.MettingRoomOrderAPI.Query) + mettingRoomOrder.GET(":id", a.MettingRoomOrderAPI.Get) + mettingRoomOrder.POST("", a.MettingRoomOrderAPI.Create) + mettingRoomOrder.PUT(":id", a.MettingRoomOrderAPI.Update) + mettingRoomOrder.DELETE(":id", a.MettingRoomOrderAPI.Delete) + } + return nil +} + +func (a *Common) Release(ctx context.Context) error { + return nil +} diff --git a/internal/mods/common/schema/banner.go b/internal/mods/common/schema/banner.go new file mode 100644 index 0000000..a0492fe --- /dev/null +++ b/internal/mods/common/schema/banner.go @@ -0,0 +1,74 @@ +package schema + +import ( + "github.com/google/uuid" + "gitlab.guxuan.icu/jinshan_community/pkg/util" + "gorm.io/gorm" +) + +// Defining the `Banner` struct. +type Banner struct { + util.BaseModel + Title string `json:"title" gorm:"size:128;index;comment:标题"` + Desc string `json:"desc" gorm:"size:128;comment:介绍"` + Img string `json:"img" gorm:"size:2048;comment:图片"` + Sequence int `json:"sequence" gorm:"index;default:0;comment:排序"` + Typer string `json:"type" gorm:"index;comment:类型"` + Link string `json:"link" gorm:"size:2048;comment:链接"` + Status string `json:"status" gorm:"size:20;index;comment:状态"` +} + +func (c *Banner) BeforeCreate(tx *gorm.DB) (err error) { + if c.ID == "" { + c.ID = uuid.New().String() + } + return +} + +// Defining the query parameters for the `Banner` struct. +type BannerQueryParam struct { + util.PaginationParam +} + +// Defining the query options for the `Banner` struct. +type BannerQueryOptions struct { + util.QueryOptions +} + +// Defining the query result for the `Banner` struct. +type BannerQueryResult struct { + Data Banners + PageResult *util.PaginationResult +} + +// Defining the slice of `Banner` struct. +type Banners []*Banner + +// Defining the data structure for creating a `Banner` struct. +type BannerForm struct { + Title string `json:"title"` + Desc string `json:"desc"` + Img string `json:"img"` + Sequence int `json:"sequence"` + Link string `json:"link"` + Typer string `json:"type"` + + Status string `json:"status"` +} + +// A validation function for the `BannerForm` struct. +func (a *BannerForm) Validate() error { + return nil +} + +// Convert `BannerForm` to `Banner` object. +func (a *BannerForm) FillTo(banner *Banner) error { + banner.Title = a.Title + banner.Desc = a.Desc + banner.Img = a.Img + banner.Sequence = a.Sequence + banner.Link = a.Link + banner.Typer = a.Typer + banner.Status = a.Status + return nil +} diff --git a/internal/mods/common/schema/help.go b/internal/mods/common/schema/help.go new file mode 100644 index 0000000..c63e50b --- /dev/null +++ b/internal/mods/common/schema/help.go @@ -0,0 +1,59 @@ +package schema + +import ( + "github.com/google/uuid" + "gitlab.guxuan.icu/jinshan_community/pkg/util" + "gorm.io/gorm" +) + +// Defining the `Help` struct. +type Help struct { + util.BaseModel + CustomerID string `json:"customerId" gorm:"type:char(36);index;comment:客户id"` + Desc string `json:"desc" gorm:"size:128;comment:介绍"` + Img string `json:"img" gorm:"size:2048;comment:图片"` + Sequence int `json:"sequence" gorm:"index;default:0;comment:排序"` + Typer string `json:"type" gorm:"index;comment:类型"` + Link string `json:"link" gorm:"size:2048;comment:链接"` + Status string `json:"status" gorm:"size:20;index;comment:状态"` +} + +func (c *Help) BeforeCreate(tx *gorm.DB) (err error) { + if c.ID == "" { + c.ID = uuid.New().String() + } + return +} + +// Defining the query parameters for the `Help` struct. +type HelpQueryParam struct { + util.PaginationParam +} + +// Defining the query options for the `Help` struct. +type HelpQueryOptions struct { + util.QueryOptions +} + +// Defining the query result for the `Help` struct. +type HelpQueryResult struct { + Data Helps + PageResult *util.PaginationResult +} + +// Defining the slice of `Help` struct. +type Helps []*Help + +// Defining the data structure for creating a `Help` struct. +type HelpForm struct { +} + +// A validation function for the `HelpForm` struct. +func (a *HelpForm) Validate() error { + return nil +} + +// Convert `HelpForm` to `Help` object. +func (a *HelpForm) FillTo(help *Help) error { + return nil +} diff --git a/internal/mods/common/schema/metting_room.go b/internal/mods/common/schema/metting_room.go new file mode 100644 index 0000000..091237b --- /dev/null +++ b/internal/mods/common/schema/metting_room.go @@ -0,0 +1,70 @@ +package schema + +import ( + "github.com/google/uuid" + "gitlab.guxuan.icu/jinshan_community/pkg/util" + "gorm.io/gorm" +) + +// Defining the `MettingRoom` struct. +type MettingRoom struct { + util.BaseModel + Title string `json:"title" gorm:"size:128;index;comment:标题"` + Desc string `json:"desc" gorm:"size:128;comment:介绍"` + Imgs []*string `json:"imgs" gorm:"size:2048;comment:图片"` + MaxNum int `json:"maxNum" gorm:"comment:容纳人数"` + Link string `json:"link" gorm:"size:2048;comment:链接"` + Status string `json:"status" gorm:"size:20;index;comment:状态"` +} + +func (c *MettingRoom) BeforeCreate(tx *gorm.DB) (err error) { + if c.ID == "" { + c.ID = uuid.New().String() + } + return +} + +// Defining the query parameters for the `MettingRoom` struct. +type MettingRoomQueryParam struct { + util.PaginationParam +} + +// Defining the query options for the `MettingRoom` struct. +type MettingRoomQueryOptions struct { + util.QueryOptions +} + +// Defining the query result for the `MettingRoom` struct. +type MettingRoomQueryResult struct { + Data MettingRooms + PageResult *util.PaginationResult +} + +// Defining the slice of `MettingRoom` struct. +type MettingRooms []*MettingRoom + +// Defining the data structure for creating a `MettingRoom` struct. +type MettingRoomForm struct { + Title string `json:"title"` + Desc string `json:"desc" ` + Imgs []*string `json:"imgs" ` + MaxNum int `json:"maxNum" ` + Link string `json:"link" ` + Status string `json:"status"` +} + +// A validation function for the `MettingRoomForm` struct. +func (a *MettingRoomForm) Validate() error { + return nil +} + +// Convert `MettingRoomForm` to `MettingRoom` object. +func (a *MettingRoomForm) FillTo(mettingRoom *MettingRoom) error { + mettingRoom.Title = a.Title + mettingRoom.Desc = a.Desc + mettingRoom.Imgs = a.Imgs + mettingRoom.MaxNum = a.MaxNum + mettingRoom.Link = a.Link + mettingRoom.Status = a.Status + return nil +} diff --git a/internal/mods/common/schema/metting_room_order.go b/internal/mods/common/schema/metting_room_order.go new file mode 100644 index 0000000..0ca5ae7 --- /dev/null +++ b/internal/mods/common/schema/metting_room_order.go @@ -0,0 +1,74 @@ +package schema + +import ( + "github.com/google/uuid" + "gitlab.guxuan.icu/jinshan_community/pkg/util" + "gorm.io/gorm" + "time" +) + +// Defining the `MettingRoomOrder` struct. +type MettingRoomOrder struct { + util.BaseModel + RoomID string `json:"roomId" gorm:"type:char(36);index;not null;comment:会议室id"` + CustomerID string `json:"customerId" gorm:"type:char(36);index;not null;comment:用户id"` + ConcatName string `json:"concatName" gorm:"type:varchar(255);comment:联系人"` + ConcatPhone string `json:"concatPhone" gorm:"type:varchar(255);comment:联系手机号"` + StartAt *time.Time `json:"startAt" gorm:"type:datetime;comment:开始时间"` + EndAt *time.Time `json:"endAt" gorm:"type:datetime;comment:结束时间"` + Status string `json:"status" gorm:"size:20;index;comment:状态"` +} + +func (c *MettingRoomOrder) BeforeCreate(tx *gorm.DB) (err error) { + if c.ID == "" { + c.ID = uuid.New().String() + } + return +} + +// Defining the query parameters for the `MettingRoomOrder` struct. +type MettingRoomOrderQueryParam struct { + util.PaginationParam +} + +// Defining the query options for the `MettingRoomOrder` struct. +type MettingRoomOrderQueryOptions struct { + util.QueryOptions +} + +// Defining the query result for the `MettingRoomOrder` struct. +type MettingRoomOrderQueryResult struct { + Data MettingRoomOrders + PageResult *util.PaginationResult +} + +// Defining the slice of `MettingRoomOrder` struct. +type MettingRoomOrders []*MettingRoomOrder + +// Defining the data structure for creating a `MettingRoomOrder` struct. +type MettingRoomOrderForm struct { + RoomID string `json:"roomId" ` + CustomerID string `json:"customerId" ` + ConcatName string `json:"concatName"` + ConcatPhone string `json:"concatPhone"` + StartAt *time.Time `json:"startAt" ` + EndAt *time.Time `json:"endAt" ` + Status string `json:"status"` +} + +// A validation function for the `MettingRoomOrderForm` struct. +func (a *MettingRoomOrderForm) Validate() error { + return nil +} + +// Convert `MettingRoomOrderForm` to `MettingRoomOrder` object. +func (a *MettingRoomOrderForm) FillTo(mettingRoomOrder *MettingRoomOrder) error { + mettingRoomOrder.RoomID = a.RoomID + mettingRoomOrder.CustomerID = a.CustomerID + mettingRoomOrder.ConcatName = a.ConcatName + mettingRoomOrder.ConcatPhone = a.ConcatPhone + mettingRoomOrder.StartAt = a.StartAt + mettingRoomOrder.EndAt = a.EndAt + mettingRoomOrder.Status = a.Status + return nil +} diff --git a/internal/mods/common/schema/notice.go b/internal/mods/common/schema/notice.go new file mode 100644 index 0000000..a09f01b --- /dev/null +++ b/internal/mods/common/schema/notice.go @@ -0,0 +1,67 @@ +package schema + +import ( + "github.com/google/uuid" + "gitlab.guxuan.icu/jinshan_community/pkg/util" + "gorm.io/gorm" +) + +// Defining the `Notice` struct. +type Notice struct { + util.BaseModel + Title string `json:"title" gorm:"size:128;index;comment:标题"` + Desc string `json:"desc" gorm:"size:128;comment:介绍"` + Sequence int `json:"sequence" gorm:"index;default:0;comment:排序"` + Link string `json:"link" gorm:"size:2048;comment:链接"` + Status string `json:"status" gorm:"size:20;index;comment:状态"` +} + +func (c *Notice) BeforeCreate(tx *gorm.DB) (err error) { + if c.ID == "" { + c.ID = uuid.New().String() + } + return +} + +// Defining the query parameters for the `Notice` struct. +type NoticeQueryParam struct { + util.PaginationParam +} + +// Defining the query options for the `Notice` struct. +type NoticeQueryOptions struct { + util.QueryOptions +} + +// Defining the query result for the `Notice` struct. +type NoticeQueryResult struct { + Data Notices + PageResult *util.PaginationResult +} + +// Defining the slice of `Notice` struct. +type Notices []*Notice + +// Defining the data structure for creating a `Notice` struct. +type NoticeForm struct { + Title string `json:"title"` + Desc string `json:"desc"` + Sequence int `json:"sequence"` + Link string `json:"link"` + Status string `json:"status"` +} + +// A validation function for the `NoticeForm` struct. +func (a *NoticeForm) Validate() error { + return nil +} + +// Convert `NoticeForm` to `Notice` object. +func (a *NoticeForm) FillTo(notice *Notice) error { + notice.Title = a.Title + notice.Desc = a.Desc + notice.Sequence = a.Sequence + notice.Link = a.Link + notice.Status = a.Status + return nil +} diff --git a/internal/mods/common/schema/surrounding_service.go b/internal/mods/common/schema/surrounding_service.go new file mode 100644 index 0000000..4839db2 --- /dev/null +++ b/internal/mods/common/schema/surrounding_service.go @@ -0,0 +1,76 @@ +package schema + +import ( + "github.com/google/uuid" + "gitlab.guxuan.icu/jinshan_community/pkg/util" + "gorm.io/gorm" +) + +// Defining the `SurroundingService` struct. +type SurroundingService struct { + util.BaseModel + TypeID string `json:"typeId" gorm:"type:char(36);index;not null;comment:分类id"` + Title string `json:"title" gorm:"size:128;index;comment:标题"` + Desc string `json:"desc" gorm:"size:128;comment:介绍"` + Img string `json:"img" gorm:"size:2048;comment:图片"` + Content string `json:"content" gorm:"type:text;comment:内容"` + Sequence int `json:"sequence" gorm:"index;default:0;comment:排序"` + Link string `json:"link" gorm:"size:2048;comment:链接"` + Status string `json:"status" gorm:"size:20;index;comment:状态"` +} + +func (c *SurroundingService) BeforeCreate(tx *gorm.DB) (err error) { + if c.ID == "" { + c.ID = uuid.New().String() + } + return +} + +// Defining the query parameters for the `SurroundingService` struct. +type SurroundingServiceQueryParam struct { + util.PaginationParam +} + +// Defining the query options for the `SurroundingService` struct. +type SurroundingServiceQueryOptions struct { + util.QueryOptions +} + +// Defining the query result for the `SurroundingService` struct. +type SurroundingServiceQueryResult struct { + Data SurroundingServices + PageResult *util.PaginationResult +} + +// Defining the slice of `SurroundingService` struct. +type SurroundingServices []*SurroundingService + +// Defining the data structure for creating a `SurroundingService` struct. +type SurroundingServiceForm struct { + TypeID string `json:"typeId" ` + Title string `json:"title" ` + Desc string `json:"desc" ` + Img string `json:"img" ` + Content string `json:"content" ` + Sequence int `json:"sequence"` + Link string `json:"link"` + Status string `json:"status"` +} + +// A validation function for the `SurroundingServiceForm` struct. +func (a *SurroundingServiceForm) Validate() error { + return nil +} + +// Convert `SurroundingServiceForm` to `SurroundingService` object. +func (a *SurroundingServiceForm) FillTo(surroundingService *SurroundingService) error { + surroundingService.TypeID = a.TypeID + surroundingService.Title = a.Title + surroundingService.Desc = a.Desc + surroundingService.Img = a.Img + surroundingService.Content = a.Content + surroundingService.Sequence = a.Sequence + surroundingService.Link = a.Link + surroundingService.Status = a.Status + return nil +} diff --git a/internal/mods/common/schema/surrounding_service_type.go b/internal/mods/common/schema/surrounding_service_type.go new file mode 100644 index 0000000..8e67d58 --- /dev/null +++ b/internal/mods/common/schema/surrounding_service_type.go @@ -0,0 +1,62 @@ +package schema + +import ( + "github.com/google/uuid" + "gitlab.guxuan.icu/jinshan_community/pkg/util" + "gorm.io/gorm" +) + +// Defining the `SurroundingServiceType` struct. +type SurroundingServiceType struct { + util.BaseModel + Name string `json:"name" gorm:"size:1024;comment:名字" ` + Sequence int `json:"sequence" gorm:"index;default:0;comment:排序"` + Status string `json:"status" gorm:"size:20;index;comment:状态"` +} + +func (c *SurroundingServiceType) BeforeCreate(tx *gorm.DB) (err error) { + if c.ID == "" { + c.ID = uuid.New().String() + } + return +} + +// Defining the query parameters for the `SurroundingServiceType` struct. +type SurroundingServiceTypeQueryParam struct { + util.PaginationParam +} + +// Defining the query options for the `SurroundingServiceType` struct. +type SurroundingServiceTypeQueryOptions struct { + util.QueryOptions +} + +// Defining the query result for the `SurroundingServiceType` struct. +type SurroundingServiceTypeQueryResult struct { + Data SurroundingServiceTypes + PageResult *util.PaginationResult +} + +// Defining the slice of `SurroundingServiceType` struct. +type SurroundingServiceTypes []*SurroundingServiceType + +// Defining the data structure for creating a `SurroundingServiceType` struct. +type SurroundingServiceTypeForm struct { + Name string `json:"name" ` + Sequence int `json:"sequence"` + Status string `json:"status"` +} + +// A validation function for the `SurroundingServiceTypeForm` struct. +func (a *SurroundingServiceTypeForm) Validate() error { + return nil +} + +// Convert `SurroundingServiceTypeForm` to `SurroundingServiceType` object. +func (a *SurroundingServiceTypeForm) FillTo(surroundingServiceType *SurroundingServiceType) error { + surroundingServiceType.Name = a.Name + surroundingServiceType.Sequence = a.Sequence + surroundingServiceType.Status = a.Status + + return nil +} diff --git a/internal/mods/common/schema/web_site.go b/internal/mods/common/schema/web_site.go new file mode 100644 index 0000000..c368583 --- /dev/null +++ b/internal/mods/common/schema/web_site.go @@ -0,0 +1,55 @@ +package schema + +import ( + "github.com/google/uuid" + "gitlab.guxuan.icu/jinshan_community/pkg/util" + "gorm.io/gorm" +) + +// Defining the `WebSite` struct. +type WebSite struct { + util.BaseModel + Phone string `json:"phone" gorm:"size:128;comment:联系电话"` +} + +// Defining the query parameters for the `WebSite` struct. +type WebSiteQueryParam struct { + util.PaginationParam +} + +func (c *WebSite) BeforeCreate(tx *gorm.DB) (err error) { + if c.ID == "" { + c.ID = uuid.New().String() + } + return +} + +// Defining the query options for the `WebSite` struct. +type WebSiteQueryOptions struct { + util.QueryOptions +} + +// Defining the query result for the `WebSite` struct. +type WebSiteQueryResult struct { + Data WebSites + PageResult *util.PaginationResult +} + +// Defining the slice of `WebSite` struct. +type WebSites []*WebSite + +// Defining the data structure for creating a `WebSite` struct. +type WebSiteForm struct { + Phone string `json:"phone"` +} + +// A validation function for the `WebSiteForm` struct. +func (a *WebSiteForm) Validate() error { + return nil +} + +// Convert `WebSiteForm` to `WebSite` object. +func (a *WebSiteForm) FillTo(webSite *WebSite) error { + webSite.Phone = a.Phone + return nil +} diff --git a/internal/mods/common/schema/work_order.go b/internal/mods/common/schema/work_order.go new file mode 100644 index 0000000..7d0d8cb --- /dev/null +++ b/internal/mods/common/schema/work_order.go @@ -0,0 +1,77 @@ +package schema + +import ( + "github.com/google/uuid" + "gitlab.guxuan.icu/jinshan_community/pkg/util" + "gorm.io/gorm" +) + +// Defining the `WorkOrder` struct. +type WorkOrder struct { + util.BaseModel + CustomerID string `json:"customerId" gorm:"type:char(36);index;comment:客户id"` + Content string `json:"content" gorm:"type:text;comment:工单内容"` + Images *[]string `json:"images" gorm:"serializer:json;comment:图片数组"` + Videos *[]string `json:"videos" gorm:"serializer:json;comment:视频数组"` + Records *[]Record `json:"records" gorm:"serializer:json;comment:回复记录"` + Status string `json:"status" gorm:"size:20;index;comment:状态"` +} + +type Record struct { + Sender string `json:"sender"` + Content string `json:"content"` + SendAt string `json:"sendAt"` +} + +func (c *WorkOrder) BeforeCreate(tx *gorm.DB) (err error) { + if c.ID == "" { + c.ID = uuid.New().String() + } + return +} + +// Defining the query parameters for the `WorkOrder` struct. +type WorkOrderQueryParam struct { + util.PaginationParam +} + +// Defining the query options for the `WorkOrder` struct. +type WorkOrderQueryOptions struct { + util.QueryOptions +} + +// Defining the query result for the `WorkOrder` struct. +type WorkOrderQueryResult struct { + Data WorkOrders + PageResult *util.PaginationResult +} + +// Defining the slice of `WorkOrder` struct. +type WorkOrders []*WorkOrder + +// Defining the data structure for creating a `WorkOrder` struct. +type WorkOrderForm struct { + CustomerID string `json:"customerId"` + Content string `json:"content"` + Images *[]string `json:"images"` + Videos *[]string `json:"videos"` + Records *[]Record `json:"records"` + Status string `json:"status"` +} + +// A validation function for the `WorkOrderForm` struct. +func (a *WorkOrderForm) Validate() error { + + return nil +} + +// Convert `WorkOrderForm` to `WorkOrder` object. +func (a *WorkOrderForm) FillTo(workOrder *WorkOrder) error { + workOrder.CustomerID = a.CustomerID + workOrder.Content = a.Content + workOrder.Images = a.Images + workOrder.Videos = a.Videos + workOrder.Records = a.Records + workOrder.Status = a.Status + return nil +} diff --git a/internal/mods/common/schema/work_order_type.go b/internal/mods/common/schema/work_order_type.go new file mode 100644 index 0000000..2f6333d --- /dev/null +++ b/internal/mods/common/schema/work_order_type.go @@ -0,0 +1,61 @@ +package schema + +import ( + "github.com/google/uuid" + "gitlab.guxuan.icu/jinshan_community/pkg/util" + "gorm.io/gorm" +) + +// Defining the `WorkOrderType` struct. +type WorkOrderType struct { + util.BaseModel + Name string `json:"name" gorm:"size:1024;comment:名字" ` + Sequence int `json:"sequence" gorm:"index;default:0;comment:排序"` + Status string `json:"status" gorm:"size:20;index;comment:状态"` +} + +func (c *WorkOrderType) BeforeCreate(tx *gorm.DB) (err error) { + if c.ID == "" { + c.ID = uuid.New().String() + } + return +} + +// Defining the query parameters for the `WorkOrderType` struct. +type WorkOrderTypeQueryParam struct { + util.PaginationParam +} + +// Defining the query options for the `WorkOrderType` struct. +type WorkOrderTypeQueryOptions struct { + util.QueryOptions +} + +// Defining the query result for the `WorkOrderType` struct. +type WorkOrderTypeQueryResult struct { + Data WorkOrderTypes + PageResult *util.PaginationResult +} + +// Defining the slice of `WorkOrderType` struct. +type WorkOrderTypes []*WorkOrderType + +// Defining the data structure for creating a `WorkOrderType` struct. +type WorkOrderTypeForm struct { + Name string `json:"name" ` + Sequence int `json:"sequence"` + Status string `json:"status"` +} + +// A validation function for the `WorkOrderTypeForm` struct. +func (a *WorkOrderTypeForm) Validate() error { + return nil +} + +// Convert `WorkOrderTypeForm` to `WorkOrderType` object. +func (a *WorkOrderTypeForm) FillTo(workOrderType *WorkOrderType) error { + workOrderType.Name = a.Name + workOrderType.Sequence = a.Sequence + workOrderType.Status = a.Status + return nil +} diff --git a/internal/mods/common/wire.go b/internal/mods/common/wire.go new file mode 100644 index 0000000..54a1c5d --- /dev/null +++ b/internal/mods/common/wire.go @@ -0,0 +1,42 @@ +package common + +import ( + "github.com/google/wire" + "gitlab.guxuan.icu/jinshan_community/internal/mods/common/api" + "gitlab.guxuan.icu/jinshan_community/internal/mods/common/biz" + "gitlab.guxuan.icu/jinshan_community/internal/mods/common/dal" +) + +var Set = wire.NewSet( + wire.Struct(new(Common), "*"), + wire.Struct(new(dal.Banner), "*"), + wire.Struct(new(biz.Banner), "*"), + wire.Struct(new(api.Banner), "*"), + wire.Struct(new(dal.Notice), "*"), + wire.Struct(new(biz.Notice), "*"), + wire.Struct(new(api.Notice), "*"), + wire.Struct(new(dal.WorkOrder), "*"), + wire.Struct(new(biz.WorkOrder), "*"), + wire.Struct(new(api.WorkOrder), "*"), + wire.Struct(new(dal.WorkOrderType), "*"), + wire.Struct(new(biz.WorkOrderType), "*"), + wire.Struct(new(api.WorkOrderType), "*"), + wire.Struct(new(dal.SurroundingService), "*"), + wire.Struct(new(biz.SurroundingService), "*"), + wire.Struct(new(api.SurroundingService), "*"), + wire.Struct(new(dal.SurroundingServiceType), "*"), + wire.Struct(new(biz.SurroundingServiceType), "*"), + wire.Struct(new(api.SurroundingServiceType), "*"), + wire.Struct(new(dal.WebSite), "*"), + wire.Struct(new(biz.WebSite), "*"), + wire.Struct(new(api.WebSite), "*"), + wire.Struct(new(dal.Help), "*"), + wire.Struct(new(biz.Help), "*"), + wire.Struct(new(api.Help), "*"), + wire.Struct(new(dal.MettingRoom), "*"), + wire.Struct(new(biz.MettingRoom), "*"), + wire.Struct(new(api.MettingRoom), "*"), + wire.Struct(new(dal.MettingRoomOrder), "*"), + wire.Struct(new(biz.MettingRoomOrder), "*"), + wire.Struct(new(api.MettingRoomOrder), "*"), +) diff --git a/internal/mods/customer/api/balance.api.go b/internal/mods/customer/api/balance.api.go new file mode 100644 index 0000000..0f3201e --- /dev/null +++ b/internal/mods/customer/api/balance.api.go @@ -0,0 +1,131 @@ +package api + +import ( + "github.com/gin-gonic/gin" + "gitlab.guxuan.icu/jinshan_community/internal/mods/customer/biz" + "gitlab.guxuan.icu/jinshan_community/internal/mods/customer/schema" + "gitlab.guxuan.icu/jinshan_community/pkg/util" +) + +// Defining the `Balance` api. +type Balance struct { + BalanceBIZ *biz.Balance +} + +// @Tags BalanceAPI +// @Security ApiKeyAuth +// @Summary Query balance list +// @Param current query int true "pagination index" default(1) +// @Param pageSize query int true "pagination size" default(10) +// @Success 200 {object} util.ResponseResult{data=[]schema.Balance} +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/balances [get] +func (a *Balance) Query(c *gin.Context) { + ctx := c.Request.Context() + var params schema.BalanceQueryParam + if err := util.ParseQuery(c, ¶ms); err != nil { + util.ResError(c, err) + return + } + + result, err := a.BalanceBIZ.Query(ctx, params) + if err != nil { + util.ResError(c, err) + return + } + util.ResPage(c, result.Data, result.PageResult) +} + +// @Tags BalanceAPI +// @Security ApiKeyAuth +// @Summary Get balance record by ID +// @Param id path string true "unique id" +// @Success 200 {object} util.ResponseResult{data=schema.Balance} +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/balances/{id} [get] +func (a *Balance) Get(c *gin.Context) { + ctx := c.Request.Context() + item, err := a.BalanceBIZ.Get(ctx, c.Param("id")) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, item) +} + +// @Tags BalanceAPI +// @Security ApiKeyAuth +// @Summary Create balance record +// @Param body body schema.BalanceForm true "Request body" +// @Success 200 {object} util.ResponseResult{data=schema.Balance} +// @Failure 400 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/balances [post] +func (a *Balance) Create(c *gin.Context) { + ctx := c.Request.Context() + item := new(schema.BalanceForm) + if err := util.ParseJSON(c, item); err != nil { + util.ResError(c, err) + return + } else if err := item.Validate(); err != nil { + util.ResError(c, err) + return + } + + result, err := a.BalanceBIZ.Create(ctx, item) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, result) +} + +// @Tags BalanceAPI +// @Security ApiKeyAuth +// @Summary Update balance record by ID +// @Param id path string true "unique id" +// @Param body body schema.BalanceForm true "Request body" +// @Success 200 {object} util.ResponseResult +// @Failure 400 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/balances/{id} [put] +func (a *Balance) Update(c *gin.Context) { + ctx := c.Request.Context() + item := new(schema.BalanceForm) + if err := util.ParseJSON(c, item); err != nil { + util.ResError(c, err) + return + } else if err := item.Validate(); err != nil { + util.ResError(c, err) + return + } + + err := a.BalanceBIZ.Update(ctx, c.Param("id"), item) + if err != nil { + util.ResError(c, err) + return + } + util.ResOK(c) +} + +// @Tags BalanceAPI +// @Security ApiKeyAuth +// @Summary Delete balance record by ID +// @Param id path string true "unique id" +// @Success 200 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/balances/{id} [delete] +func (a *Balance) Delete(c *gin.Context) { + ctx := c.Request.Context() + err := a.BalanceBIZ.Delete(ctx, c.Param("id")) + if err != nil { + util.ResError(c, err) + return + } + util.ResOK(c) +} diff --git a/internal/mods/customer/api/customer.api.go b/internal/mods/customer/api/customer.api.go new file mode 100644 index 0000000..21fc53d --- /dev/null +++ b/internal/mods/customer/api/customer.api.go @@ -0,0 +1,131 @@ +package api + +import ( + "github.com/gin-gonic/gin" + "gitlab.guxuan.icu/jinshan_community/internal/mods/customer/biz" + "gitlab.guxuan.icu/jinshan_community/internal/mods/customer/schema" + "gitlab.guxuan.icu/jinshan_community/pkg/util" +) + +// Defining the `Customer` api. +type Customer struct { + CustomerBIZ *biz.Customer +} + +// @Tags CustomerAPI +// @Security ApiKeyAuth +// @Summary Query customer list +// @Param current query int true "pagination index" default(1) +// @Param pageSize query int true "pagination size" default(10) +// @Success 200 {object} util.ResponseResult{data=[]schema.Customer} +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/customers [get] +func (a *Customer) Query(c *gin.Context) { + ctx := c.Request.Context() + var params schema.CustomerQueryParam + if err := util.ParseQuery(c, ¶ms); err != nil { + util.ResError(c, err) + return + } + + result, err := a.CustomerBIZ.Query(ctx, params) + if err != nil { + util.ResError(c, err) + return + } + util.ResPage(c, result.Data, result.PageResult) +} + +// @Tags CustomerAPI +// @Security ApiKeyAuth +// @Summary Get customer record by ID +// @Param id path string true "unique id" +// @Success 200 {object} util.ResponseResult{data=schema.Customer} +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/customers/{id} [get] +func (a *Customer) Get(c *gin.Context) { + ctx := c.Request.Context() + item, err := a.CustomerBIZ.Get(ctx, c.Param("id")) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, item) +} + +// @Tags CustomerAPI +// @Security ApiKeyAuth +// @Summary Create customer record +// @Param body body schema.CustomerForm true "Request body" +// @Success 200 {object} util.ResponseResult{data=schema.Customer} +// @Failure 400 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/customers [post] +func (a *Customer) Create(c *gin.Context) { + ctx := c.Request.Context() + item := new(schema.CustomerForm) + if err := util.ParseJSON(c, item); err != nil { + util.ResError(c, err) + return + } else if err := item.Validate(); err != nil { + util.ResError(c, err) + return + } + + result, err := a.CustomerBIZ.Create(ctx, item) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, result) +} + +// @Tags CustomerAPI +// @Security ApiKeyAuth +// @Summary Update customer record by ID +// @Param id path string true "unique id" +// @Param body body schema.CustomerForm true "Request body" +// @Success 200 {object} util.ResponseResult +// @Failure 400 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/customers/{id} [put] +func (a *Customer) Update(c *gin.Context) { + ctx := c.Request.Context() + item := new(schema.CustomerForm) + if err := util.ParseJSON(c, item); err != nil { + util.ResError(c, err) + return + } else if err := item.Validate(); err != nil { + util.ResError(c, err) + return + } + + err := a.CustomerBIZ.Update(ctx, c.Param("id"), item) + if err != nil { + util.ResError(c, err) + return + } + util.ResOK(c) +} + +// @Tags CustomerAPI +// @Security ApiKeyAuth +// @Summary Delete customer record by ID +// @Param id path string true "unique id" +// @Success 200 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/customers/{id} [delete] +func (a *Customer) Delete(c *gin.Context) { + ctx := c.Request.Context() + err := a.CustomerBIZ.Delete(ctx, c.Param("id")) + if err != nil { + util.ResError(c, err) + return + } + util.ResOK(c) +} diff --git a/internal/mods/customer/biz/balance.biz.go b/internal/mods/customer/biz/balance.biz.go new file mode 100644 index 0000000..9c58cdc --- /dev/null +++ b/internal/mods/customer/biz/balance.biz.go @@ -0,0 +1,104 @@ +package biz + +import ( + "context" + "time" + + "gitlab.guxuan.icu/jinshan_community/internal/mods/customer/dal" + "gitlab.guxuan.icu/jinshan_community/internal/mods/customer/schema" + "gitlab.guxuan.icu/jinshan_community/pkg/errors" + "gitlab.guxuan.icu/jinshan_community/pkg/util" +) + +// Defining the `Balance` business logic. +type Balance struct { + Trans *util.Trans + BalanceDAL *dal.Balance +} + +// Query balances from the data access object based on the provided parameters and options. +func (a *Balance) Query(ctx context.Context, params schema.BalanceQueryParam) (*schema.BalanceQueryResult, error) { + params.Pagination = true + + result, err := a.BalanceDAL.Query(ctx, params, schema.BalanceQueryOptions{ + QueryOptions: util.QueryOptions{ + OrderFields: []util.OrderByParam{ + {Field: "created_at", Direction: util.DESC}, + }, + }, + }) + if err != nil { + return nil, err + } + return result, nil +} + +// Get the specified balance from the data access object. +func (a *Balance) Get(ctx context.Context, id string) (*schema.Balance, error) { + balance, err := a.BalanceDAL.Get(ctx, id) + if err != nil { + return nil, err + } else if balance == nil { + return nil, errors.NotFound("", "Balance not found") + } + return balance, nil +} + +// Create a new balance in the data access object. +func (a *Balance) Create(ctx context.Context, formItem *schema.BalanceForm) (*schema.Balance, error) { + balance := &schema.Balance{} + + if err := formItem.FillTo(balance); err != nil { + return nil, err + } + + err := a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.BalanceDAL.Create(ctx, balance); err != nil { + return err + } + return nil + }) + if err != nil { + return nil, err + } + return balance, nil +} + +// Update the specified balance in the data access object. +func (a *Balance) Update(ctx context.Context, id string, formItem *schema.BalanceForm) error { + balance, err := a.BalanceDAL.Get(ctx, id) + if err != nil { + return err + } else if balance == nil { + return errors.NotFound("", "Balance not found") + } + + if err := formItem.FillTo(balance); err != nil { + return err + } + balance.UpdatedAt = time.Now() + + return a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.BalanceDAL.Update(ctx, balance); err != nil { + return err + } + return nil + }) +} + +// Delete the specified balance from the data access object. +func (a *Balance) Delete(ctx context.Context, id string) error { + exists, err := a.BalanceDAL.Exists(ctx, id) + if err != nil { + return err + } else if !exists { + return errors.NotFound("", "Balance not found") + } + + return a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.BalanceDAL.Delete(ctx, id); err != nil { + return err + } + return nil + }) +} diff --git a/internal/mods/customer/biz/customer.biz.go b/internal/mods/customer/biz/customer.biz.go new file mode 100644 index 0000000..9d943d3 --- /dev/null +++ b/internal/mods/customer/biz/customer.biz.go @@ -0,0 +1,104 @@ +package biz + +import ( + "context" + "time" + + "gitlab.guxuan.icu/jinshan_community/internal/mods/customer/dal" + "gitlab.guxuan.icu/jinshan_community/internal/mods/customer/schema" + "gitlab.guxuan.icu/jinshan_community/pkg/errors" + "gitlab.guxuan.icu/jinshan_community/pkg/util" +) + +// Defining the `Customer` business logic. +type Customer struct { + Trans *util.Trans + CustomerDAL *dal.Customer +} + +// Query customers from the data access object based on the provided parameters and options. +func (a *Customer) Query(ctx context.Context, params schema.CustomerQueryParam) (*schema.CustomerQueryResult, error) { + params.Pagination = true + + result, err := a.CustomerDAL.Query(ctx, params, schema.CustomerQueryOptions{ + QueryOptions: util.QueryOptions{ + OrderFields: []util.OrderByParam{ + {Field: "created_at", Direction: util.DESC}, + }, + }, + }) + if err != nil { + return nil, err + } + return result, nil +} + +// Get the specified customer from the data access object. +func (a *Customer) Get(ctx context.Context, id string) (*schema.Customer, error) { + customer, err := a.CustomerDAL.Get(ctx, id) + if err != nil { + return nil, err + } else if customer == nil { + return nil, errors.NotFound("", "Customer not found") + } + return customer, nil +} + +// Create a new customer in the data access object. +func (a *Customer) Create(ctx context.Context, formItem *schema.CustomerForm) (*schema.Customer, error) { + customer := &schema.Customer{} + + if err := formItem.FillTo(customer); err != nil { + return nil, err + } + + err := a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.CustomerDAL.Create(ctx, customer); err != nil { + return err + } + return nil + }) + if err != nil { + return nil, err + } + return customer, nil +} + +// Update the specified customer in the data access object. +func (a *Customer) Update(ctx context.Context, id string, formItem *schema.CustomerForm) error { + customer, err := a.CustomerDAL.Get(ctx, id) + if err != nil { + return err + } else if customer == nil { + return errors.NotFound("", "Customer not found") + } + + if err := formItem.FillTo(customer); err != nil { + return err + } + customer.UpdatedAt = time.Now() + + return a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.CustomerDAL.Update(ctx, customer); err != nil { + return err + } + return nil + }) +} + +// Delete the specified customer from the data access object. +func (a *Customer) Delete(ctx context.Context, id string) error { + exists, err := a.CustomerDAL.Exists(ctx, id) + if err != nil { + return err + } else if !exists { + return errors.NotFound("", "Customer not found") + } + + return a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.CustomerDAL.Delete(ctx, id); err != nil { + return err + } + return nil + }) +} diff --git a/internal/mods/customer/dal/balance.dal.go b/internal/mods/customer/dal/balance.dal.go new file mode 100644 index 0000000..8f2444e --- /dev/null +++ b/internal/mods/customer/dal/balance.dal.go @@ -0,0 +1,83 @@ +package dal + +import ( + "context" + + "gitlab.guxuan.icu/jinshan_community/internal/mods/customer/schema" + "gitlab.guxuan.icu/jinshan_community/pkg/errors" + "gitlab.guxuan.icu/jinshan_community/pkg/util" + "gorm.io/gorm" +) + +// Get balance storage instance +func GetBalanceDB(ctx context.Context, defDB *gorm.DB) *gorm.DB { + return util.GetDB(ctx, defDB).Model(new(schema.Balance)) +} + +// Defining the `Balance` data access object. +type Balance struct { + DB *gorm.DB +} + +// Query balances from the database based on the provided parameters and options. +func (a *Balance) Query(ctx context.Context, params schema.BalanceQueryParam, opts ...schema.BalanceQueryOptions) (*schema.BalanceQueryResult, error) { + var opt schema.BalanceQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + db := GetBalanceDB(ctx, a.DB) + + var list schema.Balances + pageResult, err := util.WrapPageQuery(ctx, db, params.PaginationParam, opt.QueryOptions, &list) + if err != nil { + return nil, errors.WithStack(err) + } + + queryResult := &schema.BalanceQueryResult{ + PageResult: pageResult, + Data: list, + } + return queryResult, nil +} + +// Get the specified balance from the database. +func (a *Balance) Get(ctx context.Context, id string, opts ...schema.BalanceQueryOptions) (*schema.Balance, error) { + var opt schema.BalanceQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + item := new(schema.Balance) + ok, err := util.FindOne(ctx, GetBalanceDB(ctx, a.DB).Where("id=?", id), opt.QueryOptions, item) + if err != nil { + return nil, errors.WithStack(err) + } else if !ok { + return nil, nil + } + return item, nil +} + +// Exists checks if the specified balance exists in the database. +func (a *Balance) Exists(ctx context.Context, id string) (bool, error) { + ok, err := util.Exists(ctx, GetBalanceDB(ctx, a.DB).Where("id=?", id)) + return ok, errors.WithStack(err) +} + +// Create a new balance. +func (a *Balance) Create(ctx context.Context, item *schema.Balance) error { + result := GetBalanceDB(ctx, a.DB).Create(item) + return errors.WithStack(result.Error) +} + +// Update the specified balance in the database. +func (a *Balance) Update(ctx context.Context, item *schema.Balance) error { + result := GetBalanceDB(ctx, a.DB).Where("id=?", item.ID).Select("*").Omit("created_at").Updates(item) + return errors.WithStack(result.Error) +} + +// Delete the specified balance from the database. +func (a *Balance) Delete(ctx context.Context, id string) error { + result := GetBalanceDB(ctx, a.DB).Where("id=?", id).Delete(new(schema.Balance)) + return errors.WithStack(result.Error) +} diff --git a/internal/mods/customer/dal/customer.dal.go b/internal/mods/customer/dal/customer.dal.go new file mode 100644 index 0000000..c04fc34 --- /dev/null +++ b/internal/mods/customer/dal/customer.dal.go @@ -0,0 +1,83 @@ +package dal + +import ( + "context" + + "gitlab.guxuan.icu/jinshan_community/internal/mods/customer/schema" + "gitlab.guxuan.icu/jinshan_community/pkg/errors" + "gitlab.guxuan.icu/jinshan_community/pkg/util" + "gorm.io/gorm" +) + +// Get customer storage instance +func GetCustomerDB(ctx context.Context, defDB *gorm.DB) *gorm.DB { + return util.GetDB(ctx, defDB).Model(new(schema.Customer)) +} + +// Defining the `Customer` data access object. +type Customer struct { + DB *gorm.DB +} + +// Query customers from the database based on the provided parameters and options. +func (a *Customer) Query(ctx context.Context, params schema.CustomerQueryParam, opts ...schema.CustomerQueryOptions) (*schema.CustomerQueryResult, error) { + var opt schema.CustomerQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + db := GetCustomerDB(ctx, a.DB) + + var list schema.Customers + pageResult, err := util.WrapPageQuery(ctx, db, params.PaginationParam, opt.QueryOptions, &list) + if err != nil { + return nil, errors.WithStack(err) + } + + queryResult := &schema.CustomerQueryResult{ + PageResult: pageResult, + Data: list, + } + return queryResult, nil +} + +// Get the specified customer from the database. +func (a *Customer) Get(ctx context.Context, id string, opts ...schema.CustomerQueryOptions) (*schema.Customer, error) { + var opt schema.CustomerQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + item := new(schema.Customer) + ok, err := util.FindOne(ctx, GetCustomerDB(ctx, a.DB).Where("id=?", id), opt.QueryOptions, item) + if err != nil { + return nil, errors.WithStack(err) + } else if !ok { + return nil, nil + } + return item, nil +} + +// Exists checks if the specified customer exists in the database. +func (a *Customer) Exists(ctx context.Context, id string) (bool, error) { + ok, err := util.Exists(ctx, GetCustomerDB(ctx, a.DB).Where("id=?", id)) + return ok, errors.WithStack(err) +} + +// Create a new customer. +func (a *Customer) Create(ctx context.Context, item *schema.Customer) error { + result := GetCustomerDB(ctx, a.DB).Create(item) + return errors.WithStack(result.Error) +} + +// Update the specified customer in the database. +func (a *Customer) Update(ctx context.Context, item *schema.Customer) error { + result := GetCustomerDB(ctx, a.DB).Where("id=?", item.ID).Select("*").Omit("created_at").Updates(item) + return errors.WithStack(result.Error) +} + +// Delete the specified customer from the database. +func (a *Customer) Delete(ctx context.Context, id string) error { + result := GetCustomerDB(ctx, a.DB).Where("id=?", id).Delete(new(schema.Customer)) + return errors.WithStack(result.Error) +} diff --git a/internal/mods/customer/main.go b/internal/mods/customer/main.go new file mode 100644 index 0000000..42d5be6 --- /dev/null +++ b/internal/mods/customer/main.go @@ -0,0 +1,55 @@ +package customer + +import ( + "context" + + "gitlab.guxuan.icu/jinshan_community/internal/config" + + "github.com/gin-gonic/gin" + "gitlab.guxuan.icu/jinshan_community/internal/mods/customer/api" + "gitlab.guxuan.icu/jinshan_community/internal/mods/customer/schema" + "gorm.io/gorm" +) + +type Customer struct { + DB *gorm.DB + CustomerAPI *api.Customer + BalanceAPI *api.Balance +} + +func (a *Customer) AutoMigrate(ctx context.Context) error { + return a.DB.AutoMigrate(new(schema.Customer), new(schema.Balance)) +} + +func (a *Customer) Init(ctx context.Context) error { + if config.C.Storage.DB.AutoMigrate { + if err := a.AutoMigrate(ctx); err != nil { + return err + } + } + return nil +} + +func (a *Customer) RegisterV1Routers(ctx context.Context, v1 *gin.RouterGroup) error { + customer := v1.Group("customers") + { + customer.GET("", a.CustomerAPI.Query) + customer.GET(":id", a.CustomerAPI.Get) + customer.POST("", a.CustomerAPI.Create) + customer.PUT(":id", a.CustomerAPI.Update) + customer.DELETE(":id", a.CustomerAPI.Delete) + } + balance := v1.Group("balances") + { + balance.GET("", a.BalanceAPI.Query) + balance.GET(":id", a.BalanceAPI.Get) + balance.POST("", a.BalanceAPI.Create) + balance.PUT(":id", a.BalanceAPI.Update) + balance.DELETE(":id", a.BalanceAPI.Delete) + } + return nil +} + +func (a *Customer) Release(ctx context.Context) error { + return nil +} diff --git a/internal/mods/customer/schema/balance.go b/internal/mods/customer/schema/balance.go new file mode 100644 index 0000000..9e1d293 --- /dev/null +++ b/internal/mods/customer/schema/balance.go @@ -0,0 +1,74 @@ +package schema + +import ( + "github.com/google/uuid" + "gitlab.guxuan.icu/jinshan_community/pkg/util" + "gorm.io/gorm" +) + +// Defining the `Balance` struct. +type Balance struct { + util.BaseModel + CustomerID string `json:"customerId" gorm:"type:char(36);index;comment:客户id"` + Before int `json:"before" gorm:"not null;comment:改变钱余额" ` + After int `json:"after" gorm:"not null;comment:改变后余额" ` + Change int `json:"change" gorm:"not null;comment:变化量" ` + Reason string `json:"reason" gorm:"size:1024;comment:原因说明" ` + Typer string `json:"type" gorm:"not null;comment:类型" ` + OperatorID string `json:"operatorId" gorm:"type:char(36);index;comment:关联id"` +} + +func (c *Balance) BeforeCreate(tx *gorm.DB) (err error) { + if c.ID == "" { + c.ID = uuid.New().String() + } + return +} + +// Defining the query parameters for the `Balance` struct. +type BalanceQueryParam struct { + util.PaginationParam +} + +// Defining the query options for the `Balance` struct. +type BalanceQueryOptions struct { + util.QueryOptions +} + +// Defining the query result for the `Balance` struct. +type BalanceQueryResult struct { + Data Balances + PageResult *util.PaginationResult +} + +// Defining the slice of `Balance` struct. +type Balances []*Balance + +// Defining the data structure for creating a `Balance` struct. +type BalanceForm struct { + CustomerID string `json:"customerId" ` + Before int `json:"before" ` + After int `json:"after"` + Change int `json:"change"` + Reason string `json:"reason" ` + Typer string `json:"type" ` + OperatorID string `json:"operatorId" ` + CreatedID string `json:"CreatedID" ` +} + +// A validation function for the `BalanceForm` struct. +func (a *BalanceForm) Validate() error { + return nil +} + +// Convert `BalanceForm` to `Balance` object. +func (a *BalanceForm) FillTo(balance *Balance) error { + balance.CustomerID = a.CustomerID + balance.Before = a.Before + balance.After = a.After + balance.Reason = a.Reason + balance.Typer = a.Typer + balance.OperatorID = a.OperatorID + balance.CreatedID = a.CreatedID + return nil +} diff --git a/internal/mods/customer/schema/customer.go b/internal/mods/customer/schema/customer.go new file mode 100644 index 0000000..0542493 --- /dev/null +++ b/internal/mods/customer/schema/customer.go @@ -0,0 +1,76 @@ +package schema + +import ( + "github.com/google/uuid" + "gitlab.guxuan.icu/jinshan_community/pkg/util" + "gorm.io/gorm" +) + +// Defining the `Customer` struct. +type Customer struct { + util.BaseModel + Name string `json:"name" gorm:"size:128;index;comment:姓名"` + Phone string `json:"phone" gorm:"size:128;index;comment:手机号"` + Birthday string `json:"birthday" gorm:"size:128;index;comment:生日"` + WxSign string `json:"wxSign" gorm:"size:1024;comment:微信签名token"` + Introduce string `json:"introduce" gorm:"size:1024;comment:个性签名"` + Avatar string `json:"avatar" gorm:"size:1024;comment:头像"` + Status string `json:"status" gorm:"size:20;index;comment:状态"` +} + +func (c *Customer) BeforeCreate(tx *gorm.DB) (err error) { + if c.ID == "" { + c.ID = uuid.New().String() + } + return +} + +// Defining the query parameters for the `Customer` struct. +type CustomerQueryParam struct { + util.PaginationParam +} + +// Defining the query options for the `Customer` struct. +type CustomerQueryOptions struct { + util.QueryOptions + LikeName string `json:"name"` + LikePhone string `json:"phone"` + Status string `json:"status"` +} + +// Defining the query result for the `Customer` struct. +type CustomerQueryResult struct { + Data Customers + PageResult *util.PaginationResult +} + +// Defining the slice of `Customer` struct. +type Customers []*Customer + +// Defining the data structure for creating a `Customer` struct. +type CustomerForm struct { + Name string `json:"name"` + Phone string `json:"phone" ` + Birthday string `json:"birthday"` + WxSign string `json:"wxSign" ` + Avatar string `json:"avatar" ` + Status string `json:"status"` + Introduce string `json:"introduce" ` +} + +// A validation function for the `CustomerForm` struct. +func (a *CustomerForm) Validate() error { + return nil +} + +// Convert `CustomerForm` to `Customer` object. +func (a *CustomerForm) FillTo(customer *Customer) error { + customer.Name = a.Name + customer.Phone = a.Phone + customer.Birthday = a.Birthday + customer.WxSign = a.WxSign + customer.Avatar = a.Avatar + customer.Status = a.Status + customer.Introduce = a.Introduce + return nil +} diff --git a/internal/mods/customer/wire.go b/internal/mods/customer/wire.go new file mode 100644 index 0000000..b3575ba --- /dev/null +++ b/internal/mods/customer/wire.go @@ -0,0 +1,18 @@ +package customer + +import ( + "github.com/google/wire" + "gitlab.guxuan.icu/jinshan_community/internal/mods/customer/api" + "gitlab.guxuan.icu/jinshan_community/internal/mods/customer/biz" + "gitlab.guxuan.icu/jinshan_community/internal/mods/customer/dal" +) + +var Set = wire.NewSet( + wire.Struct(new(Customer), "*"), + wire.Struct(new(dal.Customer), "*"), + wire.Struct(new(biz.Customer), "*"), + wire.Struct(new(api.Customer), "*"), + wire.Struct(new(dal.Balance), "*"), + wire.Struct(new(biz.Balance), "*"), + wire.Struct(new(api.Balance), "*"), +) diff --git a/internal/mods/mods.go b/internal/mods/mods.go new file mode 100644 index 0000000..eaffab0 --- /dev/null +++ b/internal/mods/mods.go @@ -0,0 +1,135 @@ +package mods + +import ( + "context" + + "github.com/gin-gonic/gin" + "github.com/google/wire" + "gitlab.guxuan.icu/jinshan_community/internal/mods/activity" + "gitlab.guxuan.icu/jinshan_community/internal/mods/ai" + "gitlab.guxuan.icu/jinshan_community/internal/mods/app" + "gitlab.guxuan.icu/jinshan_community/internal/mods/common" + "gitlab.guxuan.icu/jinshan_community/internal/mods/customer" + "gitlab.guxuan.icu/jinshan_community/internal/mods/product" + "gitlab.guxuan.icu/jinshan_community/internal/mods/rbac" +) + +const ( + apiPrefix = "/api/" +) + +// Collection of wire providers +var Set = wire.NewSet( + wire.Struct(new(Mods), "*"), + rbac.Set, + customer.Set, + common.Set, + activity.Set, + product.Set, + app.Set, + ai.Set, +) + +type Mods struct { + RBAC *rbac.RBAC + Customer *customer.Customer + Common *common.Common + Activity *activity.Activity + Product *product.Product + App *app.App + Ai *ai.Ai +} + +func (a *Mods) Init(ctx context.Context) error { + if err := a.RBAC.Init(ctx); err != nil { + return err + } + if err := a.Customer.Init( + ctx); err != nil { + return err + } + if err := a.Common.Init( + ctx); err != nil { + return err + } + if err := a.Activity.Init(ctx); err != nil { + return err + } + if err := a.Product.Init(ctx); err != nil { + return err + } + if err := a.App.Init(ctx); err != nil { + return err + } + if err := a.Ai.Init(ctx); err != nil { + return err + } + + return nil +} + +func (a *Mods) RouterPrefixes() []string { + return []string{ + apiPrefix, + } +} + +func (a *Mods) RegisterRouters(ctx context.Context, e *gin.Engine) error { + gAPI := e.Group(apiPrefix) + v1 := gAPI.Group("v1") + + if err := a.RBAC.RegisterV1Routers(ctx, v1); err != nil { + return err + } + if err := a.Customer.RegisterV1Routers(ctx, v1); err != nil { + return err + } + if err := a.Common.RegisterV1Routers(ctx, v1); err != nil { + return err + } + if err := a.Activity.RegisterV1Routers(ctx, + v1, + ); err != nil { + return err + } + if err := a.Product.RegisterV1Routers(ctx, v1); err != nil { + return err + } + if err := a.App.RegisterV1Routers(ctx, v1); err != nil { + return err + } + if err := a.Ai.RegisterV1Routers(ctx, v1); err != nil { + return err + } + + return nil +} + +func (a *Mods) Release(ctx context.Context) error { + if err := a.RBAC.Release(ctx); err != nil { + return err + } + if err := a.Customer.Release(ctx); err != nil { + return err + } + if err := a.Common.Release(ctx); err != nil { + return err + } + if err := a.Activity.Release(ctx); err != nil { + return err + } + if err := a.Product.Release(ctx); err != nil { + return err + } + if err := a.App.Release( + ctx); err != nil { + return err + } + if err := a.Ai.Release( + ctx, + ); err != nil { + return err + } + + return nil +} diff --git a/internal/mods/product/api/order.api.go b/internal/mods/product/api/order.api.go new file mode 100644 index 0000000..f3ab298 --- /dev/null +++ b/internal/mods/product/api/order.api.go @@ -0,0 +1,131 @@ +package api + +import ( + "github.com/gin-gonic/gin" + "gitlab.guxuan.icu/jinshan_community/internal/mods/product/biz" + "gitlab.guxuan.icu/jinshan_community/internal/mods/product/schema" + "gitlab.guxuan.icu/jinshan_community/pkg/util" +) + +// Defining the `Order` api. +type Order struct { + OrderBIZ *biz.Order +} + +// @Tags OrderAPI +// @Security ApiKeyAuth +// @Summary Query order list +// @Param current query int true "pagination index" default(1) +// @Param pageSize query int true "pagination size" default(10) +// @Success 200 {object} util.ResponseResult{data=[]schema.Order} +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/orders [get] +func (a *Order) Query(c *gin.Context) { + ctx := c.Request.Context() + var params schema.OrderQueryParam + if err := util.ParseQuery(c, ¶ms); err != nil { + util.ResError(c, err) + return + } + + result, err := a.OrderBIZ.Query(ctx, params) + if err != nil { + util.ResError(c, err) + return + } + util.ResPage(c, result.Data, result.PageResult) +} + +// @Tags OrderAPI +// @Security ApiKeyAuth +// @Summary Get order record by ID +// @Param id path string true "unique id" +// @Success 200 {object} util.ResponseResult{data=schema.Order} +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/orders/{id} [get] +func (a *Order) Get(c *gin.Context) { + ctx := c.Request.Context() + item, err := a.OrderBIZ.Get(ctx, c.Param("id")) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, item) +} + +// @Tags OrderAPI +// @Security ApiKeyAuth +// @Summary Create order record +// @Param body body schema.OrderForm true "Request body" +// @Success 200 {object} util.ResponseResult{data=schema.Order} +// @Failure 400 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/orders [post] +func (a *Order) Create(c *gin.Context) { + ctx := c.Request.Context() + item := new(schema.OrderForm) + 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.OrderBIZ.Create(ctx, item) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, result) +} + +// @Tags OrderAPI +// @Security ApiKeyAuth +// @Summary Update order record by ID +// @Param id path string true "unique id" +// @Param body body schema.OrderForm 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/orders/{id} [put] +func (a *Order) Update(c *gin.Context) { + ctx := c.Request.Context() + item := new(schema.OrderForm) + 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.OrderBIZ.Update(ctx, c.Param("id"), item) + if err != nil { + util.ResError(c, err) + return + } + util.ResOK(c) +} + +// @Tags OrderAPI +// @Security ApiKeyAuth +// @Summary Delete order record by ID +// @Param id path string true "unique id" +// @Success 200 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/orders/{id} [delete] +func (a *Order) Delete(c *gin.Context) { + ctx := c.Request.Context() + err := a.OrderBIZ.Delete(ctx, c.Param("id")) + if err != nil { + util.ResError(c, err) + return + } + util.ResOK(c) +} diff --git a/internal/mods/product/api/product.api.go b/internal/mods/product/api/product.api.go new file mode 100644 index 0000000..1ab8a33 --- /dev/null +++ b/internal/mods/product/api/product.api.go @@ -0,0 +1,131 @@ +package api + +import ( + "github.com/gin-gonic/gin" + "gitlab.guxuan.icu/jinshan_community/internal/mods/product/biz" + "gitlab.guxuan.icu/jinshan_community/internal/mods/product/schema" + "gitlab.guxuan.icu/jinshan_community/pkg/util" +) + +// Defining the `Product` api. +type Product struct { + ProductBIZ *biz.Product +} + +// @Tags ProductAPI +// @Security ApiKeyAuth +// @Summary Query product list +// @Param current query int true "pagination index" default(1) +// @Param pageSize query int true "pagination size" default(10) +// @Success 200 {object} util.ResponseResult{data=[]schema.Product} +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/products [get] +func (a *Product) Query(c *gin.Context) { + ctx := c.Request.Context() + var params schema.ProductQueryParam + if err := util.ParseQuery(c, ¶ms); err != nil { + util.ResError(c, err) + return + } + + result, err := a.ProductBIZ.Query(ctx, params) + if err != nil { + util.ResError(c, err) + return + } + util.ResPage(c, result.Data, result.PageResult) +} + +// @Tags ProductAPI +// @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 ProductAPI +// @Security ApiKeyAuth +// @Summary Create product record +// @Param body body schema.ProductForm true "Request body" +// @Success 200 {object} util.ResponseResult{data=schema.Product} +// @Failure 400 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/products [post] +func (a *Product) Create(c *gin.Context) { + ctx := c.Request.Context() + item := new(schema.ProductForm) + if err := util.ParseJSON(c, item); err != nil { + util.ResError(c, err) + return + } else if err := item.Validate(); err != nil { + util.ResError(c, err) + return + } + + result, err := a.ProductBIZ.Create(ctx, item) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, result) +} + +// @Tags ProductAPI +// @Security ApiKeyAuth +// @Summary Update product record by ID +// @Param id path string true "unique id" +// @Param body body schema.ProductForm true "Request body" +// @Success 200 {object} util.ResponseResult +// @Failure 400 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/products/{id} [put] +func (a *Product) Update(c *gin.Context) { + ctx := c.Request.Context() + item := new(schema.ProductForm) + if err := util.ParseJSON(c, item); err != nil { + util.ResError(c, err) + return + } else if err := item.Validate(); err != nil { + util.ResError(c, err) + return + } + + err := a.ProductBIZ.Update(ctx, c.Param("id"), item) + if err != nil { + util.ResError(c, err) + return + } + util.ResOK(c) +} + +// @Tags ProductAPI +// @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/product/api/product_category.api.go b/internal/mods/product/api/product_category.api.go new file mode 100644 index 0000000..7699be7 --- /dev/null +++ b/internal/mods/product/api/product_category.api.go @@ -0,0 +1,131 @@ +package api + +import ( + "github.com/gin-gonic/gin" + "gitlab.guxuan.icu/jinshan_community/internal/mods/product/biz" + "gitlab.guxuan.icu/jinshan_community/internal/mods/product/schema" + "gitlab.guxuan.icu/jinshan_community/pkg/util" +) + +// Defining the `ProductCategory` api. +type ProductCategory struct { + ProductCategoryBIZ *biz.ProductCategory +} + +// @Tags ProductCategoryAPI +// @Security ApiKeyAuth +// @Summary Query product category list +// @Param current query int true "pagination index" default(1) +// @Param pageSize query int true "pagination size" default(10) +// @Success 200 {object} util.ResponseResult{data=[]schema.ProductCategory} +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/product-categories [get] +func (a *ProductCategory) Query(c *gin.Context) { + ctx := c.Request.Context() + var params schema.ProductCategoryQueryParam + if err := util.ParseQuery(c, ¶ms); err != nil { + util.ResError(c, err) + return + } + + result, err := a.ProductCategoryBIZ.Query(ctx, params) + if err != nil { + util.ResError(c, err) + return + } + util.ResPage(c, result.Data, result.PageResult) +} + +// @Tags ProductCategoryAPI +// @Security ApiKeyAuth +// @Summary Get product category record by ID +// @Param id path string true "unique id" +// @Success 200 {object} util.ResponseResult{data=schema.ProductCategory} +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/product-categories/{id} [get] +func (a *ProductCategory) Get(c *gin.Context) { + ctx := c.Request.Context() + item, err := a.ProductCategoryBIZ.Get(ctx, c.Param("id")) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, item) +} + +// @Tags ProductCategoryAPI +// @Security ApiKeyAuth +// @Summary Create product category record +// @Param body body schema.ProductCategoryForm true "Request body" +// @Success 200 {object} util.ResponseResult{data=schema.ProductCategory} +// @Failure 400 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/product-categories [post] +func (a *ProductCategory) Create(c *gin.Context) { + ctx := c.Request.Context() + item := new(schema.ProductCategoryForm) + if err := util.ParseJSON(c, item); err != nil { + util.ResError(c, err) + return + } else if err := item.Validate(); err != nil { + util.ResError(c, err) + return + } + + result, err := a.ProductCategoryBIZ.Create(ctx, item) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, result) +} + +// @Tags ProductCategoryAPI +// @Security ApiKeyAuth +// @Summary Update product category record by ID +// @Param id path string true "unique id" +// @Param body body schema.ProductCategoryForm true "Request body" +// @Success 200 {object} util.ResponseResult +// @Failure 400 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/product-categories/{id} [put] +func (a *ProductCategory) Update(c *gin.Context) { + ctx := c.Request.Context() + item := new(schema.ProductCategoryForm) + if err := util.ParseJSON(c, item); err != nil { + util.ResError(c, err) + return + } else if err := item.Validate(); err != nil { + util.ResError(c, err) + return + } + + err := a.ProductCategoryBIZ.Update(ctx, c.Param("id"), item) + if err != nil { + util.ResError(c, err) + return + } + util.ResOK(c) +} + +// @Tags ProductCategoryAPI +// @Security ApiKeyAuth +// @Summary Delete product category record by ID +// @Param id path string true "unique id" +// @Success 200 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/product-categories/{id} [delete] +func (a *ProductCategory) Delete(c *gin.Context) { + ctx := c.Request.Context() + err := a.ProductCategoryBIZ.Delete(ctx, c.Param("id")) + if err != nil { + util.ResError(c, err) + return + } + util.ResOK(c) +} diff --git a/internal/mods/product/api/shop.api.go b/internal/mods/product/api/shop.api.go new file mode 100644 index 0000000..8230e0e --- /dev/null +++ b/internal/mods/product/api/shop.api.go @@ -0,0 +1,131 @@ +package api + +import ( + "github.com/gin-gonic/gin" + "gitlab.guxuan.icu/jinshan_community/internal/mods/product/biz" + "gitlab.guxuan.icu/jinshan_community/internal/mods/product/schema" + "gitlab.guxuan.icu/jinshan_community/pkg/util" +) + +// Defining the `Shop` api. +type Shop struct { + ShopBIZ *biz.Shop +} + +// @Tags ShopAPI +// @Security ApiKeyAuth +// @Summary Query shop list +// @Param current query int true "pagination index" default(1) +// @Param pageSize query int true "pagination size" default(10) +// @Success 200 {object} util.ResponseResult{data=[]schema.Shop} +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/shops [get] +func (a *Shop) Query(c *gin.Context) { + ctx := c.Request.Context() + var params schema.ShopQueryParam + if err := util.ParseQuery(c, ¶ms); err != nil { + util.ResError(c, err) + return + } + + result, err := a.ShopBIZ.Query(ctx, params) + if err != nil { + util.ResError(c, err) + return + } + util.ResPage(c, result.Data, result.PageResult) +} + +// @Tags ShopAPI +// @Security ApiKeyAuth +// @Summary Get shop record by ID +// @Param id path string true "unique id" +// @Success 200 {object} util.ResponseResult{data=schema.Shop} +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/shops/{id} [get] +func (a *Shop) Get(c *gin.Context) { + ctx := c.Request.Context() + item, err := a.ShopBIZ.Get(ctx, c.Param("id")) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, item) +} + +// @Tags ShopAPI +// @Security ApiKeyAuth +// @Summary Create shop record +// @Param body body schema.ShopForm true "Request body" +// @Success 200 {object} util.ResponseResult{data=schema.Shop} +// @Failure 400 {object} util.ResponseResult +// @Failure 401 {object} util.ResponseResult +// @Failure 500 {object} util.ResponseResult +// @Router /api/v1/shops [post] +func (a *Shop) Create(c *gin.Context) { + ctx := c.Request.Context() + item := new(schema.ShopForm) + 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.ShopBIZ.Create(ctx, item) + if err != nil { + util.ResError(c, err) + return + } + util.ResSuccess(c, result) +} + +// @Tags ShopAPI +// @Security ApiKeyAuth +// @Summary Update shop record by ID +// @Param id path string true "unique id" +// @Param body body schema.ShopForm 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/shops/{id} [put] +func (a *Shop) Update(c *gin.Context) { + ctx := c.Request.Context() + item := new(schema.ShopForm) + 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.ShopBIZ.Update(ctx, c.Param("id"), item) + if err != nil { + util.ResError(c, err) + return + } + util.ResOK(c) +} + +// @Tags ShopAPI +// @Security ApiKeyAuth +// @Summary Delete shop 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/shops/{id} [delete] +func (a *Shop) Delete(c *gin.Context) { + ctx := c.Request.Context() + err := a.ShopBIZ.Delete(ctx, c.Param("id")) + if err != nil { + util.ResError(c, err) + return + } + util.ResOK(c) +} diff --git a/internal/mods/product/biz/order.biz.go b/internal/mods/product/biz/order.biz.go new file mode 100644 index 0000000..8a5392c --- /dev/null +++ b/internal/mods/product/biz/order.biz.go @@ -0,0 +1,104 @@ +package biz + +import ( + "context" + "time" + + "gitlab.guxuan.icu/jinshan_community/internal/mods/product/dal" + "gitlab.guxuan.icu/jinshan_community/internal/mods/product/schema" + "gitlab.guxuan.icu/jinshan_community/pkg/errors" + "gitlab.guxuan.icu/jinshan_community/pkg/util" +) + +// Defining the `Order` business logic. +type Order struct { + Trans *util.Trans + OrderDAL *dal.Order +} + +// Query orders from the data access object based on the provided parameters and options. +func (a *Order) Query(ctx context.Context, params schema.OrderQueryParam) (*schema.OrderQueryResult, error) { + params.Pagination = true + + result, err := a.OrderDAL.Query(ctx, params, schema.OrderQueryOptions{ + QueryOptions: util.QueryOptions{ + OrderFields: []util.OrderByParam{ + {Field: "created_at", Direction: util.DESC}, + }, + }, + }) + if err != nil { + return nil, err + } + return result, nil +} + +// Get the specified order from the data access object. +func (a *Order) Get(ctx context.Context, id string) (*schema.Order, error) { + order, err := a.OrderDAL.Get(ctx, id) + if err != nil { + return nil, err + } else if order == nil { + return nil, errors.NotFound("", "Order not found") + } + return order, nil +} + +// Create a new order in the data access object. +func (a *Order) Create(ctx context.Context, formItem *schema.OrderForm) (*schema.Order, error) { + order := &schema.Order{} + + if err := formItem.FillTo(order); err != nil { + return nil, err + } + + err := a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.OrderDAL.Create(ctx, order); err != nil { + return err + } + return nil + }) + if err != nil { + return nil, err + } + return order, nil +} + +// Update the specified order in the data access object. +func (a *Order) Update(ctx context.Context, id string, formItem *schema.OrderForm) error { + order, err := a.OrderDAL.Get(ctx, id) + if err != nil { + return err + } else if order == nil { + return errors.NotFound("", "Order not found") + } + + if err := formItem.FillTo(order); err != nil { + return err + } + order.UpdatedAt = time.Now() + + return a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.OrderDAL.Update(ctx, order); err != nil { + return err + } + return nil + }) +} + +// Delete the specified order from the data access object. +func (a *Order) Delete(ctx context.Context, id string) error { + exists, err := a.OrderDAL.Exists(ctx, id) + if err != nil { + return err + } else if !exists { + return errors.NotFound("", "Order not found") + } + + return a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.OrderDAL.Delete(ctx, id); err != nil { + return err + } + return nil + }) +} diff --git a/internal/mods/product/biz/product.biz.go b/internal/mods/product/biz/product.biz.go new file mode 100644 index 0000000..2f8295c --- /dev/null +++ b/internal/mods/product/biz/product.biz.go @@ -0,0 +1,104 @@ +package biz + +import ( + "context" + "time" + + "gitlab.guxuan.icu/jinshan_community/internal/mods/product/dal" + "gitlab.guxuan.icu/jinshan_community/internal/mods/product/schema" + "gitlab.guxuan.icu/jinshan_community/pkg/errors" + "gitlab.guxuan.icu/jinshan_community/pkg/util" +) + +// Defining the `Product` business logic. +type Product struct { + Trans *util.Trans + ProductDAL *dal.Product +} + +// Query products from the data access object based on the provided parameters and options. +func (a *Product) Query(ctx context.Context, params schema.ProductQueryParam) (*schema.ProductQueryResult, error) { + params.Pagination = true + + result, err := a.ProductDAL.Query(ctx, params, schema.ProductQueryOptions{ + QueryOptions: util.QueryOptions{ + OrderFields: []util.OrderByParam{ + {Field: "created_at", Direction: util.DESC}, + }, + }, + }) + if err != nil { + return nil, err + } + return result, nil +} + +// Get the specified product from the data access object. +func (a *Product) Get(ctx context.Context, id 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("", "Product not found") + } + return product, nil +} + +// Create a new product in the data access object. +func (a *Product) Create(ctx context.Context, formItem *schema.ProductForm) (*schema.Product, error) { + product := &schema.Product{} + + if err := formItem.FillTo(product); err != nil { + return nil, err + } + + err := a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.ProductDAL.Create(ctx, product); err != nil { + return err + } + return nil + }) + if err != nil { + return nil, err + } + return product, nil +} + +// Update the specified product in the data access object. +func (a *Product) Update(ctx context.Context, id string, formItem *schema.ProductForm) error { + product, err := a.ProductDAL.Get(ctx, id) + if err != nil { + return err + } else if product == nil { + return errors.NotFound("", "Product not found") + } + + if err := formItem.FillTo(product); err != nil { + return err + } + product.UpdatedAt = time.Now() + + return a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.ProductDAL.Update(ctx, product); err != nil { + return err + } + return nil + }) +} + +// Delete the specified product from the data access object. +func (a *Product) Delete(ctx context.Context, id string) error { + exists, err := a.ProductDAL.Exists(ctx, id) + if err != nil { + return err + } else if !exists { + return errors.NotFound("", "Product not found") + } + + return a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.ProductDAL.Delete(ctx, id); err != nil { + return err + } + return nil + }) +} diff --git a/internal/mods/product/biz/product_category.biz.go b/internal/mods/product/biz/product_category.biz.go new file mode 100644 index 0000000..a71ff20 --- /dev/null +++ b/internal/mods/product/biz/product_category.biz.go @@ -0,0 +1,104 @@ +package biz + +import ( + "context" + "time" + + "gitlab.guxuan.icu/jinshan_community/internal/mods/product/dal" + "gitlab.guxuan.icu/jinshan_community/internal/mods/product/schema" + "gitlab.guxuan.icu/jinshan_community/pkg/errors" + "gitlab.guxuan.icu/jinshan_community/pkg/util" +) + +// Defining the `ProductCategory` business logic. +type ProductCategory struct { + Trans *util.Trans + ProductCategoryDAL *dal.ProductCategory +} + +// Query product categories from the data access object based on the provided parameters and options. +func (a *ProductCategory) Query(ctx context.Context, params schema.ProductCategoryQueryParam) (*schema.ProductCategoryQueryResult, error) { + params.Pagination = true + + result, err := a.ProductCategoryDAL.Query(ctx, params, schema.ProductCategoryQueryOptions{ + QueryOptions: util.QueryOptions{ + OrderFields: []util.OrderByParam{ + {Field: "created_at", Direction: util.DESC}, + }, + }, + }) + if err != nil { + return nil, err + } + return result, nil +} + +// Get the specified product category from the data access object. +func (a *ProductCategory) Get(ctx context.Context, id string) (*schema.ProductCategory, error) { + productCategory, err := a.ProductCategoryDAL.Get(ctx, id) + if err != nil { + return nil, err + } else if productCategory == nil { + return nil, errors.NotFound("", "Product category not found") + } + return productCategory, nil +} + +// Create a new product category in the data access object. +func (a *ProductCategory) Create(ctx context.Context, formItem *schema.ProductCategoryForm) (*schema.ProductCategory, error) { + productCategory := &schema.ProductCategory{} + + if err := formItem.FillTo(productCategory); err != nil { + return nil, err + } + + err := a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.ProductCategoryDAL.Create(ctx, productCategory); err != nil { + return err + } + return nil + }) + if err != nil { + return nil, err + } + return productCategory, nil +} + +// Update the specified product category in the data access object. +func (a *ProductCategory) Update(ctx context.Context, id string, formItem *schema.ProductCategoryForm) error { + productCategory, err := a.ProductCategoryDAL.Get(ctx, id) + if err != nil { + return err + } else if productCategory == nil { + return errors.NotFound("", "Product category not found") + } + + if err := formItem.FillTo(productCategory); err != nil { + return err + } + productCategory.UpdatedAt = time.Now() + + return a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.ProductCategoryDAL.Update(ctx, productCategory); err != nil { + return err + } + return nil + }) +} + +// Delete the specified product category from the data access object. +func (a *ProductCategory) Delete(ctx context.Context, id string) error { + exists, err := a.ProductCategoryDAL.Exists(ctx, id) + if err != nil { + return err + } else if !exists { + return errors.NotFound("", "Product category not found") + } + + return a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.ProductCategoryDAL.Delete(ctx, id); err != nil { + return err + } + return nil + }) +} diff --git a/internal/mods/product/biz/shop.biz.go b/internal/mods/product/biz/shop.biz.go new file mode 100644 index 0000000..b78dc87 --- /dev/null +++ b/internal/mods/product/biz/shop.biz.go @@ -0,0 +1,104 @@ +package biz + +import ( + "context" + "time" + + "gitlab.guxuan.icu/jinshan_community/internal/mods/product/dal" + "gitlab.guxuan.icu/jinshan_community/internal/mods/product/schema" + "gitlab.guxuan.icu/jinshan_community/pkg/errors" + "gitlab.guxuan.icu/jinshan_community/pkg/util" +) + +// Defining the `Shop` business logic. +type Shop struct { + Trans *util.Trans + ShopDAL *dal.Shop +} + +// Query shops from the data access object based on the provided parameters and options. +func (a *Shop) Query(ctx context.Context, params schema.ShopQueryParam) (*schema.ShopQueryResult, error) { + params.Pagination = true + + result, err := a.ShopDAL.Query(ctx, params, schema.ShopQueryOptions{ + QueryOptions: util.QueryOptions{ + OrderFields: []util.OrderByParam{ + {Field: "created_at", Direction: util.DESC}, + }, + }, + }) + if err != nil { + return nil, err + } + return result, nil +} + +// Get the specified shop from the data access object. +func (a *Shop) Get(ctx context.Context, id string) (*schema.Shop, error) { + shop, err := a.ShopDAL.Get(ctx, id) + if err != nil { + return nil, err + } else if shop == nil { + return nil, errors.NotFound("", "Shop not found") + } + return shop, nil +} + +// Create a new shop in the data access object. +func (a *Shop) Create(ctx context.Context, formItem *schema.ShopForm) (*schema.Shop, error) { + shop := &schema.Shop{} + + if err := formItem.FillTo(shop); err != nil { + return nil, err + } + + err := a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.ShopDAL.Create(ctx, shop); err != nil { + return err + } + return nil + }) + if err != nil { + return nil, err + } + return shop, nil +} + +// Update the specified shop in the data access object. +func (a *Shop) Update(ctx context.Context, id string, formItem *schema.ShopForm) error { + shop, err := a.ShopDAL.Get(ctx, id) + if err != nil { + return err + } else if shop == nil { + return errors.NotFound("", "Shop not found") + } + + if err := formItem.FillTo(shop); err != nil { + return err + } + shop.UpdatedAt = time.Now() + + return a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.ShopDAL.Update(ctx, shop); err != nil { + return err + } + return nil + }) +} + +// Delete the specified shop from the data access object. +func (a *Shop) Delete(ctx context.Context, id string) error { + exists, err := a.ShopDAL.Exists(ctx, id) + if err != nil { + return err + } else if !exists { + return errors.NotFound("", "Shop not found") + } + + return a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.ShopDAL.Delete(ctx, id); err != nil { + return err + } + return nil + }) +} diff --git a/internal/mods/product/dal/order.dal.go b/internal/mods/product/dal/order.dal.go new file mode 100644 index 0000000..4b0337f --- /dev/null +++ b/internal/mods/product/dal/order.dal.go @@ -0,0 +1,83 @@ +package dal + +import ( + "context" + + "gitlab.guxuan.icu/jinshan_community/internal/mods/product/schema" + "gitlab.guxuan.icu/jinshan_community/pkg/errors" + "gitlab.guxuan.icu/jinshan_community/pkg/util" + "gorm.io/gorm" +) + +// Get order storage instance +func GetOrderDB(ctx context.Context, defDB *gorm.DB) *gorm.DB { + return util.GetDB(ctx, defDB).Model(new(schema.Order)) +} + +// Defining the `Order` data access object. +type Order struct { + DB *gorm.DB +} + +// Query orders from the database based on the provided parameters and options. +func (a *Order) Query(ctx context.Context, params schema.OrderQueryParam, opts ...schema.OrderQueryOptions) (*schema.OrderQueryResult, error) { + var opt schema.OrderQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + db := GetOrderDB(ctx, a.DB) + + var list schema.Orders + pageResult, err := util.WrapPageQuery(ctx, db, params.PaginationParam, opt.QueryOptions, &list) + if err != nil { + return nil, errors.WithStack(err) + } + + queryResult := &schema.OrderQueryResult{ + PageResult: pageResult, + Data: list, + } + return queryResult, nil +} + +// Get the specified order from the database. +func (a *Order) Get(ctx context.Context, id string, opts ...schema.OrderQueryOptions) (*schema.Order, error) { + var opt schema.OrderQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + item := new(schema.Order) + ok, err := util.FindOne(ctx, GetOrderDB(ctx, a.DB).Where("id=?", id), opt.QueryOptions, item) + if err != nil { + return nil, errors.WithStack(err) + } else if !ok { + return nil, nil + } + return item, nil +} + +// Exists checks if the specified order exists in the database. +func (a *Order) Exists(ctx context.Context, id string) (bool, error) { + ok, err := util.Exists(ctx, GetOrderDB(ctx, a.DB).Where("id=?", id)) + return ok, errors.WithStack(err) +} + +// Create a new order. +func (a *Order) Create(ctx context.Context, item *schema.Order) error { + result := GetOrderDB(ctx, a.DB).Create(item) + return errors.WithStack(result.Error) +} + +// Update the specified order in the database. +func (a *Order) Update(ctx context.Context, item *schema.Order) error { + result := GetOrderDB(ctx, a.DB).Where("id=?", item.ID).Select("*").Omit("created_at").Updates(item) + return errors.WithStack(result.Error) +} + +// Delete the specified order from the database. +func (a *Order) Delete(ctx context.Context, id string) error { + result := GetOrderDB(ctx, a.DB).Where("id=?", id).Delete(new(schema.Order)) + return errors.WithStack(result.Error) +} diff --git a/internal/mods/product/dal/product.dal.go b/internal/mods/product/dal/product.dal.go new file mode 100644 index 0000000..fd50f7b --- /dev/null +++ b/internal/mods/product/dal/product.dal.go @@ -0,0 +1,83 @@ +package dal + +import ( + "context" + + "gitlab.guxuan.icu/jinshan_community/internal/mods/product/schema" + "gitlab.guxuan.icu/jinshan_community/pkg/errors" + "gitlab.guxuan.icu/jinshan_community/pkg/util" + "gorm.io/gorm" +) + +// Get product storage instance +func GetProductDB(ctx context.Context, defDB *gorm.DB) *gorm.DB { + return util.GetDB(ctx, defDB).Model(new(schema.Product)) +} + +// Defining the `Product` data access object. +type Product struct { + DB *gorm.DB +} + +// Query products from the database based on the provided parameters and options. +func (a *Product) Query(ctx context.Context, params schema.ProductQueryParam, opts ...schema.ProductQueryOptions) (*schema.ProductQueryResult, error) { + var opt schema.ProductQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + db := GetProductDB(ctx, a.DB) + + var list schema.Products + pageResult, err := util.WrapPageQuery(ctx, db, params.PaginationParam, opt.QueryOptions, &list) + if err != nil { + return nil, errors.WithStack(err) + } + + queryResult := &schema.ProductQueryResult{ + PageResult: pageResult, + Data: list, + } + return queryResult, nil +} + +// Get the specified product from the database. +func (a *Product) Get(ctx context.Context, id 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 +} + +// Exists checks if the specified product exists in the database. +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) +} + +// Create a new product. +func (a *Product) Create(ctx context.Context, item *schema.Product) error { + result := GetProductDB(ctx, a.DB).Create(item) + return errors.WithStack(result.Error) +} + +// Update the specified product in the database. +func (a *Product) Update(ctx context.Context, item *schema.Product) error { + result := GetProductDB(ctx, a.DB).Where("id=?", item.ID).Select("*").Omit("created_at").Updates(item) + return errors.WithStack(result.Error) +} + +// Delete the specified product from the database. +func (a *Product) Delete(ctx context.Context, id string) error { + result := GetProductDB(ctx, a.DB).Where("id=?", id).Delete(new(schema.Product)) + return errors.WithStack(result.Error) +} diff --git a/internal/mods/product/dal/product_category.dal.go b/internal/mods/product/dal/product_category.dal.go new file mode 100644 index 0000000..594c48b --- /dev/null +++ b/internal/mods/product/dal/product_category.dal.go @@ -0,0 +1,83 @@ +package dal + +import ( + "context" + + "gitlab.guxuan.icu/jinshan_community/internal/mods/product/schema" + "gitlab.guxuan.icu/jinshan_community/pkg/errors" + "gitlab.guxuan.icu/jinshan_community/pkg/util" + "gorm.io/gorm" +) + +// Get product category storage instance +func GetProductCategoryDB(ctx context.Context, defDB *gorm.DB) *gorm.DB { + return util.GetDB(ctx, defDB).Model(new(schema.ProductCategory)) +} + +// Defining the `ProductCategory` data access object. +type ProductCategory struct { + DB *gorm.DB +} + +// Query product categories from the database based on the provided parameters and options. +func (a *ProductCategory) Query(ctx context.Context, params schema.ProductCategoryQueryParam, opts ...schema.ProductCategoryQueryOptions) (*schema.ProductCategoryQueryResult, error) { + var opt schema.ProductCategoryQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + db := GetProductCategoryDB(ctx, a.DB) + + var list schema.ProductCategories + pageResult, err := util.WrapPageQuery(ctx, db, params.PaginationParam, opt.QueryOptions, &list) + if err != nil { + return nil, errors.WithStack(err) + } + + queryResult := &schema.ProductCategoryQueryResult{ + PageResult: pageResult, + Data: list, + } + return queryResult, nil +} + +// Get the specified product category from the database. +func (a *ProductCategory) Get(ctx context.Context, id string, opts ...schema.ProductCategoryQueryOptions) (*schema.ProductCategory, error) { + var opt schema.ProductCategoryQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + item := new(schema.ProductCategory) + ok, err := util.FindOne(ctx, GetProductCategoryDB(ctx, a.DB).Where("id=?", id), opt.QueryOptions, item) + if err != nil { + return nil, errors.WithStack(err) + } else if !ok { + return nil, nil + } + return item, nil +} + +// Exists checks if the specified product category exists in the database. +func (a *ProductCategory) Exists(ctx context.Context, id string) (bool, error) { + ok, err := util.Exists(ctx, GetProductCategoryDB(ctx, a.DB).Where("id=?", id)) + return ok, errors.WithStack(err) +} + +// Create a new product category. +func (a *ProductCategory) Create(ctx context.Context, item *schema.ProductCategory) error { + result := GetProductCategoryDB(ctx, a.DB).Create(item) + return errors.WithStack(result.Error) +} + +// Update the specified product category in the database. +func (a *ProductCategory) Update(ctx context.Context, item *schema.ProductCategory) error { + result := GetProductCategoryDB(ctx, a.DB).Where("id=?", item.ID).Select("*").Omit("created_at").Updates(item) + return errors.WithStack(result.Error) +} + +// Delete the specified product category from the database. +func (a *ProductCategory) Delete(ctx context.Context, id string) error { + result := GetProductCategoryDB(ctx, a.DB).Where("id=?", id).Delete(new(schema.ProductCategory)) + return errors.WithStack(result.Error) +} diff --git a/internal/mods/product/dal/shop.dal.go b/internal/mods/product/dal/shop.dal.go new file mode 100644 index 0000000..55fcfa8 --- /dev/null +++ b/internal/mods/product/dal/shop.dal.go @@ -0,0 +1,83 @@ +package dal + +import ( + "context" + + "gitlab.guxuan.icu/jinshan_community/internal/mods/product/schema" + "gitlab.guxuan.icu/jinshan_community/pkg/errors" + "gitlab.guxuan.icu/jinshan_community/pkg/util" + "gorm.io/gorm" +) + +// Get shop storage instance +func GetShopDB(ctx context.Context, defDB *gorm.DB) *gorm.DB { + return util.GetDB(ctx, defDB).Model(new(schema.Shop)) +} + +// Defining the `Shop` data access object. +type Shop struct { + DB *gorm.DB +} + +// Query shops from the database based on the provided parameters and options. +func (a *Shop) Query(ctx context.Context, params schema.ShopQueryParam, opts ...schema.ShopQueryOptions) (*schema.ShopQueryResult, error) { + var opt schema.ShopQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + db := GetShopDB(ctx, a.DB) + + var list schema.Shops + pageResult, err := util.WrapPageQuery(ctx, db, params.PaginationParam, opt.QueryOptions, &list) + if err != nil { + return nil, errors.WithStack(err) + } + + queryResult := &schema.ShopQueryResult{ + PageResult: pageResult, + Data: list, + } + return queryResult, nil +} + +// Get the specified shop from the database. +func (a *Shop) Get(ctx context.Context, id string, opts ...schema.ShopQueryOptions) (*schema.Shop, error) { + var opt schema.ShopQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + item := new(schema.Shop) + ok, err := util.FindOne(ctx, GetShopDB(ctx, a.DB).Where("id=?", id), opt.QueryOptions, item) + if err != nil { + return nil, errors.WithStack(err) + } else if !ok { + return nil, nil + } + return item, nil +} + +// Exists checks if the specified shop exists in the database. +func (a *Shop) Exists(ctx context.Context, id string) (bool, error) { + ok, err := util.Exists(ctx, GetShopDB(ctx, a.DB).Where("id=?", id)) + return ok, errors.WithStack(err) +} + +// Create a new shop. +func (a *Shop) Create(ctx context.Context, item *schema.Shop) error { + result := GetShopDB(ctx, a.DB).Create(item) + return errors.WithStack(result.Error) +} + +// Update the specified shop in the database. +func (a *Shop) Update(ctx context.Context, item *schema.Shop) error { + result := GetShopDB(ctx, a.DB).Where("id=?", item.ID).Select("*").Omit("created_at").Updates(item) + return errors.WithStack(result.Error) +} + +// Delete the specified shop from the database. +func (a *Shop) Delete(ctx context.Context, id string) error { + result := GetShopDB(ctx, a.DB).Where("id=?", id).Delete(new(schema.Shop)) + return errors.WithStack(result.Error) +} diff --git a/internal/mods/product/main.go b/internal/mods/product/main.go new file mode 100644 index 0000000..d1f3ac8 --- /dev/null +++ b/internal/mods/product/main.go @@ -0,0 +1,73 @@ +package product + +import ( + "context" + + "gitlab.guxuan.icu/jinshan_community/internal/config" + + "github.com/gin-gonic/gin" + "gitlab.guxuan.icu/jinshan_community/internal/mods/product/api" + "gitlab.guxuan.icu/jinshan_community/internal/mods/product/schema" + "gorm.io/gorm" +) + +type Product struct { + DB *gorm.DB + ProductAPI *api.Product + ProductCategoryAPI *api.ProductCategory + OrderAPI *api.Order + ShopAPI *api.Shop +} + +func (a *Product) AutoMigrate(ctx context.Context) error { + return a.DB.AutoMigrate(new(schema.Product), new(schema.ProductCategory), new(schema.Order), new(schema.Shop)) +} + +func (a *Product) Init(ctx context.Context) error { + if config.C.Storage.DB.AutoMigrate { + if err := a.AutoMigrate(ctx); err != nil { + return err + } + } + return nil +} + +func (a *Product) RegisterV1Routers(ctx context.Context, v1 *gin.RouterGroup) error { + product := v1.Group("products") + { + product.GET("", a.ProductAPI.Query) + product.GET(":id", a.ProductAPI.Get) + product.POST("", a.ProductAPI.Create) + product.PUT(":id", a.ProductAPI.Update) + product.DELETE(":id", a.ProductAPI.Delete) + } + productCategory := v1.Group("product-categories") + { + productCategory.GET("", a.ProductCategoryAPI.Query) + productCategory.GET(":id", a.ProductCategoryAPI.Get) + productCategory.POST("", a.ProductCategoryAPI.Create) + productCategory.PUT(":id", a.ProductCategoryAPI.Update) + productCategory.DELETE(":id", a.ProductCategoryAPI.Delete) + } + order := v1.Group("orders") + { + order.GET("", a.OrderAPI.Query) + order.GET(":id", a.OrderAPI.Get) + order.POST("", a.OrderAPI.Create) + order.PUT(":id", a.OrderAPI.Update) + order.DELETE(":id", a.OrderAPI.Delete) + } + shop := v1.Group("shops") + { + shop.GET("", a.ShopAPI.Query) + shop.GET(":id", a.ShopAPI.Get) + shop.POST("", a.ShopAPI.Create) + shop.PUT(":id", a.ShopAPI.Update) + shop.DELETE(":id", a.ShopAPI.Delete) + } + return nil +} + +func (a *Product) Release(ctx context.Context) error { + return nil +} diff --git a/internal/mods/product/schema/order.go b/internal/mods/product/schema/order.go new file mode 100644 index 0000000..762ef87 --- /dev/null +++ b/internal/mods/product/schema/order.go @@ -0,0 +1,57 @@ +package schema + +import ( + "github.com/google/uuid" + "gitlab.guxuan.icu/jinshan_community/pkg/util" + "gorm.io/gorm" +) + +// Defining the `Order` struct. +type Order struct { + util.BaseModel + ProductID string `json:"productId" gorm:"type:char(36);index;comment:产品id"` + CustomerID string `json:"customerId" gorm:"type:char(36);index;comment:客户id"` + Num int `json:"num" gorm:"comment:商品个数"` + ShopID string `json:"shopId" gorm:"type:char(36);index;comment:店铺ID"` + Status string `json:"status" gorm:"size:20;index;comment:状态"` +} + +func (c *Order) BeforeCreate(tx *gorm.DB) (err error) { + if c.ID == "" { + c.ID = uuid.New().String() + } + return +} + +// Defining the query parameters for the `Order` struct. +type OrderQueryParam struct { + util.PaginationParam +} + +// Defining the query options for the `Order` struct. +type OrderQueryOptions struct { + util.QueryOptions +} + +// Defining the query result for the `Order` struct. +type OrderQueryResult struct { + Data Orders + PageResult *util.PaginationResult +} + +// Defining the slice of `Order` struct. +type Orders []*Order + +// Defining the data structure for creating a `Order` struct. +type OrderForm struct { +} + +// A validation function for the `OrderForm` struct. +func (a *OrderForm) Validate() error { + return nil +} + +// Convert `OrderForm` to `Order` object. +func (a *OrderForm) FillTo(order *Order) error { + return nil +} diff --git a/internal/mods/product/schema/product.go b/internal/mods/product/schema/product.go new file mode 100644 index 0000000..3ef20d1 --- /dev/null +++ b/internal/mods/product/schema/product.go @@ -0,0 +1,83 @@ +package schema + +import ( + "github.com/google/uuid" + "gitlab.guxuan.icu/jinshan_community/pkg/util" + "gorm.io/gorm" +) + +// Defining the `Product` struct. +type Product struct { + util.BaseModel + CategoryID string `json:"categoryId" gorm:"type:char(36);index;comment:分类id"` + Title string `json:"title" gorm:"size:128;not null;index;comment:标题"` + Introduce string `json:"introduce" gorm:"size:1024;comment:介绍"` + Cover string `json:"cover" gorm:"size:2048;not null;comment:封面"` + Images *[]string `json:"images" gorm:"serializer:json;comment:详图数组"` + Content string `json:"content" gorm:"type:text;not null;comment:详情"` + Num int `json:"num" gorm:"not null;comment:数量"` + Price int `json:"price" gorm:"not null;comment:价值"` + ShopID string `json:"shopId" gorm:"type:char(36);index;not null;comment:店铺ID"` + Status string `json:"status" gorm:"size:20;index;comment:状态"` +} + +func (c *Product) BeforeCreate(tx *gorm.DB) (err error) { + if c.ID == "" { + c.ID = uuid.New().String() + } + return +} + +// Defining the query parameters for the `Product` struct. +type ProductQueryParam struct { + util.PaginationParam +} + +// Defining the query options for the `Product` struct. +type ProductQueryOptions struct { + util.QueryOptions +} + +// Defining the query result for the `Product` struct. +type ProductQueryResult struct { + Data Products + PageResult *util.PaginationResult +} + +// Defining the slice of `Product` struct. +type Products []*Product + +// Defining the data structure for creating a `Product` struct. +type ProductForm struct { + CategoryID string `json:"categoryId"` + Title string `json:"title" ` + Introduce string `json:"introduce"` + Cover string `json:"cover"` + Images *[]string `json:"images" ` + Content string `json:"content"` + Num int `json:"num" ` + Price int `json:"price"` + ShopID string `json:"shopId" ` + Status string `json:"status"` +} + +// A validation function for the `ProductForm` struct. +func (a *ProductForm) Validate() error { + return nil +} + +// Convert `ProductForm` to `Product` object. +func (a *ProductForm) FillTo(product *Product) error { + product.CategoryID = a.CategoryID + product.Title = a.Title + product.Introduce = a.Introduce + product.Cover = a.Cover + product.Images = a.Images + product.Content = a.Content + product.Num = a.Num + product.Price = a.Price + product.ShopID = a.ShopID + product.Status = a.Status + + return nil +} diff --git a/internal/mods/product/schema/product_category.go b/internal/mods/product/schema/product_category.go new file mode 100644 index 0000000..a439d0c --- /dev/null +++ b/internal/mods/product/schema/product_category.go @@ -0,0 +1,61 @@ +package schema + +import ( + "github.com/google/uuid" + "gitlab.guxuan.icu/jinshan_community/pkg/util" + "gorm.io/gorm" +) + +// Defining the `ProductCategory` struct. +type ProductCategory struct { + util.BaseModel + Name string `json:"name" gorm:"size:1024;comment:名字" ` + Sequence int `json:"sequence" gorm:"index;default:0;comment:排序"` + Status string `json:"status" gorm:"size:20;index;comment:状态"` +} + +func (c *ProductCategory) BeforeCreate(tx *gorm.DB) (err error) { + if c.ID == "" { + c.ID = uuid.New().String() + } + return +} + +// Defining the query parameters for the `ProductCategory` struct. +type ProductCategoryQueryParam struct { + util.PaginationParam +} + +// Defining the query options for the `ProductCategory` struct. +type ProductCategoryQueryOptions struct { + util.QueryOptions +} + +// Defining the query result for the `ProductCategory` struct. +type ProductCategoryQueryResult struct { + Data ProductCategories + PageResult *util.PaginationResult +} + +// Defining the slice of `ProductCategory` struct. +type ProductCategories []*ProductCategory + +// Defining the data structure for creating a `ProductCategory` struct. +type ProductCategoryForm struct { + Name string `json:"name" ` + Sequence int `json:"sequence"` + Status string `json:"status" ` +} + +// A validation function for the `ProductCategoryForm` struct. +func (a *ProductCategoryForm) Validate() error { + return nil +} + +// Convert `ProductCategoryForm` to `ProductCategory` object. +func (a *ProductCategoryForm) FillTo(productCategory *ProductCategory) error { + productCategory.Name = a.Name + productCategory.Sequence = a.Sequence + productCategory.Status = a.Status + return nil +} diff --git a/internal/mods/product/schema/shop.go b/internal/mods/product/schema/shop.go new file mode 100644 index 0000000..cc88077 --- /dev/null +++ b/internal/mods/product/schema/shop.go @@ -0,0 +1,76 @@ +package schema + +import ( + "github.com/google/uuid" + "gitlab.guxuan.icu/jinshan_community/pkg/util" + "gorm.io/gorm" +) + +// Defining the `Shop` struct. +type Shop struct { + util.BaseModel + Title string `json:"title" gorm:"size:128;not null;index;comment:标题"` + Introduce string `json:"introduce" gorm:"size:1024;comment:介绍"` + Cover string `json:"cover" gorm:"size:2048;not null;comment:封面"` + Images *[]string `json:"images" gorm:"serializer:json;comment:详图数组"` + Content string `json:"content" gorm:"type:text;not null;comment:详情"` + Latitude float64 `json:"latitude" gorm:"type:float;not null;comment:纬度"` + Longitude float64 `json:"longitude" gorm:"type:float;not null;comment:经度"` + Status string `json:"status" gorm:"size:20;index;comment:状态"` +} + +func (c *Shop) BeforeCreate(tx *gorm.DB) (err error) { + if c.ID == "" { + c.ID = uuid.New().String() + } + return +} + +// Defining the query parameters for the `Shop` struct. +type ShopQueryParam struct { + util.PaginationParam +} + +// Defining the query options for the `Shop` struct. +type ShopQueryOptions struct { + util.QueryOptions +} + +// Defining the query result for the `Shop` struct. +type ShopQueryResult struct { + Data Shops + PageResult *util.PaginationResult +} + +// Defining the slice of `Shop` struct. +type Shops []*Shop + +// Defining the data structure for creating a `Shop` struct. +type ShopForm struct { + Title string `json:"title"` + Introduce string `json:"introduce" ` + Cover string `json:"cover" ` + Images *[]string `json:"images" ` + Content string `json:"content" ` + Latitude float64 `json:"latitude" ` + Longitude float64 `json:"longitude"` + Status string `json:"status" ` +} + +// A validation function for the `ShopForm` struct. +func (a *ShopForm) Validate() error { + return nil +} + +// Convert `ShopForm` to `Shop` object. +func (a *ShopForm) FillTo(shop *Shop) error { + shop.Title = a.Title + shop.Introduce = a.Introduce + shop.Cover = a.Cover + shop.Images = a.Images + shop.Content = a.Content + shop.Latitude = a.Latitude + shop.Longitude = a.Longitude + shop.Status = a.Status + return nil +} diff --git a/internal/mods/product/wire.go b/internal/mods/product/wire.go new file mode 100644 index 0000000..c5e43a0 --- /dev/null +++ b/internal/mods/product/wire.go @@ -0,0 +1,24 @@ +package product + +import ( + "github.com/google/wire" + "gitlab.guxuan.icu/jinshan_community/internal/mods/product/api" + "gitlab.guxuan.icu/jinshan_community/internal/mods/product/biz" + "gitlab.guxuan.icu/jinshan_community/internal/mods/product/dal" +) + +var Set = wire.NewSet( + wire.Struct(new(Product), "*"), + wire.Struct(new(dal.Product), "*"), + wire.Struct(new(biz.Product), "*"), + wire.Struct(new(api.Product), "*"), + wire.Struct(new(dal.ProductCategory), "*"), + wire.Struct(new(biz.ProductCategory), "*"), + wire.Struct(new(api.ProductCategory), "*"), + wire.Struct(new(dal.Order), "*"), + wire.Struct(new(biz.Order), "*"), + wire.Struct(new(api.Order), "*"), + wire.Struct(new(dal.Shop), "*"), + wire.Struct(new(biz.Shop), "*"), + wire.Struct(new(api.Shop), "*"), +) diff --git a/internal/mods/rbac/api/logger.api.go b/internal/mods/rbac/api/logger.api.go new file mode 100644 index 0000000..7a98845 --- /dev/null +++ b/internal/mods/rbac/api/logger.api.go @@ -0,0 +1,45 @@ +package api + +import ( + "gitlab.guxuan.icu/jinshan_community/internal/mods/rbac/biz" + "gitlab.guxuan.icu/jinshan_community/internal/mods/rbac/schema" + "gitlab.guxuan.icu/jinshan_community/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..44504c4 --- /dev/null +++ b/internal/mods/rbac/api/login.api.go @@ -0,0 +1,182 @@ +package api + +import ( + "gitlab.guxuan.icu/jinshan_community/internal/mods/rbac/biz" + "gitlab.guxuan.icu/jinshan_community/internal/mods/rbac/schema" + "gitlab.guxuan.icu/jinshan_community/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/menu.api.go b/internal/mods/rbac/api/menu.api.go new file mode 100644 index 0000000..2c12c2f --- /dev/null +++ b/internal/mods/rbac/api/menu.api.go @@ -0,0 +1,132 @@ +package api + +import ( + "gitlab.guxuan.icu/jinshan_community/internal/mods/rbac/biz" + "gitlab.guxuan.icu/jinshan_community/internal/mods/rbac/schema" + "gitlab.guxuan.icu/jinshan_community/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/role.api.go b/internal/mods/rbac/api/role.api.go new file mode 100644 index 0000000..807b36f --- /dev/null +++ b/internal/mods/rbac/api/role.api.go @@ -0,0 +1,133 @@ +package api + +import ( + "gitlab.guxuan.icu/jinshan_community/internal/mods/rbac/biz" + "gitlab.guxuan.icu/jinshan_community/internal/mods/rbac/schema" + "gitlab.guxuan.icu/jinshan_community/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/user.api.go b/internal/mods/rbac/api/user.api.go new file mode 100644 index 0000000..7bedbe2 --- /dev/null +++ b/internal/mods/rbac/api/user.api.go @@ -0,0 +1,152 @@ +package api + +import ( + "gitlab.guxuan.icu/jinshan_community/internal/mods/rbac/biz" + "gitlab.guxuan.icu/jinshan_community/internal/mods/rbac/schema" + "gitlab.guxuan.icu/jinshan_community/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/biz/logger.biz.go b/internal/mods/rbac/biz/logger.biz.go new file mode 100644 index 0000000..443e334 --- /dev/null +++ b/internal/mods/rbac/biz/logger.biz.go @@ -0,0 +1,31 @@ +package biz + +import ( + "context" + + "gitlab.guxuan.icu/jinshan_community/internal/mods/rbac/dal" + "gitlab.guxuan.icu/jinshan_community/internal/mods/rbac/schema" + "gitlab.guxuan.icu/jinshan_community/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..1f7f497 --- /dev/null +++ b/internal/mods/rbac/biz/login.biz.go @@ -0,0 +1,388 @@ +package biz + +import ( + "context" + "fmt" + "net/http" + "sort" + "strings" + "time" + + "github.com/LyricTian/captcha" + "github.com/gin-gonic/gin" + "gitlab.guxuan.icu/jinshan_community/internal/config" + "gitlab.guxuan.icu/jinshan_community/internal/mods/rbac/dal" + "gitlab.guxuan.icu/jinshan_community/internal/mods/rbac/schema" + "gitlab.guxuan.icu/jinshan_community/pkg/cachex" + "gitlab.guxuan.icu/jinshan_community/pkg/crypto/hash" + "gitlab.guxuan.icu/jinshan_community/pkg/errors" + "gitlab.guxuan.icu/jinshan_community/pkg/jwtx" + "gitlab.guxuan.icu/jinshan_community/pkg/logging" + "gitlab.guxuan.icu/jinshan_community/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) + + sub, err := a.Auth.ParseSubject(ctx, token) + splitStr := strings.Split(sub, ":") + if err != nil { + if err == jwtx.ErrInvalidToken { + return "", invalidToken + } + return "", err + } else if splitStr[1] == rootID { + c.Request = c.Request.WithContext(util.NewIsRootUser(ctx)) + return splitStr[1], nil + } + platform := splitStr[0] + userID := splitStr[1] + switch platform { + case "APP": + return "", invalidToken + case "SYS": + 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 + default: + return "", invalidToken + } + +} + +// 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, "Incorrect username or password") + } + + userID := config.C.General.Root.ID + ctx = logging.NewUserID(ctx, userID) + ctx = logging.NewPlatform(ctx, "SYS") + logging.Context(ctx).Info("Login by root") + return a.genUserToken(ctx, fmt.Sprintf("SYS:%s", 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) + ctx = logging.NewPlatform(ctx, "SYS") + + // 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, fmt.Sprintf("SYS:%s", 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, fmt.Sprintf("SYS:%s", 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/menu.biz.go b/internal/mods/rbac/biz/menu.biz.go new file mode 100644 index 0000000..1d04b7d --- /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" + + "gitlab.guxuan.icu/jinshan_community/internal/config" + "gitlab.guxuan.icu/jinshan_community/internal/mods/rbac/dal" + "gitlab.guxuan.icu/jinshan_community/internal/mods/rbac/schema" + "gitlab.guxuan.icu/jinshan_community/pkg/cachex" + "gitlab.guxuan.icu/jinshan_community/pkg/encoding/json" + "gitlab.guxuan.icu/jinshan_community/pkg/encoding/yaml" + "gitlab.guxuan.icu/jinshan_community/pkg/errors" + "gitlab.guxuan.icu/jinshan_community/pkg/logging" + "gitlab.guxuan.icu/jinshan_community/pkg/util" + "go.uber.org/zap" +) + +// Menu management for RBAC +type Menu struct { + Cache cachex.Cacher + Trans *util.Trans + MenuDAL *dal.Menu + MenuResourceDAL *dal.MenuResource + RoleMenuDAL *dal.RoleMenu +} + +func (a *Menu) InitFromFile(ctx context.Context, menuFile string) error { + f, err := os.ReadFile(menuFile) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + logging.Context(ctx).Warn("Menu data file not found, skip init menu data from file", zap.String("file", menuFile)) + return nil + } + return err + } + + var menus schema.Menus + if ext := filepath.Ext(menuFile); ext == ".json" { + if err := json.Unmarshal(f, &menus); err != nil { + return errors.Wrapf(err, "Unmarshal JSON file '%s' failed", menuFile) + } + } else if ext == ".yaml" || ext == ".yml" { + if err := yaml.Unmarshal(f, &menus); err != nil { + return errors.Wrapf(err, "Unmarshal YAML file '%s' failed", menuFile) + } + } else { + return errors.Errorf("Unsupported file type '%s'", ext) + } + + return a.Trans.Exec(ctx, func(ctx context.Context) error { + return a.createInBatchByParent(ctx, menus, nil) + }) +} + +func (a *Menu) createInBatchByParent(ctx context.Context, items schema.Menus, parent *schema.Menu) error { + total := len(items) + + for i, item := range items { + var parentID string + if parent != nil { + parentID = parent.ID + } + + var ( + menuItem *schema.Menu + err error + ) + + if item.ID != "" { + menuItem, err = a.MenuDAL.Get(ctx, item.ID) + } else if item.Code != "" { + menuItem, err = a.MenuDAL.GetByCodeAndParentID(ctx, item.Code, parentID) + } else if item.Name != "" { + menuItem, err = a.MenuDAL.GetByNameAndParentID(ctx, item.Name, parentID) + } + + if err != nil { + return err + } + + if item.Status == "" { + item.Status = schema.MenuStatusEnabled + } + + if menuItem != nil { + changed := false + if menuItem.Name != item.Name { + menuItem.Name = item.Name + changed = true + } + if menuItem.Description != item.Description { + menuItem.Description = item.Description + changed = true + } + if menuItem.Path != item.Path { + menuItem.Path = item.Path + changed = true + } + if menuItem.Type != item.Type { + menuItem.Type = item.Type + changed = true + } + if menuItem.Sequence != item.Sequence { + menuItem.Sequence = item.Sequence + changed = true + } + if menuItem.Status != item.Status { + menuItem.Status = item.Status + changed = true + } + if changed { + menuItem.UpdatedAt = time.Now() + if err := a.MenuDAL.Update(ctx, menuItem); err != nil { + return err + } + } + } else { + if item.ID == "" { + item.ID = util.NewXID() + } + if item.Sequence == 0 { + item.Sequence = total - i + } + item.ParentID = parentID + if parent != nil { + item.ParentPath = parent.ParentPath + parentID + util.TreePathDelimiter + } + menuItem = item + if err := a.MenuDAL.Create(ctx, item); err != nil { + return err + } + } + + for _, res := range item.Resources { + if res.ID != "" { + exists, err := a.MenuResourceDAL.Exists(ctx, res.ID) + if err != nil { + return err + } else if exists { + continue + } + } + + if res.Path != "" { + exists, err := a.MenuResourceDAL.ExistsMethodPathByMenuID(ctx, res.Method, res.Path, menuItem.ID) + if err != nil { + return err + } else if exists { + continue + } + } + if res.ID == "" { + res.ID = util.NewXID() + } + res.MenuID = menuItem.ID + if err := a.MenuResourceDAL.Create(ctx, res); err != nil { + return err + } + } + + if item.Children != nil { + if err := a.createInBatchByParent(ctx, *item.Children, menuItem); err != nil { + return err + } + } + } + return nil +} + +// Query menus from the data access object based on the provided parameters and options. +func (a *Menu) Query(ctx context.Context, params schema.MenuQueryParam) (*schema.MenuQueryResult, error) { + params.Pagination = false + + if err := a.fillQueryParam(ctx, ¶ms); err != nil { + return nil, err + } + + result, err := a.MenuDAL.Query(ctx, params, schema.MenuQueryOptions{ + QueryOptions: util.QueryOptions{ + OrderFields: schema.MenusOrderParams, + }, + }) + if err != nil { + return nil, err + } + + if params.LikeName != "" || params.CodePath != "" { + result.Data, err = a.appendChildren(ctx, result.Data) + if err != nil { + return nil, err + } + } + + if params.IncludeResources { + for i, item := range result.Data { + resResult, err := a.MenuResourceDAL.Query(ctx, schema.MenuResourceQueryParam{ + MenuID: item.ID, + }) + if err != nil { + return nil, err + } + result.Data[i].Resources = resResult.Data + } + } + + result.Data = result.Data.ToTree() + return result, nil +} + +func (a *Menu) fillQueryParam(ctx context.Context, params *schema.MenuQueryParam) error { + if params.CodePath != "" { + var ( + codes []string + lastMenu schema.Menu + ) + for _, code := range strings.Split(params.CodePath, util.TreePathDelimiter) { + if code == "" { + continue + } + codes = append(codes, code) + menu, err := a.MenuDAL.GetByCodeAndParentID(ctx, code, lastMenu.ParentID, schema.MenuQueryOptions{ + QueryOptions: util.QueryOptions{ + SelectFields: []string{"id", "parent_id", "parent_path"}, + }, + }) + if err != nil { + return err + } else if menu == nil { + return errors.NotFound("", "Menu not found by code '%s'", strings.Join(codes, util.TreePathDelimiter)) + } + lastMenu = *menu + } + params.ParentPathPrefix = lastMenu.ParentPath + lastMenu.ID + util.TreePathDelimiter + } + return nil +} + +func (a *Menu) appendChildren(ctx context.Context, data schema.Menus) (schema.Menus, error) { + if len(data) == 0 { + return data, nil + } + + existsInData := func(id string) bool { + for _, item := range data { + if item.ID == id { + return true + } + } + return false + } + + for _, item := range data { + childResult, err := a.MenuDAL.Query(ctx, schema.MenuQueryParam{ + ParentPathPrefix: item.ParentPath + item.ID + util.TreePathDelimiter, + }) + if err != nil { + return nil, err + } + for _, child := range childResult.Data { + if existsInData(child.ID) { + continue + } + data = append(data, child) + } + } + + if parentIDs := data.SplitParentIDs(); len(parentIDs) > 0 { + parentResult, err := a.MenuDAL.Query(ctx, schema.MenuQueryParam{ + InIDs: parentIDs, + }) + if err != nil { + return nil, err + } + for _, p := range parentResult.Data { + if existsInData(p.ID) { + continue + } + data = append(data, p) + } + } + sort.Sort(data) + + return data, nil +} + +// Get the specified menu from the data access object. +func (a *Menu) Get(ctx context.Context, id string) (*schema.Menu, error) { + menu, err := a.MenuDAL.Get(ctx, id) + if err != nil { + return nil, err + } else if menu == nil { + return nil, errors.NotFound("", "Menu not found") + } + + menuResResult, err := a.MenuResourceDAL.Query(ctx, schema.MenuResourceQueryParam{ + MenuID: menu.ID, + }) + if err != nil { + return nil, err + } + menu.Resources = menuResResult.Data + + return menu, nil +} + +// Create a new menu in the data access object. +func (a *Menu) Create(ctx context.Context, formItem *schema.MenuForm) (*schema.Menu, error) { + if config.C.General.DenyOperateMenu { + return nil, errors.BadRequest("", "Menu creation is not allowed") + } + + menu := &schema.Menu{ + ID: util.NewXID(), + CreatedAt: time.Now(), + } + + if parentID := formItem.ParentID; parentID != "" { + parent, err := a.MenuDAL.Get(ctx, parentID) + if err != nil { + return nil, err + } else if parent == nil { + return nil, errors.NotFound("", "Parent not found") + } + menu.ParentPath = parent.ParentPath + parent.ID + util.TreePathDelimiter + } + + if exists, err := a.MenuDAL.ExistsCodeByParentID(ctx, formItem.Code, formItem.ParentID); err != nil { + return nil, err + } else if exists { + return nil, errors.BadRequest("", "Menu code already exists at the same level") + } + + if err := formItem.FillTo(menu); err != nil { + return nil, err + } + + err := a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.MenuDAL.Create(ctx, menu); err != nil { + return err + } + + for _, res := range formItem.Resources { + res.ID = util.NewXID() + res.MenuID = menu.ID + res.CreatedAt = time.Now() + if err := a.MenuResourceDAL.Create(ctx, res); err != nil { + return err + } + } + + return nil + }) + if err != nil { + return nil, err + } + return menu, nil +} + +// Update the specified menu in the data access object. +func (a *Menu) Update(ctx context.Context, id string, formItem *schema.MenuForm) error { + if config.C.General.DenyOperateMenu { + return errors.BadRequest("", "Menu update is not allowed") + } + + menu, err := a.MenuDAL.Get(ctx, id) + if err != nil { + return err + } else if menu == nil { + return errors.NotFound("", "Menu not found") + } + + oldParentPath := menu.ParentPath + oldStatus := menu.Status + var childData schema.Menus + if menu.ParentID != formItem.ParentID { + if parentID := formItem.ParentID; parentID != "" { + parent, err := a.MenuDAL.Get(ctx, parentID) + if err != nil { + return err + } else if parent == nil { + return errors.NotFound("", "Parent not found") + } + menu.ParentPath = parent.ParentPath + parent.ID + util.TreePathDelimiter + } else { + menu.ParentPath = "" + } + + childResult, err := a.MenuDAL.Query(ctx, schema.MenuQueryParam{ + ParentPathPrefix: oldParentPath + menu.ID + util.TreePathDelimiter, + }, schema.MenuQueryOptions{ + QueryOptions: util.QueryOptions{ + SelectFields: []string{"id", "parent_path"}, + }, + }) + if err != nil { + return err + } + childData = childResult.Data + } + + if menu.Code != formItem.Code { + if exists, err := a.MenuDAL.ExistsCodeByParentID(ctx, formItem.Code, formItem.ParentID); err != nil { + return err + } else if exists { + return errors.BadRequest("", "Menu code already exists at the same level") + } + } + + if err := formItem.FillTo(menu); err != nil { + return err + } + + return a.Trans.Exec(ctx, func(ctx context.Context) error { + if oldStatus != formItem.Status { + oldPath := oldParentPath + menu.ID + util.TreePathDelimiter + if err := a.MenuDAL.UpdateStatusByParentPath(ctx, oldPath, formItem.Status); err != nil { + return err + } + } + + for _, child := range childData { + oldPath := oldParentPath + menu.ID + util.TreePathDelimiter + newPath := menu.ParentPath + menu.ID + util.TreePathDelimiter + err := a.MenuDAL.UpdateParentPath(ctx, child.ID, strings.Replace(child.ParentPath, oldPath, newPath, 1)) + if err != nil { + return err + } + } + + if err := a.MenuDAL.Update(ctx, menu); err != nil { + return err + } + + if err := a.MenuResourceDAL.DeleteByMenuID(ctx, id); err != nil { + return err + } + for _, res := range formItem.Resources { + if res.ID == "" { + res.ID = util.NewXID() + } + res.MenuID = id + if res.CreatedAt.IsZero() { + res.CreatedAt = time.Now() + } + res.UpdatedAt = time.Now() + if err := a.MenuResourceDAL.Create(ctx, res); err != nil { + return err + } + } + + return a.syncToCasbin(ctx) + }) +} + +// Delete the specified menu from the data access object. +func (a *Menu) Delete(ctx context.Context, id string) error { + if config.C.General.DenyOperateMenu { + return errors.BadRequest("", "Menu deletion is not allowed") + } + + menu, err := a.MenuDAL.Get(ctx, id) + if err != nil { + return err + } else if menu == nil { + return errors.NotFound("", "Menu not found") + } + + childResult, err := a.MenuDAL.Query(ctx, schema.MenuQueryParam{ + ParentPathPrefix: menu.ParentPath + menu.ID + util.TreePathDelimiter, + }, schema.MenuQueryOptions{ + QueryOptions: util.QueryOptions{ + SelectFields: []string{"id"}, + }, + }) + if err != nil { + return err + } + + return a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.delete(ctx, id); err != nil { + return err + } + + for _, child := range childResult.Data { + if err := a.delete(ctx, child.ID); err != nil { + return err + } + } + + return a.syncToCasbin(ctx) + }) +} + +func (a *Menu) delete(ctx context.Context, id string) error { + if err := a.MenuDAL.Delete(ctx, id); err != nil { + return err + } + if err := a.MenuResourceDAL.DeleteByMenuID(ctx, id); err != nil { + return err + } + if err := a.RoleMenuDAL.DeleteByMenuID(ctx, id); err != nil { + return err + } + return nil +} + +func (a *Menu) syncToCasbin(ctx context.Context) error { + return a.Cache.Set(ctx, config.CacheNSForRole, config.CacheKeyForSyncToCasbin, fmt.Sprintf("%d", time.Now().Unix())) +} diff --git a/internal/mods/rbac/biz/role.biz.go b/internal/mods/rbac/biz/role.biz.go new file mode 100644 index 0000000..e55b957 --- /dev/null +++ b/internal/mods/rbac/biz/role.biz.go @@ -0,0 +1,179 @@ +package biz + +import ( + "context" + "fmt" + "time" + + "gitlab.guxuan.icu/jinshan_community/internal/config" + "gitlab.guxuan.icu/jinshan_community/internal/mods/rbac/dal" + "gitlab.guxuan.icu/jinshan_community/internal/mods/rbac/schema" + "gitlab.guxuan.icu/jinshan_community/pkg/cachex" + "gitlab.guxuan.icu/jinshan_community/pkg/errors" + "gitlab.guxuan.icu/jinshan_community/pkg/util" +) + +// Role management for RBAC +type Role struct { + Cache cachex.Cacher + Trans *util.Trans + RoleDAL *dal.Role + RoleMenuDAL *dal.RoleMenu + UserRoleDAL *dal.UserRole +} + +// Query roles from the data access object based on the provided parameters and options. +func (a *Role) Query(ctx context.Context, params schema.RoleQueryParam) (*schema.RoleQueryResult, error) { + params.Pagination = true + + var selectFields []string + if params.ResultType == schema.RoleResultTypeSelect { + params.Pagination = false + selectFields = []string{"id", "name"} + } + + result, err := a.RoleDAL.Query(ctx, params, schema.RoleQueryOptions{ + QueryOptions: util.QueryOptions{ + OrderFields: []util.OrderByParam{ + {Field: "sequence", Direction: util.DESC}, + {Field: "created_at", Direction: util.DESC}, + }, + SelectFields: selectFields, + }, + }) + if err != nil { + return nil, err + } + return result, nil +} + +// Get the specified role from the data access object. +func (a *Role) Get(ctx context.Context, id string) (*schema.Role, error) { + role, err := a.RoleDAL.Get(ctx, id) + if err != nil { + return nil, err + } else if role == nil { + return nil, errors.NotFound("", "Role not found") + } + + roleMenuResult, err := a.RoleMenuDAL.Query(ctx, schema.RoleMenuQueryParam{ + RoleID: id, + }) + if err != nil { + return nil, err + } + role.Menus = roleMenuResult.Data + + return role, nil +} + +// Create a new role in the data access object. +func (a *Role) Create(ctx context.Context, formItem *schema.RoleForm) (*schema.Role, error) { + if exists, err := a.RoleDAL.ExistsCode(ctx, formItem.Code); err != nil { + return nil, err + } else if exists { + return nil, errors.BadRequest("", "Role code already exists") + } + + role := &schema.Role{ + ID: util.NewXID(), + CreatedAt: time.Now(), + } + if err := formItem.FillTo(role); err != nil { + return nil, err + } + + err := a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.RoleDAL.Create(ctx, role); err != nil { + return err + } + + for _, roleMenu := range formItem.Menus { + roleMenu.ID = util.NewXID() + roleMenu.RoleID = role.ID + roleMenu.CreatedAt = time.Now() + if err := a.RoleMenuDAL.Create(ctx, roleMenu); err != nil { + return err + } + } + return a.syncToCasbin(ctx) + }) + if err != nil { + return nil, err + } + role.Menus = formItem.Menus + + return role, nil +} + +// Update the specified role in the data access object. +func (a *Role) Update(ctx context.Context, id string, formItem *schema.RoleForm) error { + role, err := a.RoleDAL.Get(ctx, id) + if err != nil { + return err + } else if role == nil { + return errors.NotFound("", "Role not found") + } else if role.Code != formItem.Code { + if exists, err := a.RoleDAL.ExistsCode(ctx, formItem.Code); err != nil { + return err + } else if exists { + return errors.BadRequest("", "Role code already exists") + } + } + + if err := formItem.FillTo(role); err != nil { + return err + } + role.UpdatedAt = time.Now() + + return a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.RoleDAL.Update(ctx, role); err != nil { + return err + } + if err := a.RoleMenuDAL.DeleteByRoleID(ctx, id); err != nil { + return err + } + for _, roleMenu := range formItem.Menus { + if roleMenu.ID == "" { + roleMenu.ID = util.NewXID() + } + roleMenu.RoleID = role.ID + if roleMenu.CreatedAt.IsZero() { + roleMenu.CreatedAt = time.Now() + } + roleMenu.UpdatedAt = time.Now() + if err := a.RoleMenuDAL.Create(ctx, roleMenu); err != nil { + return err + } + } + return a.syncToCasbin(ctx) + }) +} + +// Delete the specified role from the data access object. +func (a *Role) Delete(ctx context.Context, id string) error { + exists, err := a.RoleDAL.Exists(ctx, id) + if err != nil { + return err + } else if !exists { + return errors.NotFound("", "Role not found") + } + + return a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.RoleDAL.Delete(ctx, id); err != nil { + return err + } + if err := a.RoleMenuDAL.DeleteByRoleID(ctx, id); err != nil { + return err + } + if err := a.UserRoleDAL.DeleteByRoleID(ctx, id); err != nil { + return err + } + + return a.syncToCasbin(ctx) + }) +} + +func (a *Role) syncToCasbin(ctx context.Context) error { + return a.Cache.Set(ctx, config.CacheNSForRole, config.CacheKeyForSyncToCasbin, fmt.Sprintf("%d", time.Now().Unix())) +} diff --git a/internal/mods/rbac/biz/user.biz.go b/internal/mods/rbac/biz/user.biz.go new file mode 100644 index 0000000..5dbe4de --- /dev/null +++ b/internal/mods/rbac/biz/user.biz.go @@ -0,0 +1,227 @@ +package biz + +import ( + "context" + "time" + + "gitlab.guxuan.icu/jinshan_community/internal/config" + "gitlab.guxuan.icu/jinshan_community/internal/mods/rbac/dal" + "gitlab.guxuan.icu/jinshan_community/internal/mods/rbac/schema" + "gitlab.guxuan.icu/jinshan_community/pkg/cachex" + "gitlab.guxuan.icu/jinshan_community/pkg/crypto/hash" + "gitlab.guxuan.icu/jinshan_community/pkg/errors" + "gitlab.guxuan.icu/jinshan_community/pkg/util" +) + +// User management for RBAC +type User struct { + Cache cachex.Cacher + Trans *util.Trans + UserDAL *dal.User + UserRoleDAL *dal.UserRole +} + +// Query users from the data access object based on the provided parameters and options. +func (a *User) Query(ctx context.Context, params schema.UserQueryParam) (*schema.UserQueryResult, error) { + params.Pagination = true + + result, err := a.UserDAL.Query(ctx, params, schema.UserQueryOptions{ + QueryOptions: util.QueryOptions{ + OrderFields: []util.OrderByParam{ + {Field: "created_at", Direction: util.DESC}, + }, + OmitFields: []string{"password"}, + }, + }) + if err != nil { + return nil, err + } + + if userIDs := result.Data.ToIDs(); len(userIDs) > 0 { + userRoleResult, err := a.UserRoleDAL.Query(ctx, schema.UserRoleQueryParam{ + InUserIDs: userIDs, + }, schema.UserRoleQueryOptions{ + JoinRole: true, + }) + if err != nil { + return nil, err + } + userRolesMap := userRoleResult.Data.ToUserIDMap() + for _, user := range result.Data { + user.Roles = userRolesMap[user.ID] + } + } + + return result, nil +} + +// Get the specified user from the data access object. +func (a *User) Get(ctx context.Context, id string) (*schema.User, error) { + user, err := a.UserDAL.Get(ctx, id, schema.UserQueryOptions{ + QueryOptions: util.QueryOptions{ + OmitFields: []string{"password"}, + }, + }) + if err != nil { + return nil, err + } else if user == nil { + return nil, errors.NotFound("", "User not found") + } + + userRoleResult, err := a.UserRoleDAL.Query(ctx, schema.UserRoleQueryParam{ + UserID: id, + }) + if err != nil { + return nil, err + } + user.Roles = userRoleResult.Data + + return user, nil +} + +// Create a new user in the data access object. +func (a *User) Create(ctx context.Context, formItem *schema.UserForm) (*schema.User, error) { + existsUsername, err := a.UserDAL.ExistsUsername(ctx, formItem.Username) + if err != nil { + return nil, err + } else if existsUsername { + return nil, errors.BadRequest("", "Username already exists") + } + + user := &schema.User{ + ID: util.NewXID(), + CreatedAt: time.Now(), + } + + if formItem.Password == "" { + formItem.Password = config.C.General.DefaultLoginPwd + } + + if err := formItem.FillTo(user); err != nil { + return nil, err + } + + err = a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.UserDAL.Create(ctx, user); err != nil { + return err + } + + for _, userRole := range formItem.Roles { + userRole.ID = util.NewXID() + userRole.UserID = user.ID + userRole.CreatedAt = time.Now() + if err := a.UserRoleDAL.Create(ctx, userRole); err != nil { + return err + } + } + return nil + }) + if err != nil { + return nil, err + } + user.Roles = formItem.Roles + + return user, nil +} + +// Update the specified user in the data access object. +func (a *User) Update(ctx context.Context, id string, formItem *schema.UserForm) error { + user, err := a.UserDAL.Get(ctx, id) + if err != nil { + return err + } else if user == nil { + return errors.NotFound("", "User not found") + } else if user.Username != formItem.Username { + existsUsername, err := a.UserDAL.ExistsUsername(ctx, formItem.Username) + if err != nil { + return err + } else if existsUsername { + return errors.BadRequest("", "Username already exists") + } + } + + if err := formItem.FillTo(user); err != nil { + return err + } + user.UpdatedAt = time.Now() + + return a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.UserDAL.Update(ctx, user); err != nil { + return err + } + + if err := a.UserRoleDAL.DeleteByUserID(ctx, id); err != nil { + return err + } + for _, userRole := range formItem.Roles { + if userRole.ID == "" { + userRole.ID = util.NewXID() + } + userRole.UserID = user.ID + if userRole.CreatedAt.IsZero() { + userRole.CreatedAt = time.Now() + } + userRole.UpdatedAt = time.Now() + if err := a.UserRoleDAL.Create(ctx, userRole); err != nil { + return err + } + } + + return a.Cache.Delete(ctx, config.CacheNSForUser, id) + }) +} + +// Delete the specified user from the data access object. +func (a *User) Delete(ctx context.Context, id string) error { + exists, err := a.UserDAL.Exists(ctx, id) + if err != nil { + return err + } else if !exists { + return errors.NotFound("", "User not found") + } + + return a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.UserDAL.Delete(ctx, id); err != nil { + return err + } + if err := a.UserRoleDAL.DeleteByUserID(ctx, id); err != nil { + return err + } + return a.Cache.Delete(ctx, config.CacheNSForUser, id) + }) +} + +func (a *User) ResetPassword(ctx context.Context, id string) error { + exists, err := a.UserDAL.Exists(ctx, id) + if err != nil { + return err + } else if !exists { + return errors.NotFound("", "User not found") + } + + hashPass, err := hash.GeneratePassword(config.C.General.DefaultLoginPwd) + if err != nil { + return errors.BadRequest("", "Failed to generate hash password: %s", err.Error()) + } + + return a.Trans.Exec(ctx, func(ctx context.Context) error { + if err := a.UserDAL.UpdatePasswordByID(ctx, id, hashPass); err != nil { + return err + } + return nil + }) +} + +func (a *User) GetRoleIDs(ctx context.Context, id string) ([]string, error) { + userRoleResult, err := a.UserRoleDAL.Query(ctx, schema.UserRoleQueryParam{ + UserID: id, + }, schema.UserRoleQueryOptions{ + QueryOptions: util.QueryOptions{ + SelectFields: []string{"role_id"}, + }, + }) + if err != nil { + return nil, err + } + return userRoleResult.Data.ToRoleIDs(), nil +} diff --git a/internal/mods/rbac/casbin.go b/internal/mods/rbac/casbin.go new file mode 100644 index 0000000..35840fc --- /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" + + "gitlab.guxuan.icu/jinshan_community/internal/config" + "gitlab.guxuan.icu/jinshan_community/internal/mods/rbac/dal" + "gitlab.guxuan.icu/jinshan_community/internal/mods/rbac/schema" + "gitlab.guxuan.icu/jinshan_community/pkg/cachex" + "gitlab.guxuan.icu/jinshan_community/pkg/logging" + "gitlab.guxuan.icu/jinshan_community/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/logger.dal.go b/internal/mods/rbac/dal/logger.dal.go new file mode 100644 index 0000000..5cdc68b --- /dev/null +++ b/internal/mods/rbac/dal/logger.dal.go @@ -0,0 +1,64 @@ +package dal + +import ( + "context" + "fmt" + + "gitlab.guxuan.icu/jinshan_community/internal/mods/rbac/schema" + "gitlab.guxuan.icu/jinshan_community/pkg/errors" + "gitlab.guxuan.icu/jinshan_community/pkg/util" + "gorm.io/gorm" +) + +// Get logger storage instance +func GetLoggerDB(ctx context.Context, defDB *gorm.DB) *gorm.DB { + return util.GetDB(ctx, defDB).Model(new(schema.Logger)) +} + +// Logger management +type Logger struct { + DB *gorm.DB +} + +// Query loggers from the database based on the provided parameters and options. +func (a *Logger) Query(ctx context.Context, params schema.LoggerQueryParam, opts ...schema.LoggerQueryOptions) (*schema.LoggerQueryResult, error) { + var opt schema.LoggerQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + db := a.DB.Table(fmt.Sprintf("%s AS a", new(schema.Logger).TableName())) + db = db.Joins(fmt.Sprintf("left join %s b on a.user_id=b.id", new(schema.User).TableName())) + db = db.Select("a.*,b.name as user_name,b.username as login_name") + + if v := params.Level; v != "" { + db = db.Where("a.level = ?", v) + } + if v := params.LikeMessage; len(v) > 0 { + db = db.Where("a.message LIKE ?", "%"+v+"%") + } + if v := params.TraceID; v != "" { + db = db.Where("a.trace_id = ?", v) + } + if v := params.LikeUserName; v != "" { + db = db.Where("b.username LIKE ?", "%"+v+"%") + } + if v := params.Tag; v != "" { + db = db.Where("a.tag = ?", v) + } + if start, end := params.StartTime, params.EndTime; start != "" && end != "" { + db = db.Where("a.created_at BETWEEN ? AND ?", start, end) + } + + var list schema.Loggers + pageResult, err := util.WrapPageQuery(ctx, db, params.PaginationParam, opt.QueryOptions, &list) + if err != nil { + return nil, errors.WithStack(err) + } + + queryResult := &schema.LoggerQueryResult{ + PageResult: pageResult, + Data: list, + } + return queryResult, nil +} diff --git a/internal/mods/rbac/dal/menu.dal.go b/internal/mods/rbac/dal/menu.dal.go new file mode 100644 index 0000000..5f9e567 --- /dev/null +++ b/internal/mods/rbac/dal/menu.dal.go @@ -0,0 +1,165 @@ +package dal + +import ( + "context" + + "gitlab.guxuan.icu/jinshan_community/internal/mods/rbac/schema" + "gitlab.guxuan.icu/jinshan_community/pkg/errors" + "gitlab.guxuan.icu/jinshan_community/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..8a1163e --- /dev/null +++ b/internal/mods/rbac/dal/menu_resource.dal.go @@ -0,0 +1,101 @@ +package dal + +import ( + "context" + + "gitlab.guxuan.icu/jinshan_community/internal/mods/rbac/schema" + "gitlab.guxuan.icu/jinshan_community/pkg/errors" + "gitlab.guxuan.icu/jinshan_community/pkg/util" + "gorm.io/gorm" +) + +// Get menu resource storage instance +func GetMenuResourceDB(ctx context.Context, defDB *gorm.DB) *gorm.DB { + return util.GetDB(ctx, defDB).Model(new(schema.MenuResource)) +} + +// Menu resource management for RBAC +type MenuResource struct { + DB *gorm.DB +} + +// Query menu resources from the database based on the provided parameters and options. +func (a *MenuResource) Query(ctx context.Context, params schema.MenuResourceQueryParam, opts ...schema.MenuResourceQueryOptions) (*schema.MenuResourceQueryResult, error) { + var opt schema.MenuResourceQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + db := GetMenuResourceDB(ctx, a.DB) + if v := params.MenuID; len(v) > 0 { + db = db.Where("menu_id = ?", v) + } + if v := params.MenuIDs; len(v) > 0 { + db = db.Where("menu_id IN ?", v) + } + + var list schema.MenuResources + pageResult, err := util.WrapPageQuery(ctx, db, params.PaginationParam, opt.QueryOptions, &list) + if err != nil { + return nil, errors.WithStack(err) + } + + queryResult := &schema.MenuResourceQueryResult{ + PageResult: pageResult, + Data: list, + } + return queryResult, nil +} + +// Get the specified menu resource from the database. +func (a *MenuResource) Get(ctx context.Context, id string, opts ...schema.MenuResourceQueryOptions) (*schema.MenuResource, error) { + var opt schema.MenuResourceQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + item := new(schema.MenuResource) + ok, err := util.FindOne(ctx, GetMenuResourceDB(ctx, a.DB).Where("id=?", id), opt.QueryOptions, item) + if err != nil { + return nil, errors.WithStack(err) + } else if !ok { + return nil, nil + } + return item, nil +} + +// Exist checks if the specified menu resource exists in the database. +func (a *MenuResource) Exists(ctx context.Context, id string) (bool, error) { + ok, err := util.Exists(ctx, GetMenuResourceDB(ctx, a.DB).Where("id=?", id)) + return ok, errors.WithStack(err) +} + +// ExistsMethodPathByMenuID checks if the specified menu resource exists in the database. +func (a *MenuResource) ExistsMethodPathByMenuID(ctx context.Context, method, path, menuID string) (bool, error) { + ok, err := util.Exists(ctx, GetMenuResourceDB(ctx, a.DB).Where("method=? AND path=? AND menu_id=?", method, path, menuID)) + return ok, errors.WithStack(err) +} + +// Create a new menu resource. +func (a *MenuResource) Create(ctx context.Context, item *schema.MenuResource) error { + result := GetMenuResourceDB(ctx, a.DB).Create(item) + return errors.WithStack(result.Error) +} + +// Update the specified menu resource in the database. +func (a *MenuResource) Update(ctx context.Context, item *schema.MenuResource) error { + result := GetMenuResourceDB(ctx, a.DB).Where("id=?", item.ID).Select("*").Omit("created_at").Updates(item) + return errors.WithStack(result.Error) +} + +// Delete the specified menu resource from the database. +func (a *MenuResource) Delete(ctx context.Context, id string) error { + result := GetMenuResourceDB(ctx, a.DB).Where("id=?", id).Delete(new(schema.MenuResource)) + return errors.WithStack(result.Error) +} + +// Deletes the menu resource by menu id. +func (a *MenuResource) DeleteByMenuID(ctx context.Context, menuID string) error { + result := GetMenuResourceDB(ctx, a.DB).Where("menu_id=?", menuID).Delete(new(schema.MenuResource)) + return errors.WithStack(result.Error) +} diff --git a/internal/mods/rbac/dal/role.dal.go b/internal/mods/rbac/dal/role.dal.go new file mode 100644 index 0000000..cf51e53 --- /dev/null +++ b/internal/mods/rbac/dal/role.dal.go @@ -0,0 +1,100 @@ +package dal + +import ( + "context" + + "gitlab.guxuan.icu/jinshan_community/internal/mods/rbac/schema" + "gitlab.guxuan.icu/jinshan_community/pkg/errors" + "gitlab.guxuan.icu/jinshan_community/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..f6877c7 --- /dev/null +++ b/internal/mods/rbac/dal/role_menu.dal.go @@ -0,0 +1,98 @@ +package dal + +import ( + "context" + + "gitlab.guxuan.icu/jinshan_community/internal/mods/rbac/schema" + "gitlab.guxuan.icu/jinshan_community/pkg/errors" + "gitlab.guxuan.icu/jinshan_community/pkg/util" + "gorm.io/gorm" +) + +// Get role menu storage instance +func GetRoleMenuDB(ctx context.Context, defDB *gorm.DB) *gorm.DB { + return util.GetDB(ctx, defDB).Model(new(schema.RoleMenu)) +} + +// Role permissions for RBAC +type RoleMenu struct { + DB *gorm.DB +} + +// Query role menus from the database based on the provided parameters and options. +func (a *RoleMenu) Query(ctx context.Context, params schema.RoleMenuQueryParam, opts ...schema.RoleMenuQueryOptions) (*schema.RoleMenuQueryResult, error) { + var opt schema.RoleMenuQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + db := GetRoleMenuDB(ctx, a.DB) + if v := params.RoleID; len(v) > 0 { + db = db.Where("role_id = ?", v) + } + + var list schema.RoleMenus + pageResult, err := util.WrapPageQuery(ctx, db, params.PaginationParam, opt.QueryOptions, &list) + if err != nil { + return nil, errors.WithStack(err) + } + + queryResult := &schema.RoleMenuQueryResult{ + PageResult: pageResult, + Data: list, + } + return queryResult, nil +} + +// Get the specified role menu from the database. +func (a *RoleMenu) Get(ctx context.Context, id string, opts ...schema.RoleMenuQueryOptions) (*schema.RoleMenu, error) { + var opt schema.RoleMenuQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + item := new(schema.RoleMenu) + ok, err := util.FindOne(ctx, GetRoleMenuDB(ctx, a.DB).Where("id=?", id), opt.QueryOptions, item) + if err != nil { + return nil, errors.WithStack(err) + } else if !ok { + return nil, nil + } + return item, nil +} + +// Exist checks if the specified role menu exists in the database. +func (a *RoleMenu) Exists(ctx context.Context, id string) (bool, error) { + ok, err := util.Exists(ctx, GetRoleMenuDB(ctx, a.DB).Where("id=?", id)) + return ok, errors.WithStack(err) +} + +// Create a new role menu. +func (a *RoleMenu) Create(ctx context.Context, item *schema.RoleMenu) error { + result := GetRoleMenuDB(ctx, a.DB).Create(item) + return errors.WithStack(result.Error) +} + +// Update the specified role menu in the database. +func (a *RoleMenu) Update(ctx context.Context, item *schema.RoleMenu) error { + result := GetRoleMenuDB(ctx, a.DB).Where("id=?", item.ID).Select("*").Omit("created_at").Updates(item) + return errors.WithStack(result.Error) +} + +// Delete the specified role menu from the database. +func (a *RoleMenu) Delete(ctx context.Context, id string) error { + result := GetRoleMenuDB(ctx, a.DB).Where("id=?", id).Delete(new(schema.RoleMenu)) + return errors.WithStack(result.Error) +} + +// Deletes role menus by role id. +func (a *RoleMenu) DeleteByRoleID(ctx context.Context, roleID string) error { + result := GetRoleMenuDB(ctx, a.DB).Where("role_id=?", roleID).Delete(new(schema.RoleMenu)) + return errors.WithStack(result.Error) +} + +// Deletes role menus by menu id. +func (a *RoleMenu) DeleteByMenuID(ctx context.Context, menuID string) error { + result := GetRoleMenuDB(ctx, a.DB).Where("menu_id=?", menuID).Delete(new(schema.RoleMenu)) + return errors.WithStack(result.Error) +} diff --git a/internal/mods/rbac/dal/user.dal.go b/internal/mods/rbac/dal/user.dal.go new file mode 100644 index 0000000..9fcb24a --- /dev/null +++ b/internal/mods/rbac/dal/user.dal.go @@ -0,0 +1,124 @@ +package dal + +import ( + "context" + + "gitlab.guxuan.icu/jinshan_community/internal/mods/rbac/schema" + "gitlab.guxuan.icu/jinshan_community/pkg/errors" + "gitlab.guxuan.icu/jinshan_community/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..5cb53af --- /dev/null +++ b/internal/mods/rbac/dal/user_role.dal.go @@ -0,0 +1,108 @@ +package dal + +import ( + "context" + "fmt" + + "gitlab.guxuan.icu/jinshan_community/internal/mods/rbac/schema" + "gitlab.guxuan.icu/jinshan_community/pkg/errors" + "gitlab.guxuan.icu/jinshan_community/pkg/util" + "gorm.io/gorm" +) + +// Get user role storage instance +func GetUserRoleDB(ctx context.Context, defDB *gorm.DB) *gorm.DB { + return util.GetDB(ctx, defDB).Model(new(schema.UserRole)) +} + +// User roles for RBAC +type UserRole struct { + DB *gorm.DB +} + +// Query user roles from the database based on the provided parameters and options. +func (a *UserRole) Query(ctx context.Context, params schema.UserRoleQueryParam, opts ...schema.UserRoleQueryOptions) (*schema.UserRoleQueryResult, error) { + var opt schema.UserRoleQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + db := a.DB.Table(fmt.Sprintf("%s AS a", new(schema.UserRole).TableName())) + if opt.JoinRole { + db = db.Joins(fmt.Sprintf("left join %s b on a.role_id=b.id", new(schema.Role).TableName())) + db = db.Select("a.*,b.name as role_name") + } + + if v := params.InUserIDs; len(v) > 0 { + db = db.Where("a.user_id IN (?)", v) + } + if v := params.UserID; len(v) > 0 { + db = db.Where("a.user_id = ?", v) + } + if v := params.RoleID; len(v) > 0 { + db = db.Where("a.role_id = ?", v) + } + + var list schema.UserRoles + pageResult, err := util.WrapPageQuery(ctx, db, params.PaginationParam, opt.QueryOptions, &list) + if err != nil { + return nil, errors.WithStack(err) + } + + queryResult := &schema.UserRoleQueryResult{ + PageResult: pageResult, + Data: list, + } + return queryResult, nil +} + +// Get the specified user role from the database. +func (a *UserRole) Get(ctx context.Context, id string, opts ...schema.UserRoleQueryOptions) (*schema.UserRole, error) { + var opt schema.UserRoleQueryOptions + if len(opts) > 0 { + opt = opts[0] + } + + item := new(schema.UserRole) + ok, err := util.FindOne(ctx, GetUserRoleDB(ctx, a.DB).Where("id=?", id), opt.QueryOptions, item) + if err != nil { + return nil, errors.WithStack(err) + } else if !ok { + return nil, nil + } + return item, nil +} + +// Exist checks if the specified user role exists in the database. +func (a *UserRole) Exists(ctx context.Context, id string) (bool, error) { + ok, err := util.Exists(ctx, GetUserRoleDB(ctx, a.DB).Where("id=?", id)) + return ok, errors.WithStack(err) +} + +// Create a new user role. +func (a *UserRole) Create(ctx context.Context, item *schema.UserRole) error { + result := GetUserRoleDB(ctx, a.DB).Create(item) + return errors.WithStack(result.Error) +} + +// Update the specified user role in the database. +func (a *UserRole) Update(ctx context.Context, item *schema.UserRole) error { + result := GetUserRoleDB(ctx, a.DB).Where("id=?", item.ID).Select("*").Omit("created_at").Updates(item) + return errors.WithStack(result.Error) +} + +// Delete the specified user role from the database. +func (a *UserRole) Delete(ctx context.Context, id string) error { + result := GetUserRoleDB(ctx, a.DB).Where("id=?", id).Delete(new(schema.UserRole)) + return errors.WithStack(result.Error) +} + +func (a *UserRole) DeleteByUserID(ctx context.Context, userID string) error { + result := GetUserRoleDB(ctx, a.DB).Where("user_id=?", userID).Delete(new(schema.UserRole)) + return errors.WithStack(result.Error) +} + +func (a *UserRole) DeleteByRoleID(ctx context.Context, roleID string) error { + result := GetUserRoleDB(ctx, a.DB).Where("role_id=?", roleID).Delete(new(schema.UserRole)) + return errors.WithStack(result.Error) +} diff --git a/internal/mods/rbac/main.go b/internal/mods/rbac/main.go new file mode 100644 index 0000000..d7267a8 --- /dev/null +++ b/internal/mods/rbac/main.go @@ -0,0 +1,118 @@ +package rbac + +import ( + "context" + "path/filepath" + + "gitlab.guxuan.icu/jinshan_community/internal/config" + "gitlab.guxuan.icu/jinshan_community/internal/mods/rbac/api" + "gitlab.guxuan.icu/jinshan_community/internal/mods/rbac/schema" + "gitlab.guxuan.icu/jinshan_community/pkg/logging" + "github.com/gin-gonic/gin" + "go.uber.org/zap" + "gorm.io/gorm" +) + +type RBAC struct { + DB *gorm.DB + MenuAPI *api.Menu + RoleAPI *api.Role + UserAPI *api.User + LoginAPI *api.Login + LoggerAPI *api.Logger + Casbinx *Casbinx +} + +func (a *RBAC) AutoMigrate(ctx context.Context) error { + return a.DB.AutoMigrate( + new(schema.Menu), + new(schema.MenuResource), + new(schema.Role), + new(schema.RoleMenu), + new(schema.User), + new(schema.UserRole), + ) +} + +func (a *RBAC) Init(ctx context.Context) error { + if config.C.Storage.DB.AutoMigrate { + if err := a.AutoMigrate(ctx); err != nil { + return err + } + } + + if err := a.Casbinx.Load(ctx); err != nil { + return err + } + + if name := config.C.General.MenuFile; name != "" { + fullPath := filepath.Join(config.C.General.WorkDir, name) + if err := a.MenuAPI.MenuBIZ.InitFromFile(ctx, fullPath); err != nil { + logging.Context(ctx).Error("failed to init menu data", zap.Error(err), zap.String("file", fullPath)) + } + } + + return nil +} + +func (a *RBAC) RegisterV1Routers(ctx context.Context, v1 *gin.RouterGroup) error { + captcha := v1.Group("captcha") + { + captcha.GET("id", a.LoginAPI.GetCaptcha) + captcha.GET("image", a.LoginAPI.ResponseCaptcha) + } + + v1.POST("login", a.LoginAPI.Login) + + current := v1.Group("current") + { + current.POST("refresh-token", a.LoginAPI.RefreshToken) + current.GET("user", a.LoginAPI.GetUserInfo) + current.GET("menus", a.LoginAPI.QueryMenus) + current.PUT("password", a.LoginAPI.UpdatePassword) + current.PUT("user", a.LoginAPI.UpdateUser) + current.POST("logout", a.LoginAPI.Logout) + } + + menu := v1.Group("menus") + { + menu.GET("", a.MenuAPI.Query) + menu.GET(":id", a.MenuAPI.Get) + menu.POST("", a.MenuAPI.Create) + menu.PUT(":id", a.MenuAPI.Update) + menu.DELETE(":id", a.MenuAPI.Delete) + } + + role := v1.Group("roles") + { + role.GET("", a.RoleAPI.Query) + role.GET(":id", a.RoleAPI.Get) + role.POST("", a.RoleAPI.Create) + role.PUT(":id", a.RoleAPI.Update) + role.DELETE(":id", a.RoleAPI.Delete) + } + + user := v1.Group("users") + { + user.GET("", a.UserAPI.Query) + user.GET(":id", a.UserAPI.Get) + user.POST("", a.UserAPI.Create) + user.PUT(":id", a.UserAPI.Update) + user.DELETE(":id", a.UserAPI.Delete) + user.PATCH(":id/reset-pwd", a.UserAPI.ResetPassword) + } + + logger := v1.Group("loggers") + { + logger.GET("", a.LoggerAPI.Query) + } + + return nil +} + +func (a *RBAC) Release(ctx context.Context) error { + if err := a.Casbinx.Release(ctx); err != nil { + return err + } + return nil +} diff --git a/internal/mods/rbac/schema/logger.go b/internal/mods/rbac/schema/logger.go new file mode 100644 index 0000000..276f39d --- /dev/null +++ b/internal/mods/rbac/schema/logger.go @@ -0,0 +1,53 @@ +package schema + +import ( + "time" + + "gitlab.guxuan.icu/jinshan_community/internal/config" + "gitlab.guxuan.icu/jinshan_community/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/menu.go b/internal/mods/rbac/schema/menu.go new file mode 100644 index 0000000..ff12160 --- /dev/null +++ b/internal/mods/rbac/schema/menu.go @@ -0,0 +1,178 @@ +package schema + +import ( + "encoding/json" + "strings" + "time" + + "gitlab.guxuan.icu/jinshan_community/internal/config" + "gitlab.guxuan.icu/jinshan_community/pkg/errors" + "gitlab.guxuan.icu/jinshan_community/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..0ca9231 --- /dev/null +++ b/internal/mods/rbac/schema/menu_resource.go @@ -0,0 +1,56 @@ +package schema + +import ( + "time" + + "gitlab.guxuan.icu/jinshan_community/internal/config" + "gitlab.guxuan.icu/jinshan_community/pkg/util" +) + +// Menu resource management for RBAC +type MenuResource struct { + ID string `json:"id" gorm:"size:20;primarykey"` // Unique ID + MenuID string `json:"menu_id" gorm:"size:20;index"` // From Menu.ID + Method string `json:"method" gorm:"size:20;"` // HTTP method + Path string `json:"path" gorm:"size:255;"` // API request path (e.g. /api/v1/users/:id) + CreatedAt time.Time `json:"created_at" gorm:"index;"` // Create time + UpdatedAt time.Time `json:"updated_at" gorm:"index;"` // Update time +} + +func (a *MenuResource) TableName() string { + return config.C.FormatTableName("menu_resource") +} + +// Defining the query parameters for the `MenuResource` struct. +type MenuResourceQueryParam struct { + util.PaginationParam + MenuID string `form:"-"` // From Menu.ID + MenuIDs []string `form:"-"` // From Menu.ID +} + +// Defining the query options for the `MenuResource` struct. +type MenuResourceQueryOptions struct { + util.QueryOptions +} + +// Defining the query result for the `MenuResource` struct. +type MenuResourceQueryResult struct { + Data MenuResources + PageResult *util.PaginationResult +} + +// Defining the slice of `MenuResource` struct. +type MenuResources []*MenuResource + +// Defining the data structure for creating a `MenuResource` struct. +type MenuResourceForm struct { +} + +// A validation function for the `MenuResourceForm` struct. +func (a *MenuResourceForm) Validate() error { + return nil +} + +func (a *MenuResourceForm) FillTo(menuResource *MenuResource) error { + return nil +} diff --git a/internal/mods/rbac/schema/role.go b/internal/mods/rbac/schema/role.go new file mode 100644 index 0000000..c2e8327 --- /dev/null +++ b/internal/mods/rbac/schema/role.go @@ -0,0 +1,80 @@ +package schema + +import ( + "time" + + "gitlab.guxuan.icu/jinshan_community/internal/config" + "gitlab.guxuan.icu/jinshan_community/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..8641ca9 --- /dev/null +++ b/internal/mods/rbac/schema/role_menu.go @@ -0,0 +1,54 @@ +package schema + +import ( + "time" + + "gitlab.guxuan.icu/jinshan_community/internal/config" + "gitlab.guxuan.icu/jinshan_community/pkg/util" +) + +// Role permissions for RBAC +type RoleMenu struct { + ID string `json:"id" gorm:"size:20;primarykey"` // Unique ID + RoleID string `json:"role_id" gorm:"size:20;index"` // From Role.ID + MenuID string `json:"menu_id" gorm:"size:20;index"` // From Menu.ID + CreatedAt time.Time `json:"created_at" gorm:"index;"` // Create time + UpdatedAt time.Time `json:"updated_at" gorm:"index;"` // Update time +} + +func (a *RoleMenu) TableName() string { + return config.C.FormatTableName("role_menu") +} + +// Defining the query parameters for the `RoleMenu` struct. +type RoleMenuQueryParam struct { + util.PaginationParam + RoleID string `form:"-"` // From Role.ID +} + +// Defining the query options for the `RoleMenu` struct. +type RoleMenuQueryOptions struct { + util.QueryOptions +} + +// Defining the query result for the `RoleMenu` struct. +type RoleMenuQueryResult struct { + Data RoleMenus + PageResult *util.PaginationResult +} + +// Defining the slice of `RoleMenu` struct. +type RoleMenus []*RoleMenu + +// Defining the data structure for creating a `RoleMenu` struct. +type RoleMenuForm struct { +} + +// A validation function for the `RoleMenuForm` struct. +func (a *RoleMenuForm) Validate() error { + return nil +} + +func (a *RoleMenuForm) FillTo(roleMenu *RoleMenu) error { + return nil +} diff --git a/internal/mods/rbac/schema/user.go b/internal/mods/rbac/schema/user.go new file mode 100644 index 0000000..2919200 --- /dev/null +++ b/internal/mods/rbac/schema/user.go @@ -0,0 +1,105 @@ +package schema + +import ( + "time" + + "gitlab.guxuan.icu/jinshan_community/internal/config" + "gitlab.guxuan.icu/jinshan_community/pkg/crypto/hash" + "gitlab.guxuan.icu/jinshan_community/pkg/errors" + "gitlab.guxuan.icu/jinshan_community/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..6037cc8 --- /dev/null +++ b/internal/mods/rbac/schema/user_role.go @@ -0,0 +1,74 @@ +package schema + +import ( + "time" + + "gitlab.guxuan.icu/jinshan_community/internal/config" + "gitlab.guxuan.icu/jinshan_community/pkg/util" +) + +// User roles for RBAC +type UserRole struct { + ID string `json:"id" gorm:"size:20;primarykey"` // Unique ID + UserID string `json:"user_id" gorm:"size:20;index"` // From User.ID + RoleID string `json:"role_id" gorm:"size:20;index"` // From Role.ID + CreatedAt time.Time `json:"created_at" gorm:"index;"` // Create time + UpdatedAt time.Time `json:"updated_at" gorm:"index;"` // Update time + RoleName string `json:"role_name" gorm:"<-:false;-:migration;"` // From Role.Name +} + +func (a *UserRole) TableName() string { + return config.C.FormatTableName("user_role") +} + +// Defining the query parameters for the `UserRole` struct. +type UserRoleQueryParam struct { + util.PaginationParam + InUserIDs []string `form:"-"` // From User.ID + UserID string `form:"-"` // From User.ID + RoleID string `form:"-"` // From Role.ID +} + +// Defining the query options for the `UserRole` struct. +type UserRoleQueryOptions struct { + util.QueryOptions + JoinRole bool // Join role table +} + +// Defining the query result for the `UserRole` struct. +type UserRoleQueryResult struct { + Data UserRoles + PageResult *util.PaginationResult +} + +// Defining the slice of `UserRole` struct. +type UserRoles []*UserRole + +func (a UserRoles) ToUserIDMap() map[string]UserRoles { + m := make(map[string]UserRoles) + for _, userRole := range a { + m[userRole.UserID] = append(m[userRole.UserID], userRole) + } + return m +} + +func (a UserRoles) ToRoleIDs() []string { + var ids []string + for _, item := range a { + ids = append(ids, item.RoleID) + } + return ids +} + +// Defining the data structure for creating a `UserRole` struct. +type UserRoleForm struct { +} + +// A validation function for the `UserRoleForm` struct. +func (a *UserRoleForm) Validate() error { + return nil +} + +func (a *UserRoleForm) FillTo(userRole *UserRole) error { + return nil +} diff --git a/internal/mods/rbac/wire.go b/internal/mods/rbac/wire.go new file mode 100644 index 0000000..2a9de0d --- /dev/null +++ b/internal/mods/rbac/wire.go @@ -0,0 +1,31 @@ +package rbac + +import ( + "gitlab.guxuan.icu/jinshan_community/internal/mods/rbac/api" + "gitlab.guxuan.icu/jinshan_community/internal/mods/rbac/biz" + "gitlab.guxuan.icu/jinshan_community/internal/mods/rbac/dal" + "github.com/google/wire" +) + +// Collection of wire providers +var Set = wire.NewSet( + wire.Struct(new(RBAC), "*"), + wire.Struct(new(Casbinx), "*"), + wire.Struct(new(dal.Menu), "*"), + wire.Struct(new(biz.Menu), "*"), + wire.Struct(new(api.Menu), "*"), + wire.Struct(new(dal.MenuResource), "*"), + wire.Struct(new(dal.Role), "*"), + wire.Struct(new(biz.Role), "*"), + wire.Struct(new(api.Role), "*"), + wire.Struct(new(dal.RoleMenu), "*"), + wire.Struct(new(dal.User), "*"), + wire.Struct(new(biz.User), "*"), + wire.Struct(new(api.User), "*"), + wire.Struct(new(dal.UserRole), "*"), + wire.Struct(new(biz.Login), "*"), + wire.Struct(new(api.Login), "*"), + wire.Struct(new(api.Logger), "*"), + wire.Struct(new(biz.Logger), "*"), + wire.Struct(new(dal.Logger), "*"), +) diff --git a/internal/swagger/docs.go b/internal/swagger/docs.go new file mode 100644 index 0000000..3f7415d --- /dev/null +++ b/internal/swagger/docs.go @@ -0,0 +1,9074 @@ +// Package swagger Code generated by swaggo/swag. DO NOT EDIT +package swagger + +import "github.com/swaggo/swag" + +const docTemplate = `{ + "schemes": {{ marshal .Schemes }}, + "swagger": "2.0", + "info": { + "description": "{{escape .Description}}", + "title": "{{.Title}}", + "contact": {}, + "version": "{{.Version}}" + }, + "host": "{{.Host}}", + "basePath": "{{.BasePath}}", + "paths": { + "/api/v1/activities": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "ActivityAPI" + ], + "summary": "Query activity list", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "pagination index", + "name": "current", + "in": "query", + "required": true + }, + { + "type": "integer", + "default": 10, + "description": "pagination size", + "name": "pageSize", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.Activity" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "ActivityAPI" + ], + "summary": "Create activity record", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.ActivityForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.Activity" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/activities/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "ActivityAPI" + ], + "summary": "Get activity record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.Activity" + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "ActivityAPI" + ], + "summary": "Update activity record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.ActivityForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "ActivityAPI" + ], + "summary": "Delete activity record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/activity-categories": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "ActivityCategoryAPI" + ], + "summary": "Query activity category list", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "pagination index", + "name": "current", + "in": "query", + "required": true + }, + { + "type": "integer", + "default": 10, + "description": "pagination size", + "name": "pageSize", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.ActivityCategory" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "ActivityCategoryAPI" + ], + "summary": "Create activity category record", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.ActivityCategoryForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.ActivityCategory" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/activity-categories/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "ActivityCategoryAPI" + ], + "summary": "Get activity category record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.ActivityCategory" + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "ActivityCategoryAPI" + ], + "summary": "Update activity category record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.ActivityCategoryForm" + } + } + ], + "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": [ + "ActivityCategoryAPI" + ], + "summary": "Delete activity category record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/ai-requests": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "AiRequestAPI" + ], + "summary": "Query ai request list", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "pagination index", + "name": "current", + "in": "query", + "required": true + }, + { + "type": "integer", + "default": 10, + "description": "pagination size", + "name": "pageSize", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.AiRequest" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "AiRequestAPI" + ], + "summary": "Create ai request record", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.AiRequestForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.AiRequest" + } + } + } + ] + } + }, + "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/ai-requests/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "AiRequestAPI" + ], + "summary": "Get ai request 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.AiRequest" + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "AiRequestAPI" + ], + "summary": "Update ai request 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.AiRequestForm" + } + } + ], + "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": [ + "AiRequestAPI" + ], + "summary": "Delete ai request record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/apps": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "AppAPI" + ], + "summary": "Query app list", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "pagination index", + "name": "current", + "in": "query", + "required": true + }, + { + "type": "integer", + "default": 10, + "description": "pagination size", + "name": "pageSize", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.App" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "AppAPI" + ], + "summary": "Create app record", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.AppForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.App" + } + } + } + ] + } + }, + "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/apps/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "AppAPI" + ], + "summary": "Get app 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.App" + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "AppAPI" + ], + "summary": "Update app 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.AppForm" + } + } + ], + "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": [ + "AppAPI" + ], + "summary": "Delete app record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/balances": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "BalanceAPI" + ], + "summary": "Query balance list", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "pagination index", + "name": "current", + "in": "query", + "required": true + }, + { + "type": "integer", + "default": 10, + "description": "pagination size", + "name": "pageSize", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.Balance" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "BalanceAPI" + ], + "summary": "Create balance record", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.BalanceForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.Balance" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/balances/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "BalanceAPI" + ], + "summary": "Get balance record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.Balance" + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "BalanceAPI" + ], + "summary": "Update balance record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.BalanceForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "BalanceAPI" + ], + "summary": "Delete balance record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/banners": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "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 + } + ], + "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/customers": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "CustomerAPI" + ], + "summary": "Query customer list", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "pagination index", + "name": "current", + "in": "query", + "required": true + }, + { + "type": "integer", + "default": 10, + "description": "pagination size", + "name": "pageSize", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.Customer" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "CustomerAPI" + ], + "summary": "Create customer record", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.CustomerForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.Customer" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/customers/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "CustomerAPI" + ], + "summary": "Get customer record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.Customer" + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "CustomerAPI" + ], + "summary": "Update customer record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.CustomerForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "CustomerAPI" + ], + "summary": "Delete customer record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/helps": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "HelpAPI" + ], + "summary": "Query help list", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "pagination index", + "name": "current", + "in": "query", + "required": true + }, + { + "type": "integer", + "default": 10, + "description": "pagination size", + "name": "pageSize", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.Help" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "HelpAPI" + ], + "summary": "Create help record", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.HelpForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.Help" + } + } + } + ] + } + }, + "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/helps/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "HelpAPI" + ], + "summary": "Get help 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.Help" + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "HelpAPI" + ], + "summary": "Update help 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.HelpForm" + } + } + ], + "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": [ + "HelpAPI" + ], + "summary": "Delete help record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/loggers": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "LoggerAPI" + ], + "summary": "Query logger list", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "pagination index", + "name": "current", + "in": "query", + "required": true + }, + { + "type": "integer", + "default": 10, + "description": "pagination size", + "name": "pageSize", + "in": "query", + "required": true + }, + { + "type": "string", + "description": "log level", + "name": "level", + "in": "query" + }, + { + "type": "string", + "description": "trace ID", + "name": "traceID", + "in": "query" + }, + { + "type": "string", + "description": "user name", + "name": "userName", + "in": "query" + }, + { + "type": "string", + "description": "log tag", + "name": "tag", + "in": "query" + }, + { + "type": "string", + "description": "log message", + "name": "message", + "in": "query" + }, + { + "type": "string", + "description": "start time", + "name": "startTime", + "in": "query" + }, + { + "type": "string", + "description": "end time", + "name": "endTime", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.Logger" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/login": { + "post": { + "tags": [ + "LoginAPI" + ], + "summary": "Login system with username and password", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.LoginForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.LoginToken" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/menus": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "MenuAPI" + ], + "summary": "Query menu tree data", + "parameters": [ + { + "type": "string", + "description": "Code path of menu (like xxx.xxx.xxx)", + "name": "code", + "in": "query" + }, + { + "type": "string", + "description": "Name of menu", + "name": "name", + "in": "query" + }, + { + "type": "boolean", + "description": "Whether to include menu resources", + "name": "includeResources", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.Menu" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "MenuAPI" + ], + "summary": "Create menu record", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.MenuForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.Menu" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/menus/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "MenuAPI" + ], + "summary": "Get menu record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.Menu" + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "MenuAPI" + ], + "summary": "Update menu record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.MenuForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "MenuAPI" + ], + "summary": "Delete menu record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/metting-room-orders": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "MettingRoomOrderAPI" + ], + "summary": "Query metting room order list", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "pagination index", + "name": "current", + "in": "query", + "required": true + }, + { + "type": "integer", + "default": 10, + "description": "pagination size", + "name": "pageSize", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.MettingRoomOrder" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "MettingRoomOrderAPI" + ], + "summary": "Create metting room order record", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.MettingRoomOrderForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.MettingRoomOrder" + } + } + } + ] + } + }, + "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/metting-room-orders/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "MettingRoomOrderAPI" + ], + "summary": "Get metting room order record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.MettingRoomOrder" + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "MettingRoomOrderAPI" + ], + "summary": "Update metting room order record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.MettingRoomOrderForm" + } + } + ], + "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": [ + "MettingRoomOrderAPI" + ], + "summary": "Delete metting room order record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/metting-rooms": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "MettingRoomAPI" + ], + "summary": "Query metting room list", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "pagination index", + "name": "current", + "in": "query", + "required": true + }, + { + "type": "integer", + "default": 10, + "description": "pagination size", + "name": "pageSize", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.MettingRoom" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "MettingRoomAPI" + ], + "summary": "Create metting room record", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.MettingRoomForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.MettingRoom" + } + } + } + ] + } + }, + "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/metting-rooms/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "MettingRoomAPI" + ], + "summary": "Get metting room 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.MettingRoom" + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "MettingRoomAPI" + ], + "summary": "Update metting room 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.MettingRoomForm" + } + } + ], + "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": [ + "MettingRoomAPI" + ], + "summary": "Delete metting room record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/notices": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "NoticeAPI" + ], + "summary": "Query notice list", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "pagination index", + "name": "current", + "in": "query", + "required": true + }, + { + "type": "integer", + "default": 10, + "description": "pagination size", + "name": "pageSize", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.Notice" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "NoticeAPI" + ], + "summary": "Create notice record", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.NoticeForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.Notice" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/notices/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "NoticeAPI" + ], + "summary": "Get notice record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.Notice" + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "NoticeAPI" + ], + "summary": "Update notice record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.NoticeForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "NoticeAPI" + ], + "summary": "Delete notice record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/orders": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "OrderAPI" + ], + "summary": "Query order list", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "pagination index", + "name": "current", + "in": "query", + "required": true + }, + { + "type": "integer", + "default": 10, + "description": "pagination size", + "name": "pageSize", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.Order" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "OrderAPI" + ], + "summary": "Create order record", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.OrderForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.Order" + } + } + } + ] + } + }, + "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/orders/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "OrderAPI" + ], + "summary": "Get order record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.Order" + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "OrderAPI" + ], + "summary": "Update order record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.OrderForm" + } + } + ], + "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": [ + "OrderAPI" + ], + "summary": "Delete order record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/product-categories": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "ProductCategoryAPI" + ], + "summary": "Query product category list", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "pagination index", + "name": "current", + "in": "query", + "required": true + }, + { + "type": "integer", + "default": 10, + "description": "pagination size", + "name": "pageSize", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.ProductCategory" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "ProductCategoryAPI" + ], + "summary": "Create product category record", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.ProductCategoryForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.ProductCategory" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/product-categories/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "ProductCategoryAPI" + ], + "summary": "Get product category record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.ProductCategory" + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "ProductCategoryAPI" + ], + "summary": "Update product category record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.ProductCategoryForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "ProductCategoryAPI" + ], + "summary": "Delete product category record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/products": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "ProductAPI" + ], + "summary": "Query product list", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "pagination index", + "name": "current", + "in": "query", + "required": true + }, + { + "type": "integer", + "default": 10, + "description": "pagination size", + "name": "pageSize", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.Product" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "ProductAPI" + ], + "summary": "Create product record", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.ProductForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.Product" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/products/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "ProductAPI" + ], + "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": [ + "ProductAPI" + ], + "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": [ + "ProductAPI" + ], + "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/reciprocities": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "ReciprocityAPI" + ], + "summary": "Query reciprocity list", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "pagination index", + "name": "current", + "in": "query", + "required": true + }, + { + "type": "integer", + "default": 10, + "description": "pagination size", + "name": "pageSize", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.Reciprocity" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "ReciprocityAPI" + ], + "summary": "Create reciprocity record", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.ReciprocityForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.Reciprocity" + } + } + } + ] + } + }, + "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/reciprocities/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "ReciprocityAPI" + ], + "summary": "Get reciprocity 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.Reciprocity" + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "ReciprocityAPI" + ], + "summary": "Update reciprocity 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.ReciprocityForm" + } + } + ], + "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": [ + "ReciprocityAPI" + ], + "summary": "Delete reciprocity 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/shops": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "ShopAPI" + ], + "summary": "Query shop list", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "pagination index", + "name": "current", + "in": "query", + "required": true + }, + { + "type": "integer", + "default": 10, + "description": "pagination size", + "name": "pageSize", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.Shop" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "ShopAPI" + ], + "summary": "Create shop record", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.ShopForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.Shop" + } + } + } + ] + } + }, + "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/shops/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "ShopAPI" + ], + "summary": "Get shop 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.Shop" + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "ShopAPI" + ], + "summary": "Update shop 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.ShopForm" + } + } + ], + "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": [ + "ShopAPI" + ], + "summary": "Delete shop 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/surrounding-service-types": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "SurroundingServiceTypeAPI" + ], + "summary": "Query surrounding service type list", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "pagination index", + "name": "current", + "in": "query", + "required": true + }, + { + "type": "integer", + "default": 10, + "description": "pagination size", + "name": "pageSize", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.SurroundingServiceType" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "SurroundingServiceTypeAPI" + ], + "summary": "Create surrounding service type record", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.SurroundingServiceTypeForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.SurroundingServiceType" + } + } + } + ] + } + }, + "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/surrounding-service-types/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "SurroundingServiceTypeAPI" + ], + "summary": "Get surrounding service type 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.SurroundingServiceType" + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "SurroundingServiceTypeAPI" + ], + "summary": "Update surrounding service type 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.SurroundingServiceTypeForm" + } + } + ], + "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": [ + "SurroundingServiceTypeAPI" + ], + "summary": "Delete surrounding service type 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/surrounding-services": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "SurroundingServiceAPI" + ], + "summary": "Query surrounding service list", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "pagination index", + "name": "current", + "in": "query", + "required": true + }, + { + "type": "integer", + "default": 10, + "description": "pagination size", + "name": "pageSize", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.SurroundingService" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "SurroundingServiceAPI" + ], + "summary": "Create surrounding service record", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.SurroundingServiceForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.SurroundingService" + } + } + } + ] + } + }, + "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/surrounding-services/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "SurroundingServiceAPI" + ], + "summary": "Get surrounding service 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.SurroundingService" + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "SurroundingServiceAPI" + ], + "summary": "Update surrounding service 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.SurroundingServiceForm" + } + } + ], + "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": [ + "SurroundingServiceAPI" + ], + "summary": "Delete surrounding service 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/web-sites": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "WebSiteAPI" + ], + "summary": "Query web site list", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "pagination index", + "name": "current", + "in": "query", + "required": true + }, + { + "type": "integer", + "default": 10, + "description": "pagination size", + "name": "pageSize", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.WebSite" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "WebSiteAPI" + ], + "summary": "Create web site 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-sites/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "WebSiteAPI" + ], + "summary": "Get web site 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.WebSite" + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "WebSiteAPI" + ], + "summary": "Update web site 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" + } + } + } + }, + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "WebSiteAPI" + ], + "summary": "Delete web site 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/work-order-types": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "WorkOrderTypeAPI" + ], + "summary": "Query work order type list", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "pagination index", + "name": "current", + "in": "query", + "required": true + }, + { + "type": "integer", + "default": 10, + "description": "pagination size", + "name": "pageSize", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.WorkOrderType" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "WorkOrderTypeAPI" + ], + "summary": "Create work order type record", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.WorkOrderTypeForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.WorkOrderType" + } + } + } + ] + } + }, + "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/work-order-types/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "WorkOrderTypeAPI" + ], + "summary": "Get work order type 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.WorkOrderType" + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "WorkOrderTypeAPI" + ], + "summary": "Update work order type 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.WorkOrderTypeForm" + } + } + ], + "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": [ + "WorkOrderTypeAPI" + ], + "summary": "Delete work order type 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/work-orders": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "WorkOrderAPI" + ], + "summary": "Query work order list", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "pagination index", + "name": "current", + "in": "query", + "required": true + }, + { + "type": "integer", + "default": 10, + "description": "pagination size", + "name": "pageSize", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.WorkOrder" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "WorkOrderAPI" + ], + "summary": "Create work order record", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.WorkOrderForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.WorkOrder" + } + } + } + ] + } + }, + "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/work-orders/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "WorkOrderAPI" + ], + "summary": "Get work order record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.WorkOrder" + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "WorkOrderAPI" + ], + "summary": "Update work order record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.WorkOrderForm" + } + } + ], + "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": [ + "WorkOrderAPI" + ], + "summary": "Delete work order record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + } + }, + "definitions": { + "errors.Error": { + "type": "object", + "properties": { + "code": { + "type": "integer" + }, + "detail": { + "type": "string" + }, + "id": { + "type": "string" + }, + "status": { + "type": "string" + } + } + }, + "schema.Activity": { + "type": "object", + "properties": { + "address": { + "type": "string" + }, + "categoryId": { + "type": "string" + }, + "content": { + "type": "string" + }, + "cover": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "createdId": { + "type": "string" + }, + "deletedAt": { + "type": "integer" + }, + "deletedId": { + "type": "string" + }, + "endAt": { + "type": "string" + }, + "endSignupAt": { + "type": "string" + }, + "id": { + "type": "string" + }, + "images": { + "type": "array", + "items": { + "type": "string" + } + }, + "maxSignupNum": { + "type": "integer" + }, + "signupNum": { + "type": "integer" + }, + "startAt": { + "type": "string" + }, + "startSignupAt": { + "type": "string" + }, + "status": { + "type": "string" + }, + "title": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + }, + "schema.ActivityCategory": { + "type": "object", + "properties": { + "createdAt": { + "type": "string" + }, + "createdId": { + "type": "string" + }, + "deletedAt": { + "type": "integer" + }, + "deletedId": { + "type": "string" + }, + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "sequence": { + "type": "integer" + }, + "status": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + }, + "schema.ActivityCategoryForm": { + "type": "object" + }, + "schema.ActivityForm": { + "type": "object", + "properties": { + "address": { + "type": "string" + }, + "categoryId": { + "type": "string" + }, + "content": { + "type": "string" + }, + "cover": { + "type": "string" + }, + "endAt": { + "type": "string" + }, + "endSignupAt": { + "type": "string" + }, + "images": { + "type": "array", + "items": { + "type": "string" + } + }, + "maxSignupNum": { + "type": "integer" + }, + "signupNum": { + "type": "integer" + }, + "startAt": { + "type": "string" + }, + "startSignupAt": { + "type": "string" + }, + "status": { + "type": "string" + }, + "title": { + "type": "string" + } + } + }, + "schema.AiRequest": { + "type": "object", + "properties": { + "createdAt": { + "type": "string" + }, + "createdId": { + "type": "string" + }, + "deletedAt": { + "type": "integer" + }, + "deletedId": { + "type": "string" + }, + "id": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + }, + "schema.AiRequestForm": { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + }, + "schema.App": { + "type": "object", + "properties": { + "created_at": { + "description": "Create time", + "type": "string" + }, + "id": { + "description": "Unique ID", + "type": "string" + }, + "updated_at": { + "description": "Update time", + "type": "string" + } + } + }, + "schema.AppForm": { + "type": "object" + }, + "schema.Balance": { + "type": "object", + "properties": { + "after": { + "type": "integer" + }, + "before": { + "type": "integer" + }, + "change": { + "type": "integer" + }, + "createdAt": { + "type": "string" + }, + "createdId": { + "type": "string" + }, + "customerId": { + "type": "string" + }, + "deletedAt": { + "type": "integer" + }, + "deletedId": { + "type": "string" + }, + "id": { + "type": "string" + }, + "operatorId": { + "type": "string" + }, + "reason": { + "type": "string" + }, + "type": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + }, + "schema.BalanceForm": { + "type": "object", + "properties": { + "CreatedID": { + "type": "string" + }, + "after": { + "type": "integer" + }, + "before": { + "type": "integer" + }, + "change": { + "type": "integer" + }, + "customerId": { + "type": "string" + }, + "operatorId": { + "type": "string" + }, + "reason": { + "type": "string" + }, + "type": { + "type": "string" + } + } + }, + "schema.Banner": { + "type": "object", + "properties": { + "createdAt": { + "type": "string" + }, + "createdId": { + "type": "string" + }, + "deletedAt": { + "type": "integer" + }, + "deletedId": { + "type": "string" + }, + "desc": { + "type": "string" + }, + "id": { + "type": "string" + }, + "img": { + "type": "string" + }, + "link": { + "type": "string" + }, + "sequence": { + "type": "integer" + }, + "status": { + "type": "string" + }, + "title": { + "type": "string" + }, + "type": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + }, + "schema.BannerForm": { + "type": "object", + "properties": { + "desc": { + "type": "string" + }, + "img": { + "type": "string" + }, + "link": { + "type": "string" + }, + "sequence": { + "type": "integer" + }, + "status": { + "type": "string" + }, + "title": { + "type": "string" + }, + "type": { + "type": "string" + } + } + }, + "schema.Captcha": { + "type": "object", + "properties": { + "captcha_id": { + "description": "Captcha ID", + "type": "string" + } + } + }, + "schema.Customer": { + "type": "object", + "properties": { + "avatar": { + "type": "string" + }, + "birthday": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "createdId": { + "type": "string" + }, + "deletedAt": { + "type": "integer" + }, + "deletedId": { + "type": "string" + }, + "id": { + "type": "string" + }, + "introduce": { + "type": "string" + }, + "name": { + "type": "string" + }, + "phone": { + "type": "string" + }, + "status": { + "type": "string" + }, + "updatedAt": { + "type": "string" + }, + "wxSign": { + "type": "string" + } + } + }, + "schema.CustomerForm": { + "type": "object", + "properties": { + "avatar": { + "type": "string" + }, + "birthday": { + "type": "string" + }, + "introduce": { + "type": "string" + }, + "name": { + "type": "string" + }, + "phone": { + "type": "string" + }, + "status": { + "type": "string" + }, + "wxSign": { + "type": "string" + } + } + }, + "schema.Help": { + "type": "object", + "properties": { + "createdAt": { + "type": "string" + }, + "createdId": { + "type": "string" + }, + "customerId": { + "type": "string" + }, + "deletedAt": { + "type": "integer" + }, + "deletedId": { + "type": "string" + }, + "desc": { + "type": "string" + }, + "id": { + "type": "string" + }, + "img": { + "type": "string" + }, + "link": { + "type": "string" + }, + "sequence": { + "type": "integer" + }, + "status": { + "type": "string" + }, + "type": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + }, + "schema.HelpForm": { + "type": "object" + }, + "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.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.MettingRoom": { + "type": "object", + "properties": { + "createdAt": { + "type": "string" + }, + "createdId": { + "type": "string" + }, + "deletedAt": { + "type": "integer" + }, + "deletedId": { + "type": "string" + }, + "desc": { + "type": "string" + }, + "id": { + "type": "string" + }, + "imgs": { + "type": "array", + "items": { + "type": "string" + } + }, + "link": { + "type": "string" + }, + "maxNum": { + "type": "integer" + }, + "status": { + "type": "string" + }, + "title": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + }, + "schema.MettingRoomForm": { + "type": "object", + "properties": { + "desc": { + "type": "string" + }, + "imgs": { + "type": "array", + "items": { + "type": "string" + } + }, + "link": { + "type": "string" + }, + "maxNum": { + "type": "integer" + }, + "status": { + "type": "string" + }, + "title": { + "type": "string" + } + } + }, + "schema.MettingRoomOrder": { + "type": "object", + "properties": { + "concatName": { + "type": "string" + }, + "concatPhone": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "createdId": { + "type": "string" + }, + "customerId": { + "type": "string" + }, + "deletedAt": { + "type": "integer" + }, + "deletedId": { + "type": "string" + }, + "endAt": { + "type": "string" + }, + "id": { + "type": "string" + }, + "roomId": { + "type": "string" + }, + "startAt": { + "type": "string" + }, + "status": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + }, + "schema.MettingRoomOrderForm": { + "type": "object", + "properties": { + "concatName": { + "type": "string" + }, + "concatPhone": { + "type": "string" + }, + "customerId": { + "type": "string" + }, + "endAt": { + "type": "string" + }, + "roomId": { + "type": "string" + }, + "startAt": { + "type": "string" + }, + "status": { + "type": "string" + } + } + }, + "schema.Notice": { + "type": "object", + "properties": { + "createdAt": { + "type": "string" + }, + "createdId": { + "type": "string" + }, + "deletedAt": { + "type": "integer" + }, + "deletedId": { + "type": "string" + }, + "desc": { + "type": "string" + }, + "id": { + "type": "string" + }, + "link": { + "type": "string" + }, + "sequence": { + "type": "integer" + }, + "status": { + "type": "string" + }, + "title": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + }, + "schema.NoticeForm": { + "type": "object", + "properties": { + "desc": { + "type": "string" + }, + "link": { + "type": "string" + }, + "sequence": { + "type": "integer" + }, + "status": { + "type": "string" + }, + "title": { + "type": "string" + } + } + }, + "schema.Order": { + "type": "object", + "properties": { + "createdAt": { + "type": "string" + }, + "createdId": { + "type": "string" + }, + "customerId": { + "type": "string" + }, + "deletedAt": { + "type": "integer" + }, + "deletedId": { + "type": "string" + }, + "id": { + "type": "string" + }, + "num": { + "type": "integer" + }, + "productId": { + "type": "string" + }, + "shopId": { + "type": "string" + }, + "status": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + }, + "schema.OrderForm": { + "type": "object" + }, + "schema.Product": { + "type": "object", + "properties": { + "categoryId": { + "type": "string" + }, + "content": { + "type": "string" + }, + "cover": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "createdId": { + "type": "string" + }, + "deletedAt": { + "type": "integer" + }, + "deletedId": { + "type": "string" + }, + "id": { + "type": "string" + }, + "images": { + "type": "array", + "items": { + "type": "string" + } + }, + "introduce": { + "type": "string" + }, + "num": { + "type": "integer" + }, + "price": { + "type": "integer" + }, + "shopId": { + "type": "string" + }, + "status": { + "type": "string" + }, + "title": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + }, + "schema.ProductCategory": { + "type": "object", + "properties": { + "createdAt": { + "type": "string" + }, + "createdId": { + "type": "string" + }, + "deletedAt": { + "type": "integer" + }, + "deletedId": { + "type": "string" + }, + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "sequence": { + "type": "integer" + }, + "status": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + }, + "schema.ProductCategoryForm": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "sequence": { + "type": "integer" + }, + "status": { + "type": "string" + } + } + }, + "schema.ProductForm": { + "type": "object", + "properties": { + "categoryId": { + "type": "string" + }, + "content": { + "type": "string" + }, + "cover": { + "type": "string" + }, + "images": { + "type": "array", + "items": { + "type": "string" + } + }, + "introduce": { + "type": "string" + }, + "num": { + "type": "integer" + }, + "price": { + "type": "integer" + }, + "shopId": { + "type": "string" + }, + "status": { + "type": "string" + }, + "title": { + "type": "string" + } + } + }, + "schema.Reciprocity": { + "type": "object", + "properties": { + "content": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "createdId": { + "type": "string" + }, + "customerId": { + "type": "string" + }, + "deletedAt": { + "type": "integer" + }, + "deletedId": { + "type": "string" + }, + "id": { + "type": "string" + }, + "images": { + "type": "array", + "items": { + "type": "string" + } + }, + "status": { + "type": "string" + }, + "title": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + }, + "schema.ReciprocityForm": { + "type": "object", + "properties": { + "content": { + "type": "string" + }, + "customerId": { + "type": "string" + }, + "images": { + "type": "array", + "items": { + "type": "string" + } + }, + "status": { + "type": "string" + }, + "title": { + "type": "string" + } + } + }, + "schema.Record": { + "type": "object", + "properties": { + "content": { + "type": "string" + }, + "sendAt": { + "type": "string" + }, + "sender": { + "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.Shop": { + "type": "object", + "properties": { + "content": { + "type": "string" + }, + "cover": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "createdId": { + "type": "string" + }, + "deletedAt": { + "type": "integer" + }, + "deletedId": { + "type": "string" + }, + "id": { + "type": "string" + }, + "images": { + "type": "array", + "items": { + "type": "string" + } + }, + "introduce": { + "type": "string" + }, + "latitude": { + "type": "number" + }, + "longitude": { + "type": "number" + }, + "status": { + "type": "string" + }, + "title": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + }, + "schema.ShopForm": { + "type": "object", + "properties": { + "content": { + "type": "string" + }, + "cover": { + "type": "string" + }, + "images": { + "type": "array", + "items": { + "type": "string" + } + }, + "introduce": { + "type": "string" + }, + "latitude": { + "type": "number" + }, + "longitude": { + "type": "number" + }, + "status": { + "type": "string" + }, + "title": { + "type": "string" + } + } + }, + "schema.SurroundingService": { + "type": "object", + "properties": { + "content": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "createdId": { + "type": "string" + }, + "deletedAt": { + "type": "integer" + }, + "deletedId": { + "type": "string" + }, + "desc": { + "type": "string" + }, + "id": { + "type": "string" + }, + "img": { + "type": "string" + }, + "link": { + "type": "string" + }, + "sequence": { + "type": "integer" + }, + "status": { + "type": "string" + }, + "title": { + "type": "string" + }, + "typeId": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + }, + "schema.SurroundingServiceForm": { + "type": "object", + "properties": { + "content": { + "type": "string" + }, + "desc": { + "type": "string" + }, + "img": { + "type": "string" + }, + "link": { + "type": "string" + }, + "sequence": { + "type": "integer" + }, + "status": { + "type": "string" + }, + "title": { + "type": "string" + }, + "typeId": { + "type": "string" + } + } + }, + "schema.SurroundingServiceType": { + "type": "object", + "properties": { + "createdAt": { + "type": "string" + }, + "createdId": { + "type": "string" + }, + "deletedAt": { + "type": "integer" + }, + "deletedId": { + "type": "string" + }, + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "sequence": { + "type": "integer" + }, + "status": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + }, + "schema.SurroundingServiceTypeForm": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "sequence": { + "type": "integer" + }, + "status": { + "type": "string" + } + } + }, + "schema.UpdateCurrentUser": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "email": { + "description": "Email of user", + "type": "string", + "maxLength": 128 + }, + "name": { + "description": "Name of user", + "type": "string", + "maxLength": 64 + }, + "phone": { + "description": "Phone number of user", + "type": "string", + "maxLength": 32 + }, + "remark": { + "description": "Remark of user", + "type": "string", + "maxLength": 1024 + } + } + }, + "schema.UpdateLoginPassword": { + "type": "object", + "required": [ + "new_password", + "old_password" + ], + "properties": { + "new_password": { + "description": "New password (md5 hash)", + "type": "string" + }, + "old_password": { + "description": "Old password (md5 hash)", + "type": "string" + } + } + }, + "schema.User": { + "type": "object", + "properties": { + "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.WebSite": { + "type": "object", + "properties": { + "createdAt": { + "type": "string" + }, + "createdId": { + "type": "string" + }, + "deletedAt": { + "type": "integer" + }, + "deletedId": { + "type": "string" + }, + "id": { + "type": "string" + }, + "phone": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + }, + "schema.WebSiteForm": { + "type": "object", + "properties": { + "phone": { + "type": "string" + } + } + }, + "schema.WorkOrder": { + "type": "object", + "properties": { + "content": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "createdId": { + "type": "string" + }, + "customerId": { + "type": "string" + }, + "deletedAt": { + "type": "integer" + }, + "deletedId": { + "type": "string" + }, + "id": { + "type": "string" + }, + "images": { + "type": "array", + "items": { + "type": "string" + } + }, + "records": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.Record" + } + }, + "status": { + "type": "string" + }, + "updatedAt": { + "type": "string" + }, + "videos": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "schema.WorkOrderForm": { + "type": "object", + "properties": { + "content": { + "type": "string" + }, + "customerId": { + "type": "string" + }, + "images": { + "type": "array", + "items": { + "type": "string" + } + }, + "records": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.Record" + } + }, + "status": { + "type": "string" + }, + "videos": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "schema.WorkOrderType": { + "type": "object", + "properties": { + "createdAt": { + "type": "string" + }, + "createdId": { + "type": "string" + }, + "deletedAt": { + "type": "integer" + }, + "deletedId": { + "type": "string" + }, + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "sequence": { + "type": "integer" + }, + "status": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + }, + "schema.WorkOrderTypeForm": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "sequence": { + "type": "integer" + }, + "status": { + "type": "string" + } + } + }, + "util.ResponseResult": { + "type": "object", + "properties": { + "data": {}, + "error": { + "$ref": "#/definitions/errors.Error" + }, + "success": { + "type": "boolean" + }, + "total": { + "type": "integer" + } + } + } + }, + "securityDefinitions": { + "ApiKeyAuth": { + "type": "apiKey", + "name": "Authorization", + "in": "header" + } + } +}` + +// SwaggerInfo holds exported Swagger Info so clients can modify it +var SwaggerInfo = &swag.Spec{ + Version: "v1.0.0", + Host: "", + BasePath: "", + Schemes: []string{}, + Title: "jinshan_community", + Description: "近山社区小程序平台", + InfoInstanceName: "swagger", + SwaggerTemplate: docTemplate, + LeftDelim: "{{", + RightDelim: "}}", +} + +func init() { + swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo) +} diff --git a/internal/swagger/swagger.json b/internal/swagger/swagger.json new file mode 100644 index 0000000..e20e0c2 --- /dev/null +++ b/internal/swagger/swagger.json @@ -0,0 +1,9048 @@ +{ + "swagger": "2.0", + "info": { + "description": "近山社区小程序平台", + "title": "jinshan_community", + "contact": {}, + "version": "v1.0.0" + }, + "paths": { + "/api/v1/activities": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "ActivityAPI" + ], + "summary": "Query activity list", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "pagination index", + "name": "current", + "in": "query", + "required": true + }, + { + "type": "integer", + "default": 10, + "description": "pagination size", + "name": "pageSize", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.Activity" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "ActivityAPI" + ], + "summary": "Create activity record", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.ActivityForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.Activity" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/activities/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "ActivityAPI" + ], + "summary": "Get activity record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.Activity" + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "ActivityAPI" + ], + "summary": "Update activity record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.ActivityForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "ActivityAPI" + ], + "summary": "Delete activity record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/activity-categories": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "ActivityCategoryAPI" + ], + "summary": "Query activity category list", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "pagination index", + "name": "current", + "in": "query", + "required": true + }, + { + "type": "integer", + "default": 10, + "description": "pagination size", + "name": "pageSize", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.ActivityCategory" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "ActivityCategoryAPI" + ], + "summary": "Create activity category record", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.ActivityCategoryForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.ActivityCategory" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/activity-categories/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "ActivityCategoryAPI" + ], + "summary": "Get activity category record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.ActivityCategory" + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "ActivityCategoryAPI" + ], + "summary": "Update activity category record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.ActivityCategoryForm" + } + } + ], + "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": [ + "ActivityCategoryAPI" + ], + "summary": "Delete activity category record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/ai-requests": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "AiRequestAPI" + ], + "summary": "Query ai request list", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "pagination index", + "name": "current", + "in": "query", + "required": true + }, + { + "type": "integer", + "default": 10, + "description": "pagination size", + "name": "pageSize", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.AiRequest" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "AiRequestAPI" + ], + "summary": "Create ai request record", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.AiRequestForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.AiRequest" + } + } + } + ] + } + }, + "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/ai-requests/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "AiRequestAPI" + ], + "summary": "Get ai request 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.AiRequest" + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "AiRequestAPI" + ], + "summary": "Update ai request 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.AiRequestForm" + } + } + ], + "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": [ + "AiRequestAPI" + ], + "summary": "Delete ai request record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/apps": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "AppAPI" + ], + "summary": "Query app list", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "pagination index", + "name": "current", + "in": "query", + "required": true + }, + { + "type": "integer", + "default": 10, + "description": "pagination size", + "name": "pageSize", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.App" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "AppAPI" + ], + "summary": "Create app record", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.AppForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.App" + } + } + } + ] + } + }, + "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/apps/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "AppAPI" + ], + "summary": "Get app 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.App" + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "AppAPI" + ], + "summary": "Update app 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.AppForm" + } + } + ], + "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": [ + "AppAPI" + ], + "summary": "Delete app record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/balances": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "BalanceAPI" + ], + "summary": "Query balance list", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "pagination index", + "name": "current", + "in": "query", + "required": true + }, + { + "type": "integer", + "default": 10, + "description": "pagination size", + "name": "pageSize", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.Balance" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "BalanceAPI" + ], + "summary": "Create balance record", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.BalanceForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.Balance" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/balances/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "BalanceAPI" + ], + "summary": "Get balance record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.Balance" + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "BalanceAPI" + ], + "summary": "Update balance record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.BalanceForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "BalanceAPI" + ], + "summary": "Delete balance record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/banners": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "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 + } + ], + "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/customers": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "CustomerAPI" + ], + "summary": "Query customer list", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "pagination index", + "name": "current", + "in": "query", + "required": true + }, + { + "type": "integer", + "default": 10, + "description": "pagination size", + "name": "pageSize", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.Customer" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "CustomerAPI" + ], + "summary": "Create customer record", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.CustomerForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.Customer" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/customers/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "CustomerAPI" + ], + "summary": "Get customer record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.Customer" + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "CustomerAPI" + ], + "summary": "Update customer record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.CustomerForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "CustomerAPI" + ], + "summary": "Delete customer record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/helps": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "HelpAPI" + ], + "summary": "Query help list", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "pagination index", + "name": "current", + "in": "query", + "required": true + }, + { + "type": "integer", + "default": 10, + "description": "pagination size", + "name": "pageSize", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.Help" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "HelpAPI" + ], + "summary": "Create help record", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.HelpForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.Help" + } + } + } + ] + } + }, + "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/helps/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "HelpAPI" + ], + "summary": "Get help 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.Help" + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "HelpAPI" + ], + "summary": "Update help 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.HelpForm" + } + } + ], + "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": [ + "HelpAPI" + ], + "summary": "Delete help record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/loggers": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "LoggerAPI" + ], + "summary": "Query logger list", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "pagination index", + "name": "current", + "in": "query", + "required": true + }, + { + "type": "integer", + "default": 10, + "description": "pagination size", + "name": "pageSize", + "in": "query", + "required": true + }, + { + "type": "string", + "description": "log level", + "name": "level", + "in": "query" + }, + { + "type": "string", + "description": "trace ID", + "name": "traceID", + "in": "query" + }, + { + "type": "string", + "description": "user name", + "name": "userName", + "in": "query" + }, + { + "type": "string", + "description": "log tag", + "name": "tag", + "in": "query" + }, + { + "type": "string", + "description": "log message", + "name": "message", + "in": "query" + }, + { + "type": "string", + "description": "start time", + "name": "startTime", + "in": "query" + }, + { + "type": "string", + "description": "end time", + "name": "endTime", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.Logger" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/login": { + "post": { + "tags": [ + "LoginAPI" + ], + "summary": "Login system with username and password", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.LoginForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.LoginToken" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/menus": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "MenuAPI" + ], + "summary": "Query menu tree data", + "parameters": [ + { + "type": "string", + "description": "Code path of menu (like xxx.xxx.xxx)", + "name": "code", + "in": "query" + }, + { + "type": "string", + "description": "Name of menu", + "name": "name", + "in": "query" + }, + { + "type": "boolean", + "description": "Whether to include menu resources", + "name": "includeResources", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.Menu" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "MenuAPI" + ], + "summary": "Create menu record", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.MenuForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.Menu" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/menus/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "MenuAPI" + ], + "summary": "Get menu record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.Menu" + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "MenuAPI" + ], + "summary": "Update menu record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.MenuForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "MenuAPI" + ], + "summary": "Delete menu record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/metting-room-orders": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "MettingRoomOrderAPI" + ], + "summary": "Query metting room order list", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "pagination index", + "name": "current", + "in": "query", + "required": true + }, + { + "type": "integer", + "default": 10, + "description": "pagination size", + "name": "pageSize", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.MettingRoomOrder" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "MettingRoomOrderAPI" + ], + "summary": "Create metting room order record", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.MettingRoomOrderForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.MettingRoomOrder" + } + } + } + ] + } + }, + "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/metting-room-orders/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "MettingRoomOrderAPI" + ], + "summary": "Get metting room order record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.MettingRoomOrder" + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "MettingRoomOrderAPI" + ], + "summary": "Update metting room order record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.MettingRoomOrderForm" + } + } + ], + "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": [ + "MettingRoomOrderAPI" + ], + "summary": "Delete metting room order record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/metting-rooms": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "MettingRoomAPI" + ], + "summary": "Query metting room list", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "pagination index", + "name": "current", + "in": "query", + "required": true + }, + { + "type": "integer", + "default": 10, + "description": "pagination size", + "name": "pageSize", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.MettingRoom" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "MettingRoomAPI" + ], + "summary": "Create metting room record", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.MettingRoomForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.MettingRoom" + } + } + } + ] + } + }, + "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/metting-rooms/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "MettingRoomAPI" + ], + "summary": "Get metting room 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.MettingRoom" + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "MettingRoomAPI" + ], + "summary": "Update metting room 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.MettingRoomForm" + } + } + ], + "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": [ + "MettingRoomAPI" + ], + "summary": "Delete metting room record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/notices": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "NoticeAPI" + ], + "summary": "Query notice list", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "pagination index", + "name": "current", + "in": "query", + "required": true + }, + { + "type": "integer", + "default": 10, + "description": "pagination size", + "name": "pageSize", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.Notice" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "NoticeAPI" + ], + "summary": "Create notice record", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.NoticeForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.Notice" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/notices/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "NoticeAPI" + ], + "summary": "Get notice record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.Notice" + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "NoticeAPI" + ], + "summary": "Update notice record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.NoticeForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "NoticeAPI" + ], + "summary": "Delete notice record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/orders": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "OrderAPI" + ], + "summary": "Query order list", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "pagination index", + "name": "current", + "in": "query", + "required": true + }, + { + "type": "integer", + "default": 10, + "description": "pagination size", + "name": "pageSize", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.Order" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "OrderAPI" + ], + "summary": "Create order record", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.OrderForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.Order" + } + } + } + ] + } + }, + "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/orders/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "OrderAPI" + ], + "summary": "Get order record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.Order" + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "OrderAPI" + ], + "summary": "Update order record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.OrderForm" + } + } + ], + "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": [ + "OrderAPI" + ], + "summary": "Delete order record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/product-categories": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "ProductCategoryAPI" + ], + "summary": "Query product category list", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "pagination index", + "name": "current", + "in": "query", + "required": true + }, + { + "type": "integer", + "default": 10, + "description": "pagination size", + "name": "pageSize", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.ProductCategory" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "ProductCategoryAPI" + ], + "summary": "Create product category record", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.ProductCategoryForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.ProductCategory" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/product-categories/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "ProductCategoryAPI" + ], + "summary": "Get product category record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.ProductCategory" + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "ProductCategoryAPI" + ], + "summary": "Update product category record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.ProductCategoryForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "ProductCategoryAPI" + ], + "summary": "Delete product category record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/products": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "ProductAPI" + ], + "summary": "Query product list", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "pagination index", + "name": "current", + "in": "query", + "required": true + }, + { + "type": "integer", + "default": 10, + "description": "pagination size", + "name": "pageSize", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.Product" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "ProductAPI" + ], + "summary": "Create product record", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.ProductForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.Product" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + }, + "/api/v1/products/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "ProductAPI" + ], + "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": [ + "ProductAPI" + ], + "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": [ + "ProductAPI" + ], + "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/reciprocities": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "ReciprocityAPI" + ], + "summary": "Query reciprocity list", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "pagination index", + "name": "current", + "in": "query", + "required": true + }, + { + "type": "integer", + "default": 10, + "description": "pagination size", + "name": "pageSize", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.Reciprocity" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "ReciprocityAPI" + ], + "summary": "Create reciprocity record", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.ReciprocityForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.Reciprocity" + } + } + } + ] + } + }, + "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/reciprocities/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "ReciprocityAPI" + ], + "summary": "Get reciprocity 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.Reciprocity" + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "ReciprocityAPI" + ], + "summary": "Update reciprocity 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.ReciprocityForm" + } + } + ], + "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": [ + "ReciprocityAPI" + ], + "summary": "Delete reciprocity 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/shops": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "ShopAPI" + ], + "summary": "Query shop list", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "pagination index", + "name": "current", + "in": "query", + "required": true + }, + { + "type": "integer", + "default": 10, + "description": "pagination size", + "name": "pageSize", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.Shop" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "ShopAPI" + ], + "summary": "Create shop record", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.ShopForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.Shop" + } + } + } + ] + } + }, + "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/shops/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "ShopAPI" + ], + "summary": "Get shop 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.Shop" + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "ShopAPI" + ], + "summary": "Update shop 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.ShopForm" + } + } + ], + "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": [ + "ShopAPI" + ], + "summary": "Delete shop 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/surrounding-service-types": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "SurroundingServiceTypeAPI" + ], + "summary": "Query surrounding service type list", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "pagination index", + "name": "current", + "in": "query", + "required": true + }, + { + "type": "integer", + "default": 10, + "description": "pagination size", + "name": "pageSize", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.SurroundingServiceType" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "SurroundingServiceTypeAPI" + ], + "summary": "Create surrounding service type record", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.SurroundingServiceTypeForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.SurroundingServiceType" + } + } + } + ] + } + }, + "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/surrounding-service-types/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "SurroundingServiceTypeAPI" + ], + "summary": "Get surrounding service type 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.SurroundingServiceType" + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "SurroundingServiceTypeAPI" + ], + "summary": "Update surrounding service type 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.SurroundingServiceTypeForm" + } + } + ], + "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": [ + "SurroundingServiceTypeAPI" + ], + "summary": "Delete surrounding service type 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/surrounding-services": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "SurroundingServiceAPI" + ], + "summary": "Query surrounding service list", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "pagination index", + "name": "current", + "in": "query", + "required": true + }, + { + "type": "integer", + "default": 10, + "description": "pagination size", + "name": "pageSize", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.SurroundingService" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "SurroundingServiceAPI" + ], + "summary": "Create surrounding service record", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.SurroundingServiceForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.SurroundingService" + } + } + } + ] + } + }, + "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/surrounding-services/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "SurroundingServiceAPI" + ], + "summary": "Get surrounding service 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.SurroundingService" + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "SurroundingServiceAPI" + ], + "summary": "Update surrounding service 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.SurroundingServiceForm" + } + } + ], + "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": [ + "SurroundingServiceAPI" + ], + "summary": "Delete surrounding service 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/web-sites": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "WebSiteAPI" + ], + "summary": "Query web site list", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "pagination index", + "name": "current", + "in": "query", + "required": true + }, + { + "type": "integer", + "default": 10, + "description": "pagination size", + "name": "pageSize", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.WebSite" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "WebSiteAPI" + ], + "summary": "Create web site 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-sites/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "WebSiteAPI" + ], + "summary": "Get web site 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.WebSite" + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "WebSiteAPI" + ], + "summary": "Update web site 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" + } + } + } + }, + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "WebSiteAPI" + ], + "summary": "Delete web site 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/work-order-types": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "WorkOrderTypeAPI" + ], + "summary": "Query work order type list", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "pagination index", + "name": "current", + "in": "query", + "required": true + }, + { + "type": "integer", + "default": 10, + "description": "pagination size", + "name": "pageSize", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.WorkOrderType" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "WorkOrderTypeAPI" + ], + "summary": "Create work order type record", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.WorkOrderTypeForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.WorkOrderType" + } + } + } + ] + } + }, + "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/work-order-types/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "WorkOrderTypeAPI" + ], + "summary": "Get work order type 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.WorkOrderType" + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "WorkOrderTypeAPI" + ], + "summary": "Update work order type 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.WorkOrderTypeForm" + } + } + ], + "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": [ + "WorkOrderTypeAPI" + ], + "summary": "Delete work order type 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/work-orders": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "WorkOrderAPI" + ], + "summary": "Query work order list", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "pagination index", + "name": "current", + "in": "query", + "required": true + }, + { + "type": "integer", + "default": 10, + "description": "pagination size", + "name": "pageSize", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.WorkOrder" + } + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "WorkOrderAPI" + ], + "summary": "Create work order record", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.WorkOrderForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.WorkOrder" + } + } + } + ] + } + }, + "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/work-orders/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "WorkOrderAPI" + ], + "summary": "Get work order record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/util.ResponseResult" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.WorkOrder" + } + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "WorkOrderAPI" + ], + "summary": "Update work order record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.WorkOrderForm" + } + } + ], + "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": [ + "WorkOrderAPI" + ], + "summary": "Delete work order record by ID", + "parameters": [ + { + "type": "string", + "description": "unique id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/util.ResponseResult" + } + } + } + } + } + }, + "definitions": { + "errors.Error": { + "type": "object", + "properties": { + "code": { + "type": "integer" + }, + "detail": { + "type": "string" + }, + "id": { + "type": "string" + }, + "status": { + "type": "string" + } + } + }, + "schema.Activity": { + "type": "object", + "properties": { + "address": { + "type": "string" + }, + "categoryId": { + "type": "string" + }, + "content": { + "type": "string" + }, + "cover": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "createdId": { + "type": "string" + }, + "deletedAt": { + "type": "integer" + }, + "deletedId": { + "type": "string" + }, + "endAt": { + "type": "string" + }, + "endSignupAt": { + "type": "string" + }, + "id": { + "type": "string" + }, + "images": { + "type": "array", + "items": { + "type": "string" + } + }, + "maxSignupNum": { + "type": "integer" + }, + "signupNum": { + "type": "integer" + }, + "startAt": { + "type": "string" + }, + "startSignupAt": { + "type": "string" + }, + "status": { + "type": "string" + }, + "title": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + }, + "schema.ActivityCategory": { + "type": "object", + "properties": { + "createdAt": { + "type": "string" + }, + "createdId": { + "type": "string" + }, + "deletedAt": { + "type": "integer" + }, + "deletedId": { + "type": "string" + }, + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "sequence": { + "type": "integer" + }, + "status": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + }, + "schema.ActivityCategoryForm": { + "type": "object" + }, + "schema.ActivityForm": { + "type": "object", + "properties": { + "address": { + "type": "string" + }, + "categoryId": { + "type": "string" + }, + "content": { + "type": "string" + }, + "cover": { + "type": "string" + }, + "endAt": { + "type": "string" + }, + "endSignupAt": { + "type": "string" + }, + "images": { + "type": "array", + "items": { + "type": "string" + } + }, + "maxSignupNum": { + "type": "integer" + }, + "signupNum": { + "type": "integer" + }, + "startAt": { + "type": "string" + }, + "startSignupAt": { + "type": "string" + }, + "status": { + "type": "string" + }, + "title": { + "type": "string" + } + } + }, + "schema.AiRequest": { + "type": "object", + "properties": { + "createdAt": { + "type": "string" + }, + "createdId": { + "type": "string" + }, + "deletedAt": { + "type": "integer" + }, + "deletedId": { + "type": "string" + }, + "id": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + }, + "schema.AiRequestForm": { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + }, + "schema.App": { + "type": "object", + "properties": { + "created_at": { + "description": "Create time", + "type": "string" + }, + "id": { + "description": "Unique ID", + "type": "string" + }, + "updated_at": { + "description": "Update time", + "type": "string" + } + } + }, + "schema.AppForm": { + "type": "object" + }, + "schema.Balance": { + "type": "object", + "properties": { + "after": { + "type": "integer" + }, + "before": { + "type": "integer" + }, + "change": { + "type": "integer" + }, + "createdAt": { + "type": "string" + }, + "createdId": { + "type": "string" + }, + "customerId": { + "type": "string" + }, + "deletedAt": { + "type": "integer" + }, + "deletedId": { + "type": "string" + }, + "id": { + "type": "string" + }, + "operatorId": { + "type": "string" + }, + "reason": { + "type": "string" + }, + "type": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + }, + "schema.BalanceForm": { + "type": "object", + "properties": { + "CreatedID": { + "type": "string" + }, + "after": { + "type": "integer" + }, + "before": { + "type": "integer" + }, + "change": { + "type": "integer" + }, + "customerId": { + "type": "string" + }, + "operatorId": { + "type": "string" + }, + "reason": { + "type": "string" + }, + "type": { + "type": "string" + } + } + }, + "schema.Banner": { + "type": "object", + "properties": { + "createdAt": { + "type": "string" + }, + "createdId": { + "type": "string" + }, + "deletedAt": { + "type": "integer" + }, + "deletedId": { + "type": "string" + }, + "desc": { + "type": "string" + }, + "id": { + "type": "string" + }, + "img": { + "type": "string" + }, + "link": { + "type": "string" + }, + "sequence": { + "type": "integer" + }, + "status": { + "type": "string" + }, + "title": { + "type": "string" + }, + "type": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + }, + "schema.BannerForm": { + "type": "object", + "properties": { + "desc": { + "type": "string" + }, + "img": { + "type": "string" + }, + "link": { + "type": "string" + }, + "sequence": { + "type": "integer" + }, + "status": { + "type": "string" + }, + "title": { + "type": "string" + }, + "type": { + "type": "string" + } + } + }, + "schema.Captcha": { + "type": "object", + "properties": { + "captcha_id": { + "description": "Captcha ID", + "type": "string" + } + } + }, + "schema.Customer": { + "type": "object", + "properties": { + "avatar": { + "type": "string" + }, + "birthday": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "createdId": { + "type": "string" + }, + "deletedAt": { + "type": "integer" + }, + "deletedId": { + "type": "string" + }, + "id": { + "type": "string" + }, + "introduce": { + "type": "string" + }, + "name": { + "type": "string" + }, + "phone": { + "type": "string" + }, + "status": { + "type": "string" + }, + "updatedAt": { + "type": "string" + }, + "wxSign": { + "type": "string" + } + } + }, + "schema.CustomerForm": { + "type": "object", + "properties": { + "avatar": { + "type": "string" + }, + "birthday": { + "type": "string" + }, + "introduce": { + "type": "string" + }, + "name": { + "type": "string" + }, + "phone": { + "type": "string" + }, + "status": { + "type": "string" + }, + "wxSign": { + "type": "string" + } + } + }, + "schema.Help": { + "type": "object", + "properties": { + "createdAt": { + "type": "string" + }, + "createdId": { + "type": "string" + }, + "customerId": { + "type": "string" + }, + "deletedAt": { + "type": "integer" + }, + "deletedId": { + "type": "string" + }, + "desc": { + "type": "string" + }, + "id": { + "type": "string" + }, + "img": { + "type": "string" + }, + "link": { + "type": "string" + }, + "sequence": { + "type": "integer" + }, + "status": { + "type": "string" + }, + "type": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + }, + "schema.HelpForm": { + "type": "object" + }, + "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.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.MettingRoom": { + "type": "object", + "properties": { + "createdAt": { + "type": "string" + }, + "createdId": { + "type": "string" + }, + "deletedAt": { + "type": "integer" + }, + "deletedId": { + "type": "string" + }, + "desc": { + "type": "string" + }, + "id": { + "type": "string" + }, + "imgs": { + "type": "array", + "items": { + "type": "string" + } + }, + "link": { + "type": "string" + }, + "maxNum": { + "type": "integer" + }, + "status": { + "type": "string" + }, + "title": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + }, + "schema.MettingRoomForm": { + "type": "object", + "properties": { + "desc": { + "type": "string" + }, + "imgs": { + "type": "array", + "items": { + "type": "string" + } + }, + "link": { + "type": "string" + }, + "maxNum": { + "type": "integer" + }, + "status": { + "type": "string" + }, + "title": { + "type": "string" + } + } + }, + "schema.MettingRoomOrder": { + "type": "object", + "properties": { + "concatName": { + "type": "string" + }, + "concatPhone": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "createdId": { + "type": "string" + }, + "customerId": { + "type": "string" + }, + "deletedAt": { + "type": "integer" + }, + "deletedId": { + "type": "string" + }, + "endAt": { + "type": "string" + }, + "id": { + "type": "string" + }, + "roomId": { + "type": "string" + }, + "startAt": { + "type": "string" + }, + "status": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + }, + "schema.MettingRoomOrderForm": { + "type": "object", + "properties": { + "concatName": { + "type": "string" + }, + "concatPhone": { + "type": "string" + }, + "customerId": { + "type": "string" + }, + "endAt": { + "type": "string" + }, + "roomId": { + "type": "string" + }, + "startAt": { + "type": "string" + }, + "status": { + "type": "string" + } + } + }, + "schema.Notice": { + "type": "object", + "properties": { + "createdAt": { + "type": "string" + }, + "createdId": { + "type": "string" + }, + "deletedAt": { + "type": "integer" + }, + "deletedId": { + "type": "string" + }, + "desc": { + "type": "string" + }, + "id": { + "type": "string" + }, + "link": { + "type": "string" + }, + "sequence": { + "type": "integer" + }, + "status": { + "type": "string" + }, + "title": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + }, + "schema.NoticeForm": { + "type": "object", + "properties": { + "desc": { + "type": "string" + }, + "link": { + "type": "string" + }, + "sequence": { + "type": "integer" + }, + "status": { + "type": "string" + }, + "title": { + "type": "string" + } + } + }, + "schema.Order": { + "type": "object", + "properties": { + "createdAt": { + "type": "string" + }, + "createdId": { + "type": "string" + }, + "customerId": { + "type": "string" + }, + "deletedAt": { + "type": "integer" + }, + "deletedId": { + "type": "string" + }, + "id": { + "type": "string" + }, + "num": { + "type": "integer" + }, + "productId": { + "type": "string" + }, + "shopId": { + "type": "string" + }, + "status": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + }, + "schema.OrderForm": { + "type": "object" + }, + "schema.Product": { + "type": "object", + "properties": { + "categoryId": { + "type": "string" + }, + "content": { + "type": "string" + }, + "cover": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "createdId": { + "type": "string" + }, + "deletedAt": { + "type": "integer" + }, + "deletedId": { + "type": "string" + }, + "id": { + "type": "string" + }, + "images": { + "type": "array", + "items": { + "type": "string" + } + }, + "introduce": { + "type": "string" + }, + "num": { + "type": "integer" + }, + "price": { + "type": "integer" + }, + "shopId": { + "type": "string" + }, + "status": { + "type": "string" + }, + "title": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + }, + "schema.ProductCategory": { + "type": "object", + "properties": { + "createdAt": { + "type": "string" + }, + "createdId": { + "type": "string" + }, + "deletedAt": { + "type": "integer" + }, + "deletedId": { + "type": "string" + }, + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "sequence": { + "type": "integer" + }, + "status": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + }, + "schema.ProductCategoryForm": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "sequence": { + "type": "integer" + }, + "status": { + "type": "string" + } + } + }, + "schema.ProductForm": { + "type": "object", + "properties": { + "categoryId": { + "type": "string" + }, + "content": { + "type": "string" + }, + "cover": { + "type": "string" + }, + "images": { + "type": "array", + "items": { + "type": "string" + } + }, + "introduce": { + "type": "string" + }, + "num": { + "type": "integer" + }, + "price": { + "type": "integer" + }, + "shopId": { + "type": "string" + }, + "status": { + "type": "string" + }, + "title": { + "type": "string" + } + } + }, + "schema.Reciprocity": { + "type": "object", + "properties": { + "content": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "createdId": { + "type": "string" + }, + "customerId": { + "type": "string" + }, + "deletedAt": { + "type": "integer" + }, + "deletedId": { + "type": "string" + }, + "id": { + "type": "string" + }, + "images": { + "type": "array", + "items": { + "type": "string" + } + }, + "status": { + "type": "string" + }, + "title": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + }, + "schema.ReciprocityForm": { + "type": "object", + "properties": { + "content": { + "type": "string" + }, + "customerId": { + "type": "string" + }, + "images": { + "type": "array", + "items": { + "type": "string" + } + }, + "status": { + "type": "string" + }, + "title": { + "type": "string" + } + } + }, + "schema.Record": { + "type": "object", + "properties": { + "content": { + "type": "string" + }, + "sendAt": { + "type": "string" + }, + "sender": { + "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.Shop": { + "type": "object", + "properties": { + "content": { + "type": "string" + }, + "cover": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "createdId": { + "type": "string" + }, + "deletedAt": { + "type": "integer" + }, + "deletedId": { + "type": "string" + }, + "id": { + "type": "string" + }, + "images": { + "type": "array", + "items": { + "type": "string" + } + }, + "introduce": { + "type": "string" + }, + "latitude": { + "type": "number" + }, + "longitude": { + "type": "number" + }, + "status": { + "type": "string" + }, + "title": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + }, + "schema.ShopForm": { + "type": "object", + "properties": { + "content": { + "type": "string" + }, + "cover": { + "type": "string" + }, + "images": { + "type": "array", + "items": { + "type": "string" + } + }, + "introduce": { + "type": "string" + }, + "latitude": { + "type": "number" + }, + "longitude": { + "type": "number" + }, + "status": { + "type": "string" + }, + "title": { + "type": "string" + } + } + }, + "schema.SurroundingService": { + "type": "object", + "properties": { + "content": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "createdId": { + "type": "string" + }, + "deletedAt": { + "type": "integer" + }, + "deletedId": { + "type": "string" + }, + "desc": { + "type": "string" + }, + "id": { + "type": "string" + }, + "img": { + "type": "string" + }, + "link": { + "type": "string" + }, + "sequence": { + "type": "integer" + }, + "status": { + "type": "string" + }, + "title": { + "type": "string" + }, + "typeId": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + }, + "schema.SurroundingServiceForm": { + "type": "object", + "properties": { + "content": { + "type": "string" + }, + "desc": { + "type": "string" + }, + "img": { + "type": "string" + }, + "link": { + "type": "string" + }, + "sequence": { + "type": "integer" + }, + "status": { + "type": "string" + }, + "title": { + "type": "string" + }, + "typeId": { + "type": "string" + } + } + }, + "schema.SurroundingServiceType": { + "type": "object", + "properties": { + "createdAt": { + "type": "string" + }, + "createdId": { + "type": "string" + }, + "deletedAt": { + "type": "integer" + }, + "deletedId": { + "type": "string" + }, + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "sequence": { + "type": "integer" + }, + "status": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + }, + "schema.SurroundingServiceTypeForm": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "sequence": { + "type": "integer" + }, + "status": { + "type": "string" + } + } + }, + "schema.UpdateCurrentUser": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "email": { + "description": "Email of user", + "type": "string", + "maxLength": 128 + }, + "name": { + "description": "Name of user", + "type": "string", + "maxLength": 64 + }, + "phone": { + "description": "Phone number of user", + "type": "string", + "maxLength": 32 + }, + "remark": { + "description": "Remark of user", + "type": "string", + "maxLength": 1024 + } + } + }, + "schema.UpdateLoginPassword": { + "type": "object", + "required": [ + "new_password", + "old_password" + ], + "properties": { + "new_password": { + "description": "New password (md5 hash)", + "type": "string" + }, + "old_password": { + "description": "Old password (md5 hash)", + "type": "string" + } + } + }, + "schema.User": { + "type": "object", + "properties": { + "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.WebSite": { + "type": "object", + "properties": { + "createdAt": { + "type": "string" + }, + "createdId": { + "type": "string" + }, + "deletedAt": { + "type": "integer" + }, + "deletedId": { + "type": "string" + }, + "id": { + "type": "string" + }, + "phone": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + }, + "schema.WebSiteForm": { + "type": "object", + "properties": { + "phone": { + "type": "string" + } + } + }, + "schema.WorkOrder": { + "type": "object", + "properties": { + "content": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "createdId": { + "type": "string" + }, + "customerId": { + "type": "string" + }, + "deletedAt": { + "type": "integer" + }, + "deletedId": { + "type": "string" + }, + "id": { + "type": "string" + }, + "images": { + "type": "array", + "items": { + "type": "string" + } + }, + "records": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.Record" + } + }, + "status": { + "type": "string" + }, + "updatedAt": { + "type": "string" + }, + "videos": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "schema.WorkOrderForm": { + "type": "object", + "properties": { + "content": { + "type": "string" + }, + "customerId": { + "type": "string" + }, + "images": { + "type": "array", + "items": { + "type": "string" + } + }, + "records": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.Record" + } + }, + "status": { + "type": "string" + }, + "videos": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "schema.WorkOrderType": { + "type": "object", + "properties": { + "createdAt": { + "type": "string" + }, + "createdId": { + "type": "string" + }, + "deletedAt": { + "type": "integer" + }, + "deletedId": { + "type": "string" + }, + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "sequence": { + "type": "integer" + }, + "status": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + }, + "schema.WorkOrderTypeForm": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "sequence": { + "type": "integer" + }, + "status": { + "type": "string" + } + } + }, + "util.ResponseResult": { + "type": "object", + "properties": { + "data": {}, + "error": { + "$ref": "#/definitions/errors.Error" + }, + "success": { + "type": "boolean" + }, + "total": { + "type": "integer" + } + } + } + }, + "securityDefinitions": { + "ApiKeyAuth": { + "type": "apiKey", + "name": "Authorization", + "in": "header" + } + } +} \ No newline at end of file diff --git a/internal/swagger/swagger.yaml b/internal/swagger/swagger.yaml new file mode 100644 index 0000000..2644044 --- /dev/null +++ b/internal/swagger/swagger.yaml @@ -0,0 +1,5604 @@ +definitions: + errors.Error: + properties: + code: + type: integer + detail: + type: string + id: + type: string + status: + type: string + type: object + schema.Activity: + properties: + address: + type: string + categoryId: + type: string + content: + type: string + cover: + type: string + createdAt: + type: string + createdId: + type: string + deletedAt: + type: integer + deletedId: + type: string + endAt: + type: string + endSignupAt: + type: string + id: + type: string + images: + items: + type: string + type: array + maxSignupNum: + type: integer + signupNum: + type: integer + startAt: + type: string + startSignupAt: + type: string + status: + type: string + title: + type: string + updatedAt: + type: string + type: object + schema.ActivityCategory: + properties: + createdAt: + type: string + createdId: + type: string + deletedAt: + type: integer + deletedId: + type: string + id: + type: string + name: + type: string + sequence: + type: integer + status: + type: string + updatedAt: + type: string + type: object + schema.ActivityCategoryForm: + type: object + schema.ActivityForm: + properties: + address: + type: string + categoryId: + type: string + content: + type: string + cover: + type: string + endAt: + type: string + endSignupAt: + type: string + images: + items: + type: string + type: array + maxSignupNum: + type: integer + signupNum: + type: integer + startAt: + type: string + startSignupAt: + type: string + status: + type: string + title: + type: string + type: object + schema.AiRequest: + properties: + createdAt: + type: string + createdId: + type: string + deletedAt: + type: integer + deletedId: + type: string + id: + type: string + updatedAt: + type: string + type: object + schema.AiRequestForm: + properties: + msg: + type: string + type: object + schema.App: + properties: + created_at: + description: Create time + type: string + id: + description: Unique ID + type: string + updated_at: + description: Update time + type: string + type: object + schema.AppForm: + type: object + schema.Balance: + properties: + after: + type: integer + before: + type: integer + change: + type: integer + createdAt: + type: string + createdId: + type: string + customerId: + type: string + deletedAt: + type: integer + deletedId: + type: string + id: + type: string + operatorId: + type: string + reason: + type: string + type: + type: string + updatedAt: + type: string + type: object + schema.BalanceForm: + properties: + CreatedID: + type: string + after: + type: integer + before: + type: integer + change: + type: integer + customerId: + type: string + operatorId: + type: string + reason: + type: string + type: + type: string + type: object + schema.Banner: + properties: + createdAt: + type: string + createdId: + type: string + deletedAt: + type: integer + deletedId: + type: string + desc: + type: string + id: + type: string + img: + type: string + link: + type: string + sequence: + type: integer + status: + type: string + title: + type: string + type: + type: string + updatedAt: + type: string + type: object + schema.BannerForm: + properties: + desc: + type: string + img: + type: string + link: + type: string + sequence: + type: integer + status: + type: string + title: + type: string + type: + type: string + type: object + schema.Captcha: + properties: + captcha_id: + description: Captcha ID + type: string + type: object + schema.Customer: + properties: + avatar: + type: string + birthday: + type: string + createdAt: + type: string + createdId: + type: string + deletedAt: + type: integer + deletedId: + type: string + id: + type: string + introduce: + type: string + name: + type: string + phone: + type: string + status: + type: string + updatedAt: + type: string + wxSign: + type: string + type: object + schema.CustomerForm: + properties: + avatar: + type: string + birthday: + type: string + introduce: + type: string + name: + type: string + phone: + type: string + status: + type: string + wxSign: + type: string + type: object + schema.Help: + properties: + createdAt: + type: string + createdId: + type: string + customerId: + type: string + deletedAt: + type: integer + deletedId: + type: string + desc: + type: string + id: + type: string + img: + type: string + link: + type: string + sequence: + type: integer + status: + type: string + type: + type: string + updatedAt: + type: string + type: object + schema.HelpForm: + 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.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.MettingRoom: + properties: + createdAt: + type: string + createdId: + type: string + deletedAt: + type: integer + deletedId: + type: string + desc: + type: string + id: + type: string + imgs: + items: + type: string + type: array + link: + type: string + maxNum: + type: integer + status: + type: string + title: + type: string + updatedAt: + type: string + type: object + schema.MettingRoomForm: + properties: + desc: + type: string + imgs: + items: + type: string + type: array + link: + type: string + maxNum: + type: integer + status: + type: string + title: + type: string + type: object + schema.MettingRoomOrder: + properties: + concatName: + type: string + concatPhone: + type: string + createdAt: + type: string + createdId: + type: string + customerId: + type: string + deletedAt: + type: integer + deletedId: + type: string + endAt: + type: string + id: + type: string + roomId: + type: string + startAt: + type: string + status: + type: string + updatedAt: + type: string + type: object + schema.MettingRoomOrderForm: + properties: + concatName: + type: string + concatPhone: + type: string + customerId: + type: string + endAt: + type: string + roomId: + type: string + startAt: + type: string + status: + type: string + type: object + schema.Notice: + properties: + createdAt: + type: string + createdId: + type: string + deletedAt: + type: integer + deletedId: + type: string + desc: + type: string + id: + type: string + link: + type: string + sequence: + type: integer + status: + type: string + title: + type: string + updatedAt: + type: string + type: object + schema.NoticeForm: + properties: + desc: + type: string + link: + type: string + sequence: + type: integer + status: + type: string + title: + type: string + type: object + schema.Order: + properties: + createdAt: + type: string + createdId: + type: string + customerId: + type: string + deletedAt: + type: integer + deletedId: + type: string + id: + type: string + num: + type: integer + productId: + type: string + shopId: + type: string + status: + type: string + updatedAt: + type: string + type: object + schema.OrderForm: + type: object + schema.Product: + properties: + categoryId: + type: string + content: + type: string + cover: + type: string + createdAt: + type: string + createdId: + type: string + deletedAt: + type: integer + deletedId: + type: string + id: + type: string + images: + items: + type: string + type: array + introduce: + type: string + num: + type: integer + price: + type: integer + shopId: + type: string + status: + type: string + title: + type: string + updatedAt: + type: string + type: object + schema.ProductCategory: + properties: + createdAt: + type: string + createdId: + type: string + deletedAt: + type: integer + deletedId: + type: string + id: + type: string + name: + type: string + sequence: + type: integer + status: + type: string + updatedAt: + type: string + type: object + schema.ProductCategoryForm: + properties: + name: + type: string + sequence: + type: integer + status: + type: string + type: object + schema.ProductForm: + properties: + categoryId: + type: string + content: + type: string + cover: + type: string + images: + items: + type: string + type: array + introduce: + type: string + num: + type: integer + price: + type: integer + shopId: + type: string + status: + type: string + title: + type: string + type: object + schema.Reciprocity: + properties: + content: + type: string + createdAt: + type: string + createdId: + type: string + customerId: + type: string + deletedAt: + type: integer + deletedId: + type: string + id: + type: string + images: + items: + type: string + type: array + status: + type: string + title: + type: string + updatedAt: + type: string + type: object + schema.ReciprocityForm: + properties: + content: + type: string + customerId: + type: string + images: + items: + type: string + type: array + status: + type: string + title: + type: string + type: object + schema.Record: + properties: + content: + type: string + sendAt: + type: string + sender: + 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.Shop: + properties: + content: + type: string + cover: + type: string + createdAt: + type: string + createdId: + type: string + deletedAt: + type: integer + deletedId: + type: string + id: + type: string + images: + items: + type: string + type: array + introduce: + type: string + latitude: + type: number + longitude: + type: number + status: + type: string + title: + type: string + updatedAt: + type: string + type: object + schema.ShopForm: + properties: + content: + type: string + cover: + type: string + images: + items: + type: string + type: array + introduce: + type: string + latitude: + type: number + longitude: + type: number + status: + type: string + title: + type: string + type: object + schema.SurroundingService: + properties: + content: + type: string + createdAt: + type: string + createdId: + type: string + deletedAt: + type: integer + deletedId: + type: string + desc: + type: string + id: + type: string + img: + type: string + link: + type: string + sequence: + type: integer + status: + type: string + title: + type: string + typeId: + type: string + updatedAt: + type: string + type: object + schema.SurroundingServiceForm: + properties: + content: + type: string + desc: + type: string + img: + type: string + link: + type: string + sequence: + type: integer + status: + type: string + title: + type: string + typeId: + type: string + type: object + schema.SurroundingServiceType: + properties: + createdAt: + type: string + createdId: + type: string + deletedAt: + type: integer + deletedId: + type: string + id: + type: string + name: + type: string + sequence: + type: integer + status: + type: string + updatedAt: + type: string + type: object + schema.SurroundingServiceTypeForm: + properties: + name: + type: string + sequence: + type: integer + status: + type: string + type: object + schema.UpdateCurrentUser: + properties: + email: + description: Email of user + maxLength: 128 + type: string + name: + description: Name of user + maxLength: 64 + type: string + phone: + description: Phone number of user + maxLength: 32 + type: string + remark: + description: Remark of user + maxLength: 1024 + type: string + required: + - name + type: object + schema.UpdateLoginPassword: + properties: + new_password: + description: New password (md5 hash) + type: string + old_password: + description: Old password (md5 hash) + type: string + required: + - new_password + - old_password + type: object + schema.User: + properties: + 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.WebSite: + properties: + createdAt: + type: string + createdId: + type: string + deletedAt: + type: integer + deletedId: + type: string + id: + type: string + phone: + type: string + updatedAt: + type: string + type: object + schema.WebSiteForm: + properties: + phone: + type: string + type: object + schema.WorkOrder: + properties: + content: + type: string + createdAt: + type: string + createdId: + type: string + customerId: + type: string + deletedAt: + type: integer + deletedId: + type: string + id: + type: string + images: + items: + type: string + type: array + records: + items: + $ref: '#/definitions/schema.Record' + type: array + status: + type: string + updatedAt: + type: string + videos: + items: + type: string + type: array + type: object + schema.WorkOrderForm: + properties: + content: + type: string + customerId: + type: string + images: + items: + type: string + type: array + records: + items: + $ref: '#/definitions/schema.Record' + type: array + status: + type: string + videos: + items: + type: string + type: array + type: object + schema.WorkOrderType: + properties: + createdAt: + type: string + createdId: + type: string + deletedAt: + type: integer + deletedId: + type: string + id: + type: string + name: + type: string + sequence: + type: integer + status: + type: string + updatedAt: + type: string + type: object + schema.WorkOrderTypeForm: + properties: + name: + type: string + sequence: + type: integer + status: + type: string + type: object + util.ResponseResult: + properties: + data: {} + error: + $ref: '#/definitions/errors.Error' + success: + type: boolean + total: + type: integer + type: object +info: + contact: {} + description: 近山社区小程序平台 + title: jinshan_community + version: v1.0.0 +paths: + /api/v1/activities: + get: + parameters: + - default: 1 + description: pagination index + in: query + name: current + required: true + type: integer + - default: 10 + description: pagination size + in: query + name: pageSize + required: true + type: integer + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + items: + $ref: '#/definitions/schema.Activity' + type: array + type: object + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Query activity list + tags: + - ActivityAPI + post: + parameters: + - description: Request body + in: body + name: body + required: true + schema: + $ref: '#/definitions/schema.ActivityForm' + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + $ref: '#/definitions/schema.Activity' + type: object + "400": + description: Bad Request + schema: + $ref: '#/definitions/util.ResponseResult' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Create activity record + tags: + - ActivityAPI + /api/v1/activities/{id}: + delete: + parameters: + - description: unique id + in: path + name: id + required: true + type: string + responses: + "200": + description: OK + schema: + $ref: '#/definitions/util.ResponseResult' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Delete activity record by ID + tags: + - ActivityAPI + get: + parameters: + - description: unique id + in: path + name: id + required: true + type: string + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + $ref: '#/definitions/schema.Activity' + type: object + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Get activity record by ID + tags: + - ActivityAPI + put: + parameters: + - description: unique id + in: path + name: id + required: true + type: string + - description: Request body + in: body + name: body + required: true + schema: + $ref: '#/definitions/schema.ActivityForm' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/util.ResponseResult' + "400": + description: Bad Request + schema: + $ref: '#/definitions/util.ResponseResult' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Update activity record by ID + tags: + - ActivityAPI + /api/v1/activity-categories: + get: + parameters: + - default: 1 + description: pagination index + in: query + name: current + required: true + type: integer + - default: 10 + description: pagination size + in: query + name: pageSize + required: true + type: integer + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + items: + $ref: '#/definitions/schema.ActivityCategory' + type: array + type: object + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Query activity category list + tags: + - ActivityCategoryAPI + post: + parameters: + - description: Request body + in: body + name: body + required: true + schema: + $ref: '#/definitions/schema.ActivityCategoryForm' + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + $ref: '#/definitions/schema.ActivityCategory' + type: object + "400": + description: Bad Request + schema: + $ref: '#/definitions/util.ResponseResult' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Create activity category record + tags: + - ActivityCategoryAPI + /api/v1/activity-categories/{id}: + delete: + parameters: + - description: unique id + in: path + name: id + required: true + type: string + responses: + "200": + description: OK + schema: + $ref: '#/definitions/util.ResponseResult' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Delete activity category record by ID + tags: + - ActivityCategoryAPI + 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.ActivityCategory' + type: object + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Get activity category record by ID + tags: + - ActivityCategoryAPI + 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.ActivityCategoryForm' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/util.ResponseResult' + "400": + description: Bad Request + schema: + $ref: '#/definitions/util.ResponseResult' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Update activity category record by ID + tags: + - ActivityCategoryAPI + /api/v1/ai-requests: + get: + parameters: + - default: 1 + description: pagination index + in: query + name: current + required: true + type: integer + - default: 10 + description: pagination size + in: query + name: pageSize + required: true + type: integer + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + items: + $ref: '#/definitions/schema.AiRequest' + 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 ai request list + tags: + - AiRequestAPI + post: + parameters: + - description: Request body + in: body + name: body + required: true + schema: + $ref: '#/definitions/schema.AiRequestForm' + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + $ref: '#/definitions/schema.AiRequest' + 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 ai request record + tags: + - AiRequestAPI + /api/v1/ai-requests/{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 ai request record by ID + tags: + - AiRequestAPI + 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.AiRequest' + type: object + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Get ai request record by ID + tags: + - AiRequestAPI + 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.AiRequestForm' + 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 ai request record by ID + tags: + - AiRequestAPI + /api/v1/apps: + get: + parameters: + - default: 1 + description: pagination index + in: query + name: current + required: true + type: integer + - default: 10 + description: pagination size + in: query + name: pageSize + required: true + type: integer + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + items: + $ref: '#/definitions/schema.App' + 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 app list + tags: + - AppAPI + post: + parameters: + - description: Request body + in: body + name: body + required: true + schema: + $ref: '#/definitions/schema.AppForm' + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + $ref: '#/definitions/schema.App' + 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 app record + tags: + - AppAPI + /api/v1/apps/{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 app record by ID + tags: + - AppAPI + 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.App' + type: object + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Get app record by ID + tags: + - AppAPI + 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.AppForm' + 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 app record by ID + tags: + - AppAPI + /api/v1/balances: + get: + parameters: + - default: 1 + description: pagination index + in: query + name: current + required: true + type: integer + - default: 10 + description: pagination size + in: query + name: pageSize + required: true + type: integer + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + items: + $ref: '#/definitions/schema.Balance' + type: array + type: object + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Query balance list + tags: + - BalanceAPI + post: + parameters: + - description: Request body + in: body + name: body + required: true + schema: + $ref: '#/definitions/schema.BalanceForm' + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + $ref: '#/definitions/schema.Balance' + type: object + "400": + description: Bad Request + schema: + $ref: '#/definitions/util.ResponseResult' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Create balance record + tags: + - BalanceAPI + /api/v1/balances/{id}: + delete: + parameters: + - description: unique id + in: path + name: id + required: true + type: string + responses: + "200": + description: OK + schema: + $ref: '#/definitions/util.ResponseResult' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Delete balance record by ID + tags: + - BalanceAPI + get: + parameters: + - description: unique id + in: path + name: id + required: true + type: string + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + $ref: '#/definitions/schema.Balance' + type: object + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Get balance record by ID + tags: + - BalanceAPI + put: + parameters: + - description: unique id + in: path + name: id + required: true + type: string + - description: Request body + in: body + name: body + required: true + schema: + $ref: '#/definitions/schema.BalanceForm' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/util.ResponseResult' + "400": + description: Bad Request + schema: + $ref: '#/definitions/util.ResponseResult' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Update balance record by ID + tags: + - BalanceAPI + /api/v1/banners: + get: + parameters: + - default: 1 + description: pagination index + in: query + name: current + required: true + type: integer + - default: 10 + description: pagination size + in: query + name: pageSize + required: true + type: integer + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + items: + $ref: '#/definitions/schema.Banner' + type: array + type: object + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Query banner list + tags: + - 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/customers: + get: + parameters: + - default: 1 + description: pagination index + in: query + name: current + required: true + type: integer + - default: 10 + description: pagination size + in: query + name: pageSize + required: true + type: integer + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + items: + $ref: '#/definitions/schema.Customer' + type: array + type: object + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Query customer list + tags: + - CustomerAPI + post: + parameters: + - description: Request body + in: body + name: body + required: true + schema: + $ref: '#/definitions/schema.CustomerForm' + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + $ref: '#/definitions/schema.Customer' + type: object + "400": + description: Bad Request + schema: + $ref: '#/definitions/util.ResponseResult' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Create customer record + tags: + - CustomerAPI + /api/v1/customers/{id}: + delete: + parameters: + - description: unique id + in: path + name: id + required: true + type: string + responses: + "200": + description: OK + schema: + $ref: '#/definitions/util.ResponseResult' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Delete customer record by ID + tags: + - CustomerAPI + get: + parameters: + - description: unique id + in: path + name: id + required: true + type: string + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + $ref: '#/definitions/schema.Customer' + type: object + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Get customer record by ID + tags: + - CustomerAPI + put: + parameters: + - description: unique id + in: path + name: id + required: true + type: string + - description: Request body + in: body + name: body + required: true + schema: + $ref: '#/definitions/schema.CustomerForm' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/util.ResponseResult' + "400": + description: Bad Request + schema: + $ref: '#/definitions/util.ResponseResult' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Update customer record by ID + tags: + - CustomerAPI + /api/v1/helps: + get: + parameters: + - default: 1 + description: pagination index + in: query + name: current + required: true + type: integer + - default: 10 + description: pagination size + in: query + name: pageSize + required: true + type: integer + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + items: + $ref: '#/definitions/schema.Help' + 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 help list + tags: + - HelpAPI + post: + parameters: + - description: Request body + in: body + name: body + required: true + schema: + $ref: '#/definitions/schema.HelpForm' + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + $ref: '#/definitions/schema.Help' + 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 help record + tags: + - HelpAPI + /api/v1/helps/{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 help record by ID + tags: + - HelpAPI + 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.Help' + type: object + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Get help record by ID + tags: + - HelpAPI + 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.HelpForm' + 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 help record by ID + tags: + - HelpAPI + /api/v1/loggers: + get: + parameters: + - default: 1 + description: pagination index + in: query + name: current + required: true + type: integer + - default: 10 + description: pagination size + in: query + name: pageSize + required: true + type: integer + - description: log level + in: query + name: level + type: string + - description: trace ID + in: query + name: traceID + type: string + - description: user name + in: query + name: userName + type: string + - description: log tag + in: query + name: tag + type: string + - description: log message + in: query + name: message + type: string + - description: start time + in: query + name: startTime + type: string + - description: end time + in: query + name: endTime + type: string + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + items: + $ref: '#/definitions/schema.Logger' + type: array + type: object + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Query logger list + tags: + - LoggerAPI + /api/v1/login: + post: + parameters: + - description: Request body + in: body + name: body + required: true + schema: + $ref: '#/definitions/schema.LoginForm' + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + $ref: '#/definitions/schema.LoginToken' + type: object + "400": + description: Bad Request + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + summary: Login system with username and password + tags: + - LoginAPI + /api/v1/menus: + get: + parameters: + - description: Code path of menu (like xxx.xxx.xxx) + in: query + name: code + type: string + - description: Name of menu + in: query + name: name + type: string + - description: Whether to include menu resources + in: query + name: includeResources + type: boolean + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + items: + $ref: '#/definitions/schema.Menu' + type: array + type: object + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Query menu tree data + tags: + - MenuAPI + post: + parameters: + - description: Request body + in: body + name: body + required: true + schema: + $ref: '#/definitions/schema.MenuForm' + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + $ref: '#/definitions/schema.Menu' + type: object + "400": + description: Bad Request + schema: + $ref: '#/definitions/util.ResponseResult' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Create menu record + tags: + - MenuAPI + /api/v1/menus/{id}: + delete: + parameters: + - description: unique id + in: path + name: id + required: true + type: string + responses: + "200": + description: OK + schema: + $ref: '#/definitions/util.ResponseResult' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Delete menu record by ID + tags: + - MenuAPI + get: + parameters: + - description: unique id + in: path + name: id + required: true + type: string + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + $ref: '#/definitions/schema.Menu' + type: object + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Get menu record by ID + tags: + - MenuAPI + put: + parameters: + - description: unique id + in: path + name: id + required: true + type: string + - description: Request body + in: body + name: body + required: true + schema: + $ref: '#/definitions/schema.MenuForm' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/util.ResponseResult' + "400": + description: Bad Request + schema: + $ref: '#/definitions/util.ResponseResult' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Update menu record by ID + tags: + - MenuAPI + /api/v1/metting-room-orders: + get: + parameters: + - default: 1 + description: pagination index + in: query + name: current + required: true + type: integer + - default: 10 + description: pagination size + in: query + name: pageSize + required: true + type: integer + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + items: + $ref: '#/definitions/schema.MettingRoomOrder' + 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 metting room order list + tags: + - MettingRoomOrderAPI + post: + parameters: + - description: Request body + in: body + name: body + required: true + schema: + $ref: '#/definitions/schema.MettingRoomOrderForm' + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + $ref: '#/definitions/schema.MettingRoomOrder' + 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 metting room order record + tags: + - MettingRoomOrderAPI + /api/v1/metting-room-orders/{id}: + delete: + parameters: + - description: unique id + in: path + name: id + required: true + type: string + responses: + "200": + description: OK + schema: + $ref: '#/definitions/util.ResponseResult' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Delete metting room order record by ID + tags: + - MettingRoomOrderAPI + 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.MettingRoomOrder' + type: object + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Get metting room order record by ID + tags: + - MettingRoomOrderAPI + 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.MettingRoomOrderForm' + 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 metting room order record by ID + tags: + - MettingRoomOrderAPI + /api/v1/metting-rooms: + get: + parameters: + - default: 1 + description: pagination index + in: query + name: current + required: true + type: integer + - default: 10 + description: pagination size + in: query + name: pageSize + required: true + type: integer + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + items: + $ref: '#/definitions/schema.MettingRoom' + 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 metting room list + tags: + - MettingRoomAPI + post: + parameters: + - description: Request body + in: body + name: body + required: true + schema: + $ref: '#/definitions/schema.MettingRoomForm' + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + $ref: '#/definitions/schema.MettingRoom' + 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 metting room record + tags: + - MettingRoomAPI + /api/v1/metting-rooms/{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 metting room record by ID + tags: + - MettingRoomAPI + 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.MettingRoom' + type: object + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Get metting room record by ID + tags: + - MettingRoomAPI + 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.MettingRoomForm' + 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 metting room record by ID + tags: + - MettingRoomAPI + /api/v1/notices: + get: + parameters: + - default: 1 + description: pagination index + in: query + name: current + required: true + type: integer + - default: 10 + description: pagination size + in: query + name: pageSize + required: true + type: integer + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + items: + $ref: '#/definitions/schema.Notice' + type: array + type: object + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Query notice list + tags: + - NoticeAPI + post: + parameters: + - description: Request body + in: body + name: body + required: true + schema: + $ref: '#/definitions/schema.NoticeForm' + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + $ref: '#/definitions/schema.Notice' + type: object + "400": + description: Bad Request + schema: + $ref: '#/definitions/util.ResponseResult' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Create notice record + tags: + - NoticeAPI + /api/v1/notices/{id}: + delete: + parameters: + - description: unique id + in: path + name: id + required: true + type: string + responses: + "200": + description: OK + schema: + $ref: '#/definitions/util.ResponseResult' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Delete notice record by ID + tags: + - NoticeAPI + get: + parameters: + - description: unique id + in: path + name: id + required: true + type: string + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + $ref: '#/definitions/schema.Notice' + type: object + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Get notice record by ID + tags: + - NoticeAPI + put: + parameters: + - description: unique id + in: path + name: id + required: true + type: string + - description: Request body + in: body + name: body + required: true + schema: + $ref: '#/definitions/schema.NoticeForm' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/util.ResponseResult' + "400": + description: Bad Request + schema: + $ref: '#/definitions/util.ResponseResult' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Update notice record by ID + tags: + - NoticeAPI + /api/v1/orders: + get: + parameters: + - default: 1 + description: pagination index + in: query + name: current + required: true + type: integer + - default: 10 + description: pagination size + in: query + name: pageSize + required: true + type: integer + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + items: + $ref: '#/definitions/schema.Order' + 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 order list + tags: + - OrderAPI + post: + parameters: + - description: Request body + in: body + name: body + required: true + schema: + $ref: '#/definitions/schema.OrderForm' + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + $ref: '#/definitions/schema.Order' + 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 order record + tags: + - OrderAPI + /api/v1/orders/{id}: + delete: + parameters: + - description: unique id + in: path + name: id + required: true + type: string + responses: + "200": + description: OK + schema: + $ref: '#/definitions/util.ResponseResult' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Delete order record by ID + tags: + - OrderAPI + 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.Order' + type: object + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Get order record by ID + tags: + - OrderAPI + 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.OrderForm' + 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 order record by ID + tags: + - OrderAPI + /api/v1/product-categories: + get: + parameters: + - default: 1 + description: pagination index + in: query + name: current + required: true + type: integer + - default: 10 + description: pagination size + in: query + name: pageSize + required: true + type: integer + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + items: + $ref: '#/definitions/schema.ProductCategory' + type: array + type: object + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Query product category list + tags: + - ProductCategoryAPI + post: + parameters: + - description: Request body + in: body + name: body + required: true + schema: + $ref: '#/definitions/schema.ProductCategoryForm' + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + $ref: '#/definitions/schema.ProductCategory' + type: object + "400": + description: Bad Request + schema: + $ref: '#/definitions/util.ResponseResult' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Create product category record + tags: + - ProductCategoryAPI + /api/v1/product-categories/{id}: + delete: + parameters: + - description: unique id + in: path + name: id + required: true + type: string + responses: + "200": + description: OK + schema: + $ref: '#/definitions/util.ResponseResult' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Delete product category record by ID + tags: + - ProductCategoryAPI + get: + parameters: + - description: unique id + in: path + name: id + required: true + type: string + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + $ref: '#/definitions/schema.ProductCategory' + type: object + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Get product category record by ID + tags: + - ProductCategoryAPI + put: + parameters: + - description: unique id + in: path + name: id + required: true + type: string + - description: Request body + in: body + name: body + required: true + schema: + $ref: '#/definitions/schema.ProductCategoryForm' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/util.ResponseResult' + "400": + description: Bad Request + schema: + $ref: '#/definitions/util.ResponseResult' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Update product category record by ID + tags: + - ProductCategoryAPI + /api/v1/products: + get: + parameters: + - default: 1 + description: pagination index + in: query + name: current + required: true + type: integer + - default: 10 + description: pagination size + in: query + name: pageSize + required: true + type: integer + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + items: + $ref: '#/definitions/schema.Product' + type: array + type: object + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Query product list + tags: + - ProductAPI + 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: + - ProductAPI + /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: + - ProductAPI + 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: + - ProductAPI + 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: + - ProductAPI + /api/v1/reciprocities: + get: + parameters: + - default: 1 + description: pagination index + in: query + name: current + required: true + type: integer + - default: 10 + description: pagination size + in: query + name: pageSize + required: true + type: integer + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + items: + $ref: '#/definitions/schema.Reciprocity' + 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 reciprocity list + tags: + - ReciprocityAPI + post: + parameters: + - description: Request body + in: body + name: body + required: true + schema: + $ref: '#/definitions/schema.ReciprocityForm' + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + $ref: '#/definitions/schema.Reciprocity' + 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 reciprocity record + tags: + - ReciprocityAPI + /api/v1/reciprocities/{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 reciprocity record by ID + tags: + - ReciprocityAPI + 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.Reciprocity' + type: object + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Get reciprocity record by ID + tags: + - ReciprocityAPI + 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.ReciprocityForm' + 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 reciprocity record by ID + tags: + - ReciprocityAPI + /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/shops: + get: + parameters: + - default: 1 + description: pagination index + in: query + name: current + required: true + type: integer + - default: 10 + description: pagination size + in: query + name: pageSize + required: true + type: integer + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + items: + $ref: '#/definitions/schema.Shop' + 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 shop list + tags: + - ShopAPI + post: + parameters: + - description: Request body + in: body + name: body + required: true + schema: + $ref: '#/definitions/schema.ShopForm' + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + $ref: '#/definitions/schema.Shop' + 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 shop record + tags: + - ShopAPI + /api/v1/shops/{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 shop record by ID + tags: + - ShopAPI + 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.Shop' + type: object + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Get shop record by ID + tags: + - ShopAPI + 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.ShopForm' + 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 shop record by ID + tags: + - ShopAPI + /api/v1/surrounding-service-types: + get: + parameters: + - default: 1 + description: pagination index + in: query + name: current + required: true + type: integer + - default: 10 + description: pagination size + in: query + name: pageSize + required: true + type: integer + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + items: + $ref: '#/definitions/schema.SurroundingServiceType' + 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 surrounding service type list + tags: + - SurroundingServiceTypeAPI + post: + parameters: + - description: Request body + in: body + name: body + required: true + schema: + $ref: '#/definitions/schema.SurroundingServiceTypeForm' + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + $ref: '#/definitions/schema.SurroundingServiceType' + 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 surrounding service type record + tags: + - SurroundingServiceTypeAPI + /api/v1/surrounding-service-types/{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 surrounding service type record by ID + tags: + - SurroundingServiceTypeAPI + 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.SurroundingServiceType' + type: object + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Get surrounding service type record by ID + tags: + - SurroundingServiceTypeAPI + 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.SurroundingServiceTypeForm' + 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 surrounding service type record by ID + tags: + - SurroundingServiceTypeAPI + /api/v1/surrounding-services: + get: + parameters: + - default: 1 + description: pagination index + in: query + name: current + required: true + type: integer + - default: 10 + description: pagination size + in: query + name: pageSize + required: true + type: integer + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + items: + $ref: '#/definitions/schema.SurroundingService' + 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 surrounding service list + tags: + - SurroundingServiceAPI + post: + parameters: + - description: Request body + in: body + name: body + required: true + schema: + $ref: '#/definitions/schema.SurroundingServiceForm' + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + $ref: '#/definitions/schema.SurroundingService' + 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 surrounding service record + tags: + - SurroundingServiceAPI + /api/v1/surrounding-services/{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 surrounding service record by ID + tags: + - SurroundingServiceAPI + 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.SurroundingService' + type: object + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Get surrounding service record by ID + tags: + - SurroundingServiceAPI + 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.SurroundingServiceForm' + 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 surrounding service record by ID + tags: + - SurroundingServiceAPI + /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/web-sites: + get: + parameters: + - default: 1 + description: pagination index + in: query + name: current + required: true + type: integer + - default: 10 + description: pagination size + in: query + name: pageSize + required: true + type: integer + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + items: + $ref: '#/definitions/schema.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 web site list + tags: + - WebSiteAPI + 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 web site record + tags: + - WebSiteAPI + /api/v1/web-sites/{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 web site record by ID + tags: + - WebSiteAPI + 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.WebSite' + type: object + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Get web site record by ID + tags: + - WebSiteAPI + 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 web site record by ID + tags: + - WebSiteAPI + /api/v1/work-order-types: + get: + parameters: + - default: 1 + description: pagination index + in: query + name: current + required: true + type: integer + - default: 10 + description: pagination size + in: query + name: pageSize + required: true + type: integer + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + items: + $ref: '#/definitions/schema.WorkOrderType' + 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 work order type list + tags: + - WorkOrderTypeAPI + post: + parameters: + - description: Request body + in: body + name: body + required: true + schema: + $ref: '#/definitions/schema.WorkOrderTypeForm' + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + $ref: '#/definitions/schema.WorkOrderType' + 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 work order type record + tags: + - WorkOrderTypeAPI + /api/v1/work-order-types/{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 work order type record by ID + tags: + - WorkOrderTypeAPI + 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.WorkOrderType' + type: object + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Get work order type record by ID + tags: + - WorkOrderTypeAPI + 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.WorkOrderTypeForm' + 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 work order type record by ID + tags: + - WorkOrderTypeAPI + /api/v1/work-orders: + get: + parameters: + - default: 1 + description: pagination index + in: query + name: current + required: true + type: integer + - default: 10 + description: pagination size + in: query + name: pageSize + required: true + type: integer + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + items: + $ref: '#/definitions/schema.WorkOrder' + 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 work order list + tags: + - WorkOrderAPI + post: + parameters: + - description: Request body + in: body + name: body + required: true + schema: + $ref: '#/definitions/schema.WorkOrderForm' + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/util.ResponseResult' + - properties: + data: + $ref: '#/definitions/schema.WorkOrder' + 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 work order record + tags: + - WorkOrderAPI + /api/v1/work-orders/{id}: + delete: + parameters: + - description: unique id + in: path + name: id + required: true + type: string + responses: + "200": + description: OK + schema: + $ref: '#/definitions/util.ResponseResult' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Delete work order record by ID + tags: + - WorkOrderAPI + 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.WorkOrder' + type: object + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.ResponseResult' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/util.ResponseResult' + security: + - ApiKeyAuth: [] + summary: Get work order record by ID + tags: + - WorkOrderAPI + 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.WorkOrderForm' + 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 work order record by ID + tags: + - WorkOrderAPI +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..7fa0697 --- /dev/null +++ b/internal/utility/prom/prom.go @@ -0,0 +1,36 @@ +package prom + +import ( + "gitlab.guxuan.icu/jinshan_community/internal/config" + "gitlab.guxuan.icu/jinshan_community/pkg/promx" + "gitlab.guxuan.icu/jinshan_community/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..2eb7b7c --- /dev/null +++ b/internal/wirex/injector.go @@ -0,0 +1,129 @@ +package wirex + +import ( + "context" + "time" + + "gitlab.guxuan.icu/jinshan_community/internal/config" + "gitlab.guxuan.icu/jinshan_community/internal/mods" + "gitlab.guxuan.icu/jinshan_community/pkg/cachex" + "gitlab.guxuan.icu/jinshan_community/pkg/gormx" + "gitlab.guxuan.icu/jinshan_community/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..1a45738 --- /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" + + "gitlab.guxuan.icu/jinshan_community/internal/mods" + "gitlab.guxuan.icu/jinshan_community/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..2d7ef38 --- /dev/null +++ b/internal/wirex/wire_gen.go @@ -0,0 +1,415 @@ +// 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" + "gitlab.guxuan.icu/jinshan_community/internal/mods" + "gitlab.guxuan.icu/jinshan_community/internal/mods/activity" + api4 "gitlab.guxuan.icu/jinshan_community/internal/mods/activity/api" + biz4 "gitlab.guxuan.icu/jinshan_community/internal/mods/activity/biz" + dal4 "gitlab.guxuan.icu/jinshan_community/internal/mods/activity/dal" + "gitlab.guxuan.icu/jinshan_community/internal/mods/ai" + api7 "gitlab.guxuan.icu/jinshan_community/internal/mods/ai/api" + biz7 "gitlab.guxuan.icu/jinshan_community/internal/mods/ai/biz" + dal7 "gitlab.guxuan.icu/jinshan_community/internal/mods/ai/dal" + "gitlab.guxuan.icu/jinshan_community/internal/mods/app" + api6 "gitlab.guxuan.icu/jinshan_community/internal/mods/app/api" + biz6 "gitlab.guxuan.icu/jinshan_community/internal/mods/app/biz" + dal6 "gitlab.guxuan.icu/jinshan_community/internal/mods/app/dal" + "gitlab.guxuan.icu/jinshan_community/internal/mods/common" + api3 "gitlab.guxuan.icu/jinshan_community/internal/mods/common/api" + biz3 "gitlab.guxuan.icu/jinshan_community/internal/mods/common/biz" + dal3 "gitlab.guxuan.icu/jinshan_community/internal/mods/common/dal" + "gitlab.guxuan.icu/jinshan_community/internal/mods/customer" + api2 "gitlab.guxuan.icu/jinshan_community/internal/mods/customer/api" + biz2 "gitlab.guxuan.icu/jinshan_community/internal/mods/customer/biz" + dal2 "gitlab.guxuan.icu/jinshan_community/internal/mods/customer/dal" + "gitlab.guxuan.icu/jinshan_community/internal/mods/product" + api5 "gitlab.guxuan.icu/jinshan_community/internal/mods/product/api" + biz5 "gitlab.guxuan.icu/jinshan_community/internal/mods/product/biz" + dal5 "gitlab.guxuan.icu/jinshan_community/internal/mods/product/dal" + "gitlab.guxuan.icu/jinshan_community/internal/mods/rbac" + "gitlab.guxuan.icu/jinshan_community/internal/mods/rbac/api" + "gitlab.guxuan.icu/jinshan_community/internal/mods/rbac/biz" + "gitlab.guxuan.icu/jinshan_community/internal/mods/rbac/dal" + "gitlab.guxuan.icu/jinshan_community/pkg/util" +) + +// Injectors from wire.go: + +func BuildInjector(ctx context.Context) (*Injector, func(), error) { + db, cleanup, err := InitDB(ctx) + if err != nil { + return nil, nil, err + } + cacher, cleanup2, err := InitCacher(ctx) + if err != nil { + cleanup() + return nil, nil, err + } + auther, cleanup3, err := InitAuth(ctx) + if err != nil { + cleanup2() + cleanup() + return nil, nil, err + } + trans := &util.Trans{ + DB: db, + } + menu := &dal.Menu{ + DB: db, + } + menuResource := &dal.MenuResource{ + DB: db, + } + roleMenu := &dal.RoleMenu{ + DB: db, + } + bizMenu := &biz.Menu{ + Cache: cacher, + Trans: trans, + MenuDAL: menu, + MenuResourceDAL: menuResource, + RoleMenuDAL: roleMenu, + } + apiMenu := &api.Menu{ + MenuBIZ: bizMenu, + } + role := &dal.Role{ + DB: db, + } + userRole := &dal.UserRole{ + DB: db, + } + bizRole := &biz.Role{ + Cache: cacher, + Trans: trans, + RoleDAL: role, + RoleMenuDAL: roleMenu, + UserRoleDAL: userRole, + } + apiRole := &api.Role{ + RoleBIZ: bizRole, + } + user := &dal.User{ + DB: db, + } + bizUser := &biz.User{ + Cache: cacher, + Trans: trans, + UserDAL: user, + UserRoleDAL: userRole, + } + apiUser := &api.User{ + UserBIZ: bizUser, + } + login := &biz.Login{ + Cache: cacher, + Auth: auther, + UserDAL: user, + UserRoleDAL: userRole, + MenuDAL: menu, + UserBIZ: bizUser, + } + apiLogin := &api.Login{ + LoginBIZ: login, + } + logger := &dal.Logger{ + DB: db, + } + bizLogger := &biz.Logger{ + LoggerDAL: logger, + } + apiLogger := &api.Logger{ + LoggerBIZ: bizLogger, + } + casbinx := &rbac.Casbinx{ + Cache: cacher, + MenuDAL: menu, + MenuResourceDAL: menuResource, + RoleDAL: role, + } + rbacRBAC := &rbac.RBAC{ + DB: db, + MenuAPI: apiMenu, + RoleAPI: apiRole, + UserAPI: apiUser, + LoginAPI: apiLogin, + LoggerAPI: apiLogger, + Casbinx: casbinx, + } + dalCustomer := &dal2.Customer{ + DB: db, + } + bizCustomer := &biz2.Customer{ + Trans: trans, + CustomerDAL: dalCustomer, + } + apiCustomer := &api2.Customer{ + CustomerBIZ: bizCustomer, + } + balance := &dal2.Balance{ + DB: db, + } + bizBalance := &biz2.Balance{ + Trans: trans, + BalanceDAL: balance, + } + apiBalance := &api2.Balance{ + BalanceBIZ: bizBalance, + } + customerCustomer := &customer.Customer{ + DB: db, + CustomerAPI: apiCustomer, + BalanceAPI: apiBalance, + } + banner := &dal3.Banner{ + DB: db, + } + bizBanner := &biz3.Banner{ + Trans: trans, + BannerDAL: banner, + } + apiBanner := &api3.Banner{ + BannerBIZ: bizBanner, + } + notice := &dal3.Notice{ + DB: db, + } + bizNotice := &biz3.Notice{ + Trans: trans, + NoticeDAL: notice, + } + apiNotice := &api3.Notice{ + NoticeBIZ: bizNotice, + } + workOrder := &dal3.WorkOrder{ + DB: db, + } + bizWorkOrder := &biz3.WorkOrder{ + Trans: trans, + WorkOrderDAL: workOrder, + } + apiWorkOrder := &api3.WorkOrder{ + WorkOrderBIZ: bizWorkOrder, + } + workOrderType := &dal3.WorkOrderType{ + DB: db, + } + bizWorkOrderType := &biz3.WorkOrderType{ + Trans: trans, + WorkOrderTypeDAL: workOrderType, + } + apiWorkOrderType := &api3.WorkOrderType{ + WorkOrderTypeBIZ: bizWorkOrderType, + } + surroundingService := &dal3.SurroundingService{ + DB: db, + } + bizSurroundingService := &biz3.SurroundingService{ + Trans: trans, + SurroundingServiceDAL: surroundingService, + } + apiSurroundingService := &api3.SurroundingService{ + SurroundingServiceBIZ: bizSurroundingService, + } + surroundingServiceType := &dal3.SurroundingServiceType{ + DB: db, + } + bizSurroundingServiceType := &biz3.SurroundingServiceType{ + Trans: trans, + SurroundingServiceTypeDAL: surroundingServiceType, + } + apiSurroundingServiceType := &api3.SurroundingServiceType{ + SurroundingServiceTypeBIZ: bizSurroundingServiceType, + } + webSite := &dal3.WebSite{ + DB: db, + } + bizWebSite := &biz3.WebSite{ + Trans: trans, + WebSiteDAL: webSite, + } + apiWebSite := &api3.WebSite{ + WebSiteBIZ: bizWebSite, + } + help := &dal3.Help{ + DB: db, + } + bizHelp := &biz3.Help{ + Trans: trans, + HelpDAL: help, + } + apiHelp := &api3.Help{ + HelpBIZ: bizHelp, + } + mettingRoom := &dal3.MettingRoom{ + DB: db, + } + bizMettingRoom := &biz3.MettingRoom{ + Trans: trans, + MettingRoomDAL: mettingRoom, + } + apiMettingRoom := &api3.MettingRoom{ + MettingRoomBIZ: bizMettingRoom, + } + mettingRoomOrder := &dal3.MettingRoomOrder{ + DB: db, + } + bizMettingRoomOrder := &biz3.MettingRoomOrder{ + Trans: trans, + MettingRoomOrderDAL: mettingRoomOrder, + } + apiMettingRoomOrder := &api3.MettingRoomOrder{ + MettingRoomOrderBIZ: bizMettingRoomOrder, + } + commonCommon := &common.Common{ + DB: db, + BannerAPI: apiBanner, + NoticeAPI: apiNotice, + WorkOrderAPI: apiWorkOrder, + WorkOrderTypeAPI: apiWorkOrderType, + SurroundingServiceAPI: apiSurroundingService, + SurroundingServiceTypeAPI: apiSurroundingServiceType, + WebSiteAPI: apiWebSite, + HelpAPI: apiHelp, + MettingRoomAPI: apiMettingRoom, + MettingRoomOrderAPI: apiMettingRoomOrder, + } + dalActivity := &dal4.Activity{ + DB: db, + } + bizActivity := &biz4.Activity{ + Trans: trans, + ActivityDAL: dalActivity, + } + apiActivity := &api4.Activity{ + ActivityBIZ: bizActivity, + } + activityCategory := &dal4.ActivityCategory{ + DB: db, + } + bizActivityCategory := &biz4.ActivityCategory{ + Trans: trans, + ActivityCategoryDAL: activityCategory, + } + apiActivityCategory := &api4.ActivityCategory{ + ActivityCategoryBIZ: bizActivityCategory, + } + reciprocity := &dal4.Reciprocity{ + DB: db, + } + bizReciprocity := &biz4.Reciprocity{ + Trans: trans, + ReciprocityDAL: reciprocity, + } + apiReciprocity := &api4.Reciprocity{ + ReciprocityBIZ: bizReciprocity, + } + activityActivity := &activity.Activity{ + DB: db, + ActivityAPI: apiActivity, + ActivityCategoryAPI: apiActivityCategory, + ReciprocityAPI: apiReciprocity, + } + dalProduct := &dal5.Product{ + DB: db, + } + bizProduct := &biz5.Product{ + Trans: trans, + ProductDAL: dalProduct, + } + apiProduct := &api5.Product{ + ProductBIZ: bizProduct, + } + productCategory := &dal5.ProductCategory{ + DB: db, + } + bizProductCategory := &biz5.ProductCategory{ + Trans: trans, + ProductCategoryDAL: productCategory, + } + apiProductCategory := &api5.ProductCategory{ + ProductCategoryBIZ: bizProductCategory, + } + order := &dal5.Order{ + DB: db, + } + bizOrder := &biz5.Order{ + Trans: trans, + OrderDAL: order, + } + apiOrder := &api5.Order{ + OrderBIZ: bizOrder, + } + shop := &dal5.Shop{ + DB: db, + } + bizShop := &biz5.Shop{ + Trans: trans, + ShopDAL: shop, + } + apiShop := &api5.Shop{ + ShopBIZ: bizShop, + } + productProduct := &product.Product{ + DB: db, + ProductAPI: apiProduct, + ProductCategoryAPI: apiProductCategory, + OrderAPI: apiOrder, + ShopAPI: apiShop, + } + dalApp := &dal6.App{ + DB: db, + } + bizApp := &biz6.App{ + Trans: trans, + AppDAL: dalApp, + } + apiApp := &api6.App{ + AppBIZ: bizApp, + } + appApp := &app.App{ + DB: db, + AppAPI: apiApp, + } + aiRequest := &dal7.AiRequest{ + DB: db, + } + bizAiRequest := &biz7.AiRequest{ + Trans: trans, + AiRequestDAL: aiRequest, + } + apiAiRequest := &api7.AiRequest{ + AiRequestBIZ: bizAiRequest, + } + aiAi := &ai.Ai{ + DB: db, + AiRequestAPI: apiAiRequest, + } + modsMods := &mods.Mods{ + RBAC: rbacRBAC, + Customer: customerCustomer, + Common: commonCommon, + Activity: activityActivity, + Product: productProduct, + App: appApp, + Ai: aiAi, + } + 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..ce73fd8 --- /dev/null +++ b/main.go @@ -0,0 +1,35 @@ +package main + +import ( + "os" + + "gitlab.guxuan.icu/jinshan_community/cmd" + "github.com/urfave/cli/v2" +) + +// Usage: go build -ldflags "-X main.VERSION=x.x.x" +var VERSION = "v1.0.0" + +// @title jinshan_community +// @version v1.0.0 +// @description 近山社区小程序平台 +// @securityDefinitions.apikey ApiKeyAuth +// @in header +// @name Authorization +// @schemes http https +// @basePath / +func main() { + app := cli.NewApp() + app.Name = "jinshan_community" + app.Version = VERSION + app.Usage = "近山社区小程序平台" + app.Commands = []*cli.Command{ + cmd.StartCmd(), + cmd.StopCmd(), + cmd.VersionCmd(VERSION), + } + err := app.Run(os.Args) + if err != nil { + panic(err) + } +} diff --git a/pkg/ai/alBaiLian.go b/pkg/ai/alBaiLian.go new file mode 100644 index 0000000..fbe4a30 --- /dev/null +++ b/pkg/ai/alBaiLian.go @@ -0,0 +1,167 @@ +package ai + +import ( + "context" + "fmt" + "github.com/openai/openai-go" + "github.com/openai/openai-go/option" + "sync" +) + +type BaichuanClient struct { + client *openai.Client + mu sync.Mutex +} + +// NewBaichuanClient 创建新的百炼客户端实例 +func NewBaichuanClient(apiKey string) *BaichuanClient { + return &BaichuanClient{ + client: openai.NewClient( + option.WithAPIKey(apiKey), + option.WithBaseURL("https://dashscope.aliyuncs.com/compatible-mode/v1/"), + ), + } +} + +// ChatMessage 表示聊天消息的结构 +type ChatMessage struct { + Role string `json:"role"` // 角色: system, user, assistant + Content string `json:"content"` // 消息内容 +} + +// ChatCompletionParams 定义聊天完成请求的参数 +type ChatCompletionParams struct { + Model string `json:"model"` // 使用的模型 (如 qwen-plus, qwen-max) + Messages []ChatMessage `json:"messages"` // 聊天消息列表 + MaxToken int `json:"maxToken"` // 最大返回token数 (可选) + Stream bool `json:"stream"` // 是否使用流式响应 (可选) +} + +// ChatCompletionResponse 定义聊天完成的响应结构 +type ChatCompletionResponse struct { + ID string `json:"id"` // 请求ID + Content string `json:"content"` // 返回内容 + Model string `json:"model"` // 使用的模型 + Usage struct { + PromptTokens int `json:"promptTokens"` // 提示token数 + CompletionTokens int `json:"completionTokens"` // 完成token数 + TotalTokens int `json:"totalTokens"` // 总token数 + } `json:"usage"` +} + +// ChatCompletion 调用阿里百炼智能体进行聊天完成 +func (bc *BaichuanClient) ChatCompletion(ctx context.Context, params ChatCompletionParams) (*ChatCompletionResponse, error) { + bc.mu.Lock() + defer bc.mu.Unlock() + + // 转换消息格式 + openaiMessages := make([]openai.ChatCompletionMessageParamUnion, len(params.Messages)) + for i, msg := range params.Messages { + switch msg.Role { + case "system": + openaiMessages[i] = openai.SystemMessage(msg.Content) + case "assistant": + openaiMessages[i] = openai.AssistantMessage(msg.Content) + default: // 默认为用户消息 + openaiMessages[i] = openai.UserMessage(msg.Content) + } + } + + // 设置请求参数 + requestParams := openai.ChatCompletionNewParams{ + Messages: openai.F(openaiMessages), + Model: openai.F(params.Model), + } + + //// 设置可选参数 + //if params.MaxToken > 0 { + // requestParams.MaxTokens = openai.F(params.MaxToken) + //} + //if params.Stream { + // requestParams.Stream = openai.F(true) + //} + + // 调用API + chatCompletion, err := bc.client.Chat.Completions.New(ctx, requestParams) + if err != nil { + return nil, fmt.Errorf("API调用失败: %w", err) + } + + // 处理响应 + response := &ChatCompletionResponse{ + ID: chatCompletion.ID, + Model: chatCompletion.Model, + } + + if len(chatCompletion.Choices) > 0 { + response.Content = chatCompletion.Choices[0].Message.Content + } + + //// 添加使用量统计 + //if chatCompletion.Usage != nil { + // response.Usage.PromptTokens = chatCompletion.Usage.PromptTokens + // response.Usage.CompletionTokens = chatCompletion.Usage.CompletionTokens + // response.Usage.TotalTokens = chatCompletion.Usage.TotalTokens + //} + + return response, nil +} + +// Conversation 表示一个对话会话 +type Conversation struct { + Messages []ChatMessage `json:"messages"` // 对话历史 + Model string `json:"model"` // 使用的模型 + Client *BaichuanClient `json:"-"` // 客户端实例 +} + +// NewConversation 创建新的对话会话 +func (bc *BaichuanClient) NewConversation(model string, systemPrompt string) *Conversation { + messages := []ChatMessage{} + if systemPrompt != "" { + messages = append(messages, ChatMessage{Role: "system", Content: systemPrompt}) + } + + return &Conversation{ + Messages: messages, + Model: model, + Client: bc, + } +} + +// AddMessage 添加消息到对话历史 +func (c *Conversation) AddMessage(role, content string) { + c.Messages = append(c.Messages, ChatMessage{ + Role: role, + Content: content, + }) +} + +// GetResponse 获取AI对最新消息的回复 +func (c *Conversation) GetResponse(ctx context.Context, maxToken int) (string, error) { + params := ChatCompletionParams{ + Model: c.Model, + Messages: c.Messages, + MaxToken: maxToken, + } + + response, err := c.Client.ChatCompletion(ctx, params) + if err != nil { + return "", err + } + + // 将AI回复添加到对话历史 + c.AddMessage("assistant", response.Content) + + return response.Content, nil +} + +// ClearHistory 清除对话历史,但保留系统提示 +func (c *Conversation) ClearHistory() { + for i := len(c.Messages) - 1; i >= 0; i-- { + if c.Messages[i].Role == "system" { + c.Messages = c.Messages[:i+1] + return + } + } + c.Messages = []ChatMessage{} +} 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..5947c2e --- /dev/null +++ b/pkg/logging/logger.go @@ -0,0 +1,135 @@ +package logging + +import ( + "context" + "fmt" + + "go.uber.org/zap" +) + +const ( + TagKeyMain = "main" + TagKeyRecovery = "recovery" + TagKeyRequest = "request" + TagKeyLogin = "login" + TagKeyLogout = "logout" + TagKeySystem = "system" + TagKeyOperate = "operate" +) + +type ( + ctxLoggerKey struct{} + ctxTraceIDKey struct{} + ctxUserIDKey struct{} + + ctxUserPlatformKey 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 NewPlatform(ctx context.Context, platform string) context.Context { + return context.WithValue(ctx, ctxUserPlatformKey{}, platform) +} + +func FromPlatform(ctx context.Context) string { + v := ctx.Value(ctxUserPlatformKey{}) + 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..bf21cd8 --- /dev/null +++ b/pkg/middleware/auth.go @@ -0,0 +1,40 @@ +package middleware + +import ( + "gitlab.guxuan.icu/jinshan_community/pkg/logging" + "gitlab.guxuan.icu/jinshan_community/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..015f6a0 --- /dev/null +++ b/pkg/middleware/casbin.go @@ -0,0 +1,46 @@ +package middleware + +import ( + "gitlab.guxuan.icu/jinshan_community/pkg/errors" + "gitlab.guxuan.icu/jinshan_community/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..0a9a520 --- /dev/null +++ b/pkg/middleware/copybody.go @@ -0,0 +1,66 @@ +package middleware + +import ( + "bytes" + "compress/gzip" + "io" + "net/http" + + "gitlab.guxuan.icu/jinshan_community/pkg/errors" + "gitlab.guxuan.icu/jinshan_community/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..c630973 --- /dev/null +++ b/pkg/middleware/logger.go @@ -0,0 +1,88 @@ +package middleware + +import ( + "fmt" + "mime" + "net/http" + "time" + + "gitlab.guxuan.icu/jinshan_community/pkg/logging" + "gitlab.guxuan.icu/jinshan_community/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..0a184eb --- /dev/null +++ b/pkg/middleware/ratelimiter.go @@ -0,0 +1,144 @@ +package middleware + +import ( + "context" + "time" + + "gitlab.guxuan.icu/jinshan_community/pkg/errors" + "gitlab.guxuan.icu/jinshan_community/pkg/logging" + "gitlab.guxuan.icu/jinshan_community/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..c7ad53c --- /dev/null +++ b/pkg/middleware/recover.go @@ -0,0 +1,59 @@ +package middleware + +import ( + "fmt" + "net/http/httputil" + "strings" + "time" + + "gitlab.guxuan.icu/jinshan_community/pkg/errors" + "gitlab.guxuan.icu/jinshan_community/pkg/logging" + "gitlab.guxuan.icu/jinshan_community/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..ad8ca29 --- /dev/null +++ b/pkg/middleware/trace.go @@ -0,0 +1,48 @@ +package middleware + +import ( + "fmt" + "strings" + + "gitlab.guxuan.icu/jinshan_community/pkg/logging" + "gitlab.guxuan.icu/jinshan_community/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/baseModel.go b/pkg/util/baseModel.go new file mode 100644 index 0000000..41a8977 --- /dev/null +++ b/pkg/util/baseModel.go @@ -0,0 +1,22 @@ +package util + +import ( + "gorm.io/plugin/soft_delete" + "time" +) + +type BaseModel struct { + ID string `json:"id" gorm:"type:char(36);primaryKey;default:''"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` + DeletedAt soft_delete.DeletedAt `json:"deletedAt" gorm:"index" ` + CreatedID string `json:"createdId" gorm:"column:created_id;type:char(36);index;comment:创建人ID"` + DeletedID string `json:"deletedId" gorm:"column:deleted_id;type:char(36);index;comment:删除人ID"` +} + +type BaseModelWithoutDeletedAt struct { + ID string `json:"id" gorm:"type:char(36);primaryKey;default:''"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` + CreatedID string `json:"createdId" gorm:"column:created_id;type:char(36);index;comment:创建人ID"` +} diff --git a/pkg/util/command.go b/pkg/util/command.go new file mode 100644 index 0000000..b2fce09 --- /dev/null +++ b/pkg/util/command.go @@ -0,0 +1,45 @@ +package util + +import ( + "context" + "os" + "os/signal" + "syscall" + "time" + + "gitlab.guxuan.icu/jinshan_community/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..105fddd --- /dev/null +++ b/pkg/util/context.go @@ -0,0 +1,115 @@ +package util + +import ( + "context" + + "gitlab.guxuan.icu/jinshan_community/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..bbbea63 --- /dev/null +++ b/pkg/util/gin.go @@ -0,0 +1,136 @@ +package util + +import ( + "fmt" + "net/http" + "reflect" + "strings" + + "gitlab.guxuan.icu/jinshan_community/pkg/encoding/json" + "gitlab.guxuan.icu/jinshan_community/pkg/errors" + "gitlab.guxuan.icu/jinshan_community/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..0a015ea --- /dev/null +++ b/pkg/util/schema.go @@ -0,0 +1,61 @@ +package util + +import "gitlab.guxuan.icu/jinshan_community/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/qqgroup.jpg b/qqgroup.jpg new file mode 100644 index 0000000000000000000000000000000000000000..ab55a64bae9b6bc1a2499648cd445eb953b5c242 GIT binary patch literal 106873 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∋X3czU) z3~);G2G0YA#QrE9V_{$#Fg%7Zt;TX9gGt#D0(5cs{qKBt*3LnXu8-*II{P_itX=!9 zwGaIKOMidky8BOl!ReZvGlh%}tN?&3bVABailPzV&;z4EAk1=Y?nd zyJtUd(cIpz{L?qTam(%B`|_~gJ2kEmw#4UX*? z>-h35x8M7~x`Dw3hxK&)!l|b_@#Vk19RrVUT4U4TsH2}wjSNQwkx36Wnkh{pW0s2) zXq|lTz4vllm1LwteoS?IK&#te22y6+X`0C;g&4d{^}a#t*VR=z{$X-E=9rZhN#)eb3KdKxX?o-LKPP~X-cq+g_7G^SlIUimzL@<&t!@J(~E}vEv zZ&;_A%)n$O%yrjYXHSdD^x_lt51gse)Ru=HiR$ju4iE#-ES~_G&ws{g5^2d%WD>nV z>Zq&+7=8?X86evD)w2-l?|tukeX>+9U33grfxP+Vn_u>_mnn;L05--LzC8-G5$;}& zLc(fob&KenKxSa53~G#uRhvT?KjaE1!1evrkw!@4-zQw%&f* zUANu#lNBqTcG&Xe3+MLk+Sz9cH3PfL-Rv~Q(Dn@j8+Pv6v~0zqDYn1I>plUp#-sa!-$kO8|Fv>=+*1;ipOb&ce|6nzb9B zw*2s=%a*+8NcSm;*&+=x_u7Q#N@aw<+TW|aA_gsGY)-4W8j4xh%$daW?+z`?s zjWI6%nUy{XYXk4^O~w1Tbe~9YISpo@1c* za+Pl|aUlX?&znh31R8+Ewl9i!|B2T@=z+pzmt97{$_)QE`nyE& zQDF|IFf##+If=8%aleGHp_vO3_OZDmBp>>zdgTW^#4{Lkscvh~$#_$qzC$ za#6IZ+2sqZ)Q7q<%)ua6PDoQ30N4V|=hCVG09VHzd+gp6>U1GzLX>GUV-%N~Lav}5 z+RBU?BSIwNrtkyMiWzJq=S&9pFaut~gB%!mv^K0=d-Z4j%M~B}v-uN4hjmY!yZV{O z9@YD!JJ)R7yz9};16w!!{Gqi!v#~a2E80gqth&3$#s{|_xnl7Po^j06m!A5|zj(Tn z{!3s6;b{ikbxe#bp)fBNv+M>ln?dB`^~apjgnAb5|$Tlzgq z=5)X0sJSORZT|C5KJBEHN6qb@nCl~yojrf{(f|MUJMP-DW&2S_9_4=6tZ4RgGc-On z+Fef8l}BYnV`w6fm+M!Zxce|&lNSI;-VaQH!qspUo&eqh0)44SN3$FNE9Cn}>WOw{PEO)HAvH&_K|Fr@(@6Z1PQDm?30hR?vh)MgeA&QO~dVgPJT8 zVhcf=f}E8B0A?DakGN%hjH#Dvf>v?~gXM+-5;{f$6_4eH!(^HOu)8UWj4-frg*lkQ z%mgq6;f(zB6_elzyKJLLa!F>b#va0`kF;`1-$U;c%;8~bjLZ*I`$RMI%z|{ zck2f?ZQj0a;|8z#kGWrVgSoJ`XYTC759wLl({==1;=aAEV{X2I!2|7HqYe8?%Qa|w2+p}`Tp-*4BXsK5e z26nmU?3mkg!`E;AZ-4ityf~5j3O`>msiN`8-;TjEZO{x=&~0oACORQFooJ%uYHpl0 zsw&UMnZN=Sn6fTTiP*=oVOCT&GQ51rvZ1Y;{XX=diFnt55OsdFv`pOtvm;kwP$YPZ z9BHVKLxO<-vsAUaN*<+`%G}d}$C%4WxeR>Gj*J{XfCQG^*}G!>Inv@s`0D+2tmB47`Mv#S}fHqPQGBO_J;u+ctvFeD;c zqEU`S{4(~|q!&m$RJwE`LB*?34D)h*2`GFA)9=fVYxJWpHE+i}d21zodz;TgPbs^(F!&vA^F39xHU*F0x9ckLYByrFZ>f`9zN z_3!`lPxX~orpiLSrA^F~*^vVK36GdU>)KgEj zDZ;=mp>pKN^H{$fw!MGwo$q<)9sl~B|Ma%kKkvDx&7I#duz9F=VUNYbtgh~kU86jG zuE*Nr!`!-S$n*2=S$=POcBh|B*sy8ygFj!pVbh}vdlw3i!;d`54CelN>AW&_?Cjgz zyTBJteB)}@=+J0yPp@BXnQO5+r^j?XIMClUI<#oT5kLOk4=%psH^;gcIbyiW{jv`X zH4kpAMjw3efgkeG#q9U5{+njC- zDTLFTb2Ci4Ujei7m}6e_;um|W(_dbn7#ipw_#c1!-?#yVTSC9;S6=nV`bTfIqZ(2 zVFgZZpb1WIT-|ijO*q%&;fVFWCh^SiNP&|pue`G2^m1O7(Cq6T7?Tm9?K2hlKng^i zktDR}=r8~BFEPPRv?Ff)0u_gbpKRW-vba^5I}HI=6VDKby3++)CQakYpjDIk(Ajrj za8hx?30+U#IS?dmJX81{`xhbP5Y+{r3fN{C10?0zFvO=yDL9FsvsN*j7st{ow_#fK zt$)_}Ad&D}p}>+zP1uSP+II3YLf8&<2RRH_W{&R8`ICQLUZb7;{L`QPkq>|P{(J8q z85kZJ7#tlODt}Ix_-m-U`77@<50;sqqAq;n8@hYv_AXkuV9CNIhb=wa&FiXT&V2EU z-*x#rzxk~jy%n=-aOdRTz>a~w9li?UZ&!agG0KelUhaKo03+js4_ckP;^Rz2O1pO){>cFg{jUwyTE@*RCUt$0T##`}C3e{@vt zZ5@A04_$6>=;|qFrv;BhAq*$aB`qnlV-iIv*gN!sn-Sbs{^;}4i+_{S=5>{scH#U5 z|9ac)#*VFt6m9A*sJjsEb{eqmOR2VE`KdlAxU`PXBirxjkO$ zqaG4yXKV&0^BxnyB=$gTj{y5wNyQb?Fh)BuwiOG*AR(^+wLqbgrWDjudTr%BE@4wp z2_{X3n2a*aUxC|&X|(FiSQ|zeLVPO7IcO}Z(jc9B1p{+>!O15wsOeO`20fK#&z^Jl zJ!^jd5C3rf!i5$iLAZM!%y3$y^mKK5DbYc4VKOv6uyte$`1KF1z5TZDf9Z?=U>z|u zSj-VN6iT^c_SpF7oQZNibhNW$Vb9$0E+2UJY|FRYXO+Vr)ZS4}r#Rf=S#b34eu-tR z8r&FDm%+rYw0qQfV@0L)Fi%gTYMXdDS*5Sm z*fHOSdjy13-p4F^gGNWRWd4&fT#C%-6VGi5=&GlA^>Xs+B5%X&PuZ+1mUI+LY=@ob zGdBXkP)3l!vRx-tc}cWC`1c3qaf0nnhbIfeaj{Nc3VZ8Io+l*|Wm$p9rM=-yeKBT} z_6=5B!PfHr7ithr>(aK_w5BI@3BMrW#QW)FJPEw9Npj<=?9CAO#a2e!x0Ofkz6iTJ zDsvsK7y{dJccDpTj+I85-yLmgX=$>}7EyNz)ZJ4wDfg&p_tt~25j^we_ljOVAeiGn zh4=CWgdzwjP?x4fuO?m815nT^=bZA&mDXDRzr65N1uzvvkK9h81sab|Q@=;Oi1R(x zt2EQrrr@D;$;>R6>y()NiF%nXNY}mw%UU)%LM1#h(<-N_4DHPw?><`djeDjv_Mn}5q zYtj<3ZI-YKQ@f^JMnWpnX*n`gP8~T)=2O__UPFed(gv_fck}_oxwUNJOuV^KZRQhWM`B_ zuQEr9HmXi41N$gs`lU%X%rL;lK;W&YYcI}k3oUkO5AGIdI?VTb{(#|>Z-VDP*DfJq&SvysKcfa7qLDmTS24c zjYSMls8?J3(loWU6$b+{T$5Sjqvk_*s$*BdW}C<0LS7OPysd{! z4g~R4p!~3P6a}Uxtwa-2{)2z@>eW8N3XhvF-5pbT=%ID0!Gy|>|?R+A+gl9k+-r%pq&n_-HuGUKN=rD z^^{Y-aP8mP(uKw^&>eq#Int!Oc*!9Su6^i39}>y9avvT-a`t5-?^X%@u z6q~8F$-ZMP)1l;29?hhQ_*Bh9>_{^i4cmBo^PArsTiYw~Cc|ew^BL}kJ-K(GvWeyL zna_Nt_v$<_bC1)OH-swbCslZ=0ys?AhHWb)vnYqw+Ez+{r)f$hIobH0d+u?1rVXzw zLd_rqj?Cy1-EVY>s#gQL=fJ7+wD3sAtb4hC$?Yx?_X^@Nt?^q^zgcMfF2R^^v-j>(k!V1oBnE>o#={2`!?(?7byk9^6{4yK6 zoHx&=V#VpC&CR#m>@1p^D7H4yEXsI*q(A|_8eknO(8^B0t# zU*g9A6wI-l9%m_#UaX$iA(v!VxpxIUx$(Qw(%C^?L3Rpb`4tC z^WB5N5%(a@ewO`+xxM8W6PAW{4GQ%(d}jO95Rf@=Vz#u&L?Xg6qC)MWmA2*TJ)<(K zTY{5lzyqcJ5{igcUvT zy^5z@;3}EG<(QOgUzL7y@-BHs%bX0|*BPIbHRJA^SA7k)E_ZXTe1#%<;mPtlVkF}!$g|dMs4Vu8iAEnD( z1`@d>l`NF_Q2?wj3!#N>j4cNcKHG$*mBVvTL5_I<`bG-cP`2X8c!We9_<%9FJOTzS zUw zdo*-zyF;Y{>nS9a%jQ%TZN%=WXB1-tpKMu z9GZK%&Bg`X(5(!Yo?k$~XiN#&nlr3>eH9y5NNN358G=KgzHHg8^9l11F#W`?`A~1|$&yWPOF2t9^iF$dG z)YD@su)cNaPTtbp2?SHIJ6Ml=oPq!;6+%tqC9n%~&5C_nbK=)(zau{?K2}uMJajE| zO)ERMl>@C-+5$|$mXcH1`g*s~qZ1OE+Ad*P?$8m#Bwe>|axC7ub&CN5Q3DveYw{48Q;dv`n+$a%7bJXx<5^%w z;e3xKi@eQnNmmkCv{J03Z-NaBC9998(WH|qG#_6zC1B4DFr#ad_&hu+dAq*rrhW=f zISV?d03cDEpgDmb%>y{dG@I}Ti6Y%G$9Hi_h6He2JUQ8@&!c%a^aNJv-6OY}RG=gK zz58A7zW#>mDFxUmr=0TXPk&mjOsC?3T4Any5*`xD1>oqFR`|uh#TQ@vCx7xMjO3CZ zP&X@vAFk|}hudibhstx$J=Yf`!~mAW%TK&5DjFcpj%2qmqCea+Xf-xH2crpk2F8^1ix=t-FV}TZ+g?4 zrY7AIzzN;BRO-pA9z}WT?W6b1b&AnzUTz%92x}M6VX6Ls!9$lHs(j)3=a*N7O>3j0 zwrYOovbR6FxxA)tWV=lQpru}32NnDT0Z$NzL)sg+epu_MqmBXqp;p*J z`e1UXNR4gm&CCoOjCg{PKhXsO;ztdR1sK^f-MN(FTgF$wv{0g`quiOWBENUj520T4 zCzms6l#-8#%41Z zGSYzwd^kxCp2+GE#V}S0_l~^&jyT- zm6zp; zGi73fX?PLq<1n#p6rgH6FbcIR=&mo$UFxpB;@4_QFd492mt%uE1R@?_v! zWT1UXJ-+4YU8R;ipPQp!znR(^X8EwN^qi=V?*9OhHF9_>;NFgaQzDt_#9B29wDyCR;3 zW(F3JFrdaF^o(aQ6S+7#PmpMsCV>eXU)6zd@ga+CQ{xbol6;M%QI1c6ra}&<+@UZX z6$%f{G8ms!FCU)gbqjJ(_ z!Oori9lQDlJGy6&I}_0}4l}nj9(xJHmQwkw&*Kv< zlLr+Z>;zXn$76)R9+8QFe|bDaSCz~W<=|3l3Bp;k4_R`EyKCPMK(S2$%;PT$19T{- z1Pd_$BGy=-5WQ8+r|&cj$Qf%GNP!XQEg0l+FdU{8+vX31FlqjeznPZR*slpKWl1>} zhp}_`8C=}JrWJ6KCdR(^-g|w& zf`3<>l)@@{2`xD!NFe0hhA;f>7x)U5#YNNu=IcT$<`3q&Xe^g5rlvl?e8@epyJd%d zSo79fZ-w8eTrAbRFYZ#(ukxd?H+$$J8s*+_LIvA$z!lylF=0cujc|X;&;rvJ2{#Cz z{p@FH!;J~{6tqGvnQ~up)=M(dg>-U#O^>IYcB-$#Dm!Luv3=tWH~N-cFt>T&!Dqbi z7mq&X=xtlK*|@SRX3X_-HQToD9O3Qey|MEC9cn2|Mzw>~<72OV?Rg&mDDZ)Q`{~`b z1G3s@Bb-YZMyt(R)eq&^FcbWK4(i-<&ug%xZ zbybD5A>sLcj3+0RTYQnms3ks{#NCZs*Ijp= =WeU-VROEGDQqAv_I?%dV)zV|(9 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 M07*qoM6N<$f^_9mi2wiq literal 0 HcmV?d00001 diff --git a/scripts/restart.sh b/scripts/restart.sh new file mode 100644 index 0000000..0371195 --- /dev/null +++ b/scripts/restart.sh @@ -0,0 +1,2 @@ +bash ./stop.sh +bash ./start.sh \ No newline at end of file diff --git a/scripts/start.sh b/scripts/start.sh new file mode 100644 index 0000000..354d72d --- /dev/null +++ b/scripts/start.sh @@ -0,0 +1 @@ +./ginadmin start -d configs -c dev -s dist --daemon \ No newline at end of file diff --git a/scripts/stop.sh b/scripts/stop.sh new file mode 100644 index 0000000..ff1a008 --- /dev/null +++ b/scripts/stop.sh @@ -0,0 +1 @@ +./ginadmin stop \ No newline at end of file diff --git a/swagger.png b/swagger.png new file mode 100644 index 0000000000000000000000000000000000000000..b136f8399e30978d230112c70f782151b9f94b85 GIT binary patch literal 326795 zcmeFZc|4SD`#&xeC0i<6QmIt3CHq>6BwMmCql7GD?E4TEm94U7uPoWa*qNb1$dbV@ zgR#We*I}43Grvpst?tkB+|T_i_dmZszP(=an$y*FUe|dZ`|&=GbDrsHt1}n=R+HD#dmQfm-eO8S7fGd*nR(ok^4iwp|s_JT}s-DnwgV@?T+0fA3covtya7TaU z&_k*}l2_Q{{F_jbRGQmYSx=ncb9#JQ<(xP@?bCr5*Ut+*f6f?x#Nvpqi}1sOBTPq+ z?%6xfbc(jE#+dC$oOiJ5z_9m-KdwX89VhROOAne-CUm`~QPHzzdeZvgAkFy@54{hS zyuz0*B!%+syRWk68V%i{Xlb z6QA6yab@%1TYlNAdU7=HC!T@?B`jn*_!!&6U(m7#ig&D(ZQCbV+i_{7J=@RhrJlHt zCheV(YvzAbZB<2h$L%5YSj$TNY4L3j!|7vdJqoH*#;^;< zrI3^_sj@9j-vXG&GsT`M2e-WX-lfeLnK*GU(C1-hMJ5|!i_7~N=rl__{v(G^ZO78w z8QHK{7y{pEr_rA+_?UR3GguL%tkaOv_4?@f+{pH|<&^0CF`t=`!>9fC$2>Z{Qg8{Y2yzFehneO}<8s}2}lt2B2 zw)LsXNOkM|GPY+gYr1PX(qu0!&gZO98o#DKKD@G~G{#1=5zdC2^2I*qq2YO9nb|u9 z^~f}QeTSy#sLSWK5hnb8#NaD|?3uv|x_fw37`SNkGu6yS8O?(Qm-hyrID9Sm$V0kA zDhJqtZ@r+C3JK7quL!otpgl$t)*$Xen?f5os!&Di{7^oF&f`%_2J68Pl-0oi`WQC) zu@eWbJ-Ks2>jvwk(2FPceLv`Xm7*(lcyE)+$_tu9s*mNy~CY%z#z;)t^Doc8=~&J_gxPz zCOyx7qxCK70lx=V(j)Eb#j2d|gt}Y0nWr76{ig%*TtxZM5WP=a*2lyy-a38uc+9ib zme{*dR-N~XtZT#{B}N-xG-TIoLEnXb&>Ofm&*LQS#b0sUpBwXxuOVu}MdnC~c;|l3 zr`8iau3J@#RTEX3RU$(t7CF)$qduFB*Drtb*`ve>3N3B~efAy;TaKaU4msW!%ywh+ zkRiuS#<2Yzk82-a`FL6{{)V6&yXv!NpCdoJ-Q!!4TiL&&`0QHG*}<6j7n_;PnQlEA zJzBSwXCzFboUVBZiVB3cnzgF8CbY7)E}cL8T&XGUkd{V{Q`Q~*qT6YDN*a1uUo(ww zo9C|Ij=Rl#+u}APbAOLt&#jpwJ%c?qJvmuRH>2Cw?&EESYz8jP-M?Vd*@|zAXuoYE ze4oxn|9-fQ<9$dgOZ(V&{G`HUm7A|y*!Q9~W?j8Ie0lP5s>9b5Z`Et_#kRB|CrwtL ztzKUh{EnGyIsE-_!I6(gN)KN+B61FY{_WxQBPP#VpPzZ2bJ+hd;#{tfc%0A8y*E8? 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 literal 0 HcmV?d00001 diff --git a/test/menu_test.go b/test/menu_test.go new file mode 100644 index 0000000..4859b0f --- /dev/null +++ b/test/menu_test.go @@ -0,0 +1,58 @@ +package test + +import ( + "net/http" + "testing" + + "gitlab.guxuan.icu/jinshan_community/internal/mods/rbac/schema" + "gitlab.guxuan.icu/jinshan_community/pkg/util" + "github.com/stretchr/testify/assert" +) + +func TestMenu(t *testing.T) { + e := tester(t) + + menuFormItem := schema.MenuForm{ + Code: "menu", + Name: "Menu management", + Description: "Menu management", + Sequence: 9, + Type: "page", + Path: "/system/menu", + Properties: `{"icon":"menu"}`, + Status: schema.MenuStatusEnabled, + } + + var menu schema.Menu + e.POST(baseAPI + "/menus").WithJSON(menuFormItem). + Expect().Status(http.StatusOK).JSON().Decode(&util.ResponseResult{Data: &menu}) + + assert := assert.New(t) + assert.NotEmpty(menu.ID) + assert.Equal(menuFormItem.Code, menu.Code) + assert.Equal(menuFormItem.Name, menu.Name) + assert.Equal(menuFormItem.Description, menu.Description) + assert.Equal(menuFormItem.Sequence, menu.Sequence) + assert.Equal(menuFormItem.Type, menu.Type) + assert.Equal(menuFormItem.Path, menu.Path) + assert.Equal(menuFormItem.Properties, menu.Properties) + assert.Equal(menuFormItem.Status, menu.Status) + + var menus schema.Menus + e.GET(baseAPI + "/menus").Expect().Status(http.StatusOK).JSON().Decode(&util.ResponseResult{Data: &menus}) + assert.GreaterOrEqual(len(menus), 1) + + newName := "Menu management 1" + newStatus := schema.MenuStatusDisabled + menu.Name = newName + menu.Status = newStatus + e.PUT(baseAPI + "/menus/" + menu.ID).WithJSON(menu).Expect().Status(http.StatusOK) + + var getMenu schema.Menu + e.GET(baseAPI + "/menus/" + menu.ID).Expect().Status(http.StatusOK).JSON().Decode(&util.ResponseResult{Data: &getMenu}) + assert.Equal(newName, getMenu.Name) + assert.Equal(newStatus, getMenu.Status) + + e.DELETE(baseAPI + "/menus/" + menu.ID).Expect().Status(http.StatusOK) + e.GET(baseAPI + "/menus/" + menu.ID).Expect().Status(http.StatusNotFound) +} diff --git a/test/role_test.go b/test/role_test.go new file mode 100644 index 0000000..2059794 --- /dev/null +++ b/test/role_test.go @@ -0,0 +1,82 @@ +package test + +import ( + "net/http" + "testing" + + "gitlab.guxuan.icu/jinshan_community/internal/mods/rbac/schema" + "gitlab.guxuan.icu/jinshan_community/pkg/util" + "github.com/stretchr/testify/assert" +) + +func TestRole(t *testing.T) { + e := tester(t) + + menuFormItem := schema.MenuForm{ + Code: "role", + Name: "Role management", + Description: "Role management", + Sequence: 8, + Type: "page", + Path: "/system/role", + Properties: `{"icon":"role"}`, + Status: schema.MenuStatusEnabled, + } + + var menu schema.Menu + e.POST(baseAPI + "/menus").WithJSON(menuFormItem). + Expect().Status(http.StatusOK).JSON().Decode(&util.ResponseResult{Data: &menu}) + + assert := assert.New(t) + assert.NotEmpty(menu.ID) + assert.Equal(menuFormItem.Code, menu.Code) + assert.Equal(menuFormItem.Name, menu.Name) + assert.Equal(menuFormItem.Description, menu.Description) + assert.Equal(menuFormItem.Sequence, menu.Sequence) + assert.Equal(menuFormItem.Type, menu.Type) + assert.Equal(menuFormItem.Path, menu.Path) + assert.Equal(menuFormItem.Properties, menu.Properties) + assert.Equal(menuFormItem.Status, menu.Status) + + roleFormItem := schema.RoleForm{ + Code: "admin", + Name: "Administrator", + Menus: schema.RoleMenus{ + {MenuID: menu.ID}, + }, + Description: "Administrator", + Sequence: 9, + Status: schema.RoleStatusEnabled, + } + + var role schema.Role + e.POST(baseAPI + "/roles").WithJSON(roleFormItem).Expect().Status(http.StatusOK).JSON().Decode(&util.ResponseResult{Data: &role}) + assert.NotEmpty(role.ID) + assert.Equal(roleFormItem.Code, role.Code) + assert.Equal(roleFormItem.Name, role.Name) + assert.Equal(roleFormItem.Description, role.Description) + assert.Equal(roleFormItem.Sequence, role.Sequence) + assert.Equal(roleFormItem.Status, role.Status) + assert.Equal(len(roleFormItem.Menus), len(role.Menus)) + + var roles schema.Roles + e.GET(baseAPI + "/roles").Expect().Status(http.StatusOK).JSON().Decode(&util.ResponseResult{Data: &roles}) + assert.GreaterOrEqual(len(roles), 1) + + newName := "Administrator 1" + newStatus := schema.RoleStatusDisabled + role.Name = newName + role.Status = newStatus + e.PUT(baseAPI + "/roles/" + role.ID).WithJSON(role).Expect().Status(http.StatusOK) + + var getRole schema.Role + e.GET(baseAPI + "/roles/" + role.ID).Expect().Status(http.StatusOK).JSON().Decode(&util.ResponseResult{Data: &getRole}) + assert.Equal(newName, getRole.Name) + assert.Equal(newStatus, getRole.Status) + + e.DELETE(baseAPI + "/roles/" + role.ID).Expect().Status(http.StatusOK) + e.GET(baseAPI + "/roles/" + role.ID).Expect().Status(http.StatusNotFound) + + e.DELETE(baseAPI + "/menus/" + menu.ID).Expect().Status(http.StatusOK) + e.GET(baseAPI + "/menus/" + menu.ID).Expect().Status(http.StatusNotFound) +} diff --git a/test/test.go b/test/test.go new file mode 100644 index 0000000..c7f7077 --- /dev/null +++ b/test/test.go @@ -0,0 +1,55 @@ +package test + +import ( + "context" + "net/http" + "os" + "testing" + + "gitlab.guxuan.icu/jinshan_community/internal/config" + "gitlab.guxuan.icu/jinshan_community/internal/wirex" + "github.com/gavv/httpexpect/v2" + "github.com/gin-gonic/gin" +) + +const ( + baseAPI = "/api/v1" +) + +var ( + app *gin.Engine +) + +func init() { + config.MustLoad("") + + _ = os.RemoveAll(config.C.Storage.DB.DSN) + ctx := context.Background() + injector, _, err := wirex.BuildInjector(ctx) + if err != nil { + panic(err) + } + + if err := injector.M.Init(ctx); err != nil { + panic(err) + } + + app = gin.New() + err = injector.M.RegisterRouters(ctx, app) + if err != nil { + panic(err) + } +} + +func tester(t *testing.T) *httpexpect.Expect { + return httpexpect.WithConfig(httpexpect.Config{ + Client: &http.Client{ + Transport: httpexpect.NewBinder(app), + Jar: httpexpect.NewCookieJar(), + }, + Reporter: httpexpect.NewAssertReporter(t), + Printers: []httpexpect.Printer{ + httpexpect.NewDebugPrinter(t, true), + }, + }) +} diff --git a/test/user_test.go b/test/user_test.go new file mode 100644 index 0000000..a3970dc --- /dev/null +++ b/test/user_test.go @@ -0,0 +1,108 @@ +package test + +import ( + "net/http" + "testing" + + "gitlab.guxuan.icu/jinshan_community/internal/mods/rbac/schema" + "gitlab.guxuan.icu/jinshan_community/pkg/crypto/hash" + "gitlab.guxuan.icu/jinshan_community/pkg/util" + "github.com/stretchr/testify/assert" +) + +func TestUser(t *testing.T) { + e := tester(t) + + menuFormItem := schema.MenuForm{ + Code: "user", + Name: "User management", + Description: "User management", + Sequence: 7, + Type: "page", + Path: "/system/user", + Properties: `{"icon":"user"}`, + Status: schema.MenuStatusEnabled, + } + + var menu schema.Menu + e.POST(baseAPI + "/menus").WithJSON(menuFormItem). + Expect().Status(http.StatusOK).JSON().Decode(&util.ResponseResult{Data: &menu}) + + assert := assert.New(t) + assert.NotEmpty(menu.ID) + assert.Equal(menuFormItem.Code, menu.Code) + assert.Equal(menuFormItem.Name, menu.Name) + assert.Equal(menuFormItem.Description, menu.Description) + assert.Equal(menuFormItem.Sequence, menu.Sequence) + assert.Equal(menuFormItem.Type, menu.Type) + assert.Equal(menuFormItem.Path, menu.Path) + assert.Equal(menuFormItem.Properties, menu.Properties) + assert.Equal(menuFormItem.Status, menu.Status) + + roleFormItem := schema.RoleForm{ + Code: "user", + Name: "Normal", + Menus: schema.RoleMenus{ + {MenuID: menu.ID}, + }, + Description: "Normal", + Sequence: 8, + Status: schema.RoleStatusEnabled, + } + + var role schema.Role + e.POST(baseAPI + "/roles").WithJSON(roleFormItem).Expect().Status(http.StatusOK).JSON().Decode(&util.ResponseResult{Data: &role}) + assert.NotEmpty(role.ID) + assert.Equal(roleFormItem.Code, role.Code) + assert.Equal(roleFormItem.Name, role.Name) + assert.Equal(roleFormItem.Description, role.Description) + assert.Equal(roleFormItem.Sequence, role.Sequence) + assert.Equal(roleFormItem.Status, role.Status) + assert.Equal(len(roleFormItem.Menus), len(role.Menus)) + + userFormItem := schema.UserForm{ + Username: "test", + Name: "Test", + Password: hash.MD5String("test"), + Phone: "0720", + Email: "test@gmail.com", + Remark: "test user", + Status: schema.UserStatusActivated, + Roles: schema.UserRoles{{RoleID: role.ID}}, + } + + var user schema.User + e.POST(baseAPI + "/users").WithJSON(userFormItem).Expect().Status(http.StatusOK).JSON().Decode(&util.ResponseResult{Data: &user}) + assert.NotEmpty(user.ID) + assert.Equal(userFormItem.Username, user.Username) + assert.Equal(userFormItem.Name, user.Name) + assert.Equal(userFormItem.Phone, user.Phone) + assert.Equal(userFormItem.Email, user.Email) + assert.Equal(userFormItem.Remark, user.Remark) + assert.Equal(userFormItem.Status, user.Status) + assert.Equal(len(userFormItem.Roles), len(user.Roles)) + + var users schema.Users + e.GET(baseAPI+"/users").WithQuery("username", userFormItem.Username).Expect().Status(http.StatusOK).JSON().Decode(&util.ResponseResult{Data: &users}) + assert.GreaterOrEqual(len(users), 1) + + newName := "Test 1" + newStatus := schema.UserStatusFreezed + user.Name = newName + user.Status = newStatus + e.PUT(baseAPI + "/users/" + user.ID).WithJSON(user).Expect().Status(http.StatusOK) + + var getUser schema.User + e.GET(baseAPI + "/users/" + user.ID).Expect().Status(http.StatusOK).JSON().Decode(&util.ResponseResult{Data: &getUser}) + assert.Equal(newName, getUser.Name) + assert.Equal(newStatus, getUser.Status) + + e.DELETE(baseAPI + "/users/" + user.ID).Expect().Status(http.StatusOK) + e.GET(baseAPI + "/users/" + user.ID).Expect().Status(http.StatusNotFound) + + e.DELETE(baseAPI + "/roles/" + role.ID).Expect().Status(http.StatusOK) + e.GET(baseAPI + "/roles/" + role.ID).Expect().Status(http.StatusNotFound) + + e.DELETE(baseAPI + "/menus/" + menu.ID).Expect().Status(http.StatusOK) + e.GET(baseAPI + "/menus/" + menu.ID).Expect().Status(http.StatusNotFound) +} diff --git a/wechat.jpg b/wechat.jpg new file mode 100644 index 0000000000000000000000000000000000000000..310bd75c52994674810b64171f5330077c31cdc5 GIT binary patch literal 14766 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 literal 0 HcmV?d00001