commit 67f1dbc8509dce7af91da263bc9cfbbb979a4f1b Author: aditya.siregar Date: Sun Oct 8 15:59:42 2023 +0700 init project diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..3642ece Binary files /dev/null and b/.DS_Store differ diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..b75eaa9 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,17 @@ +# Files +.dockerignore +.editorconfig +.gitignore +.env.* +Dockerfile +Makefile +LICENSE +**/*.md +**/*_test.go +*.out + +bin/ +# Folders +.git/ +.github/ +build/ diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..497deaa --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +.idea/* + +bin + +config/env/* +!.env + +vendor diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..80115ae --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,35 @@ +stages: + - build + - staging + +build_image: + stage: build + image: docker:19.03.12 + services: + - docker:19.03.12-dind + before_script: + - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY + script: + - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA . + - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA + only: + - main + +deploy_to_staging: + stage: staging + image: + name: bitnami/kubectl + entrypoint: [""] + script: + - echo "$KUBECONFIG_BASE64" | base64 -d > ./kubeconfig + - export KUBECONFIG=$(pwd)/kubeconfig + - sed -i "s//$CI_COMMIT_SHORT_SHA/" k8s/staging/deployment.yaml + - kubectl apply -f k8s/staging/namespace.yaml + - kubectl apply -f k8s/staging/deployment.yaml + - kubectl apply -f k8s/staging/service.yaml + - kubectl apply -f k8s/staging/ingress.yaml + only: + - main + +# tes bintang 4 + diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..5d11c74 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,24 @@ +FROM golang:1.20-alpine AS build + +RUN apk --no-cache add tzdata + +WORKDIR /src +COPY . . + +# RUN CGO_ENABLED=0 GOOS=linux go build -o /app cmd/klinik-core-service +RUN CGO_ENABLED=0 GOOS=linux go build -o /app main.go + +RUN ls -la / + +FROM gcr.io/distroless/static + +WORKDIR / + +COPY --from=build /usr/share/zoneinfo /usr/share/zoneinfo +COPY --from=build /app /app + +# RUN ls -la / + +ENV TZ=Asia/Jakarta + +ENTRYPOINT ["/app"] diff --git a/Dockerfile copy b/Dockerfile copy new file mode 100644 index 0000000..07629bb --- /dev/null +++ b/Dockerfile copy @@ -0,0 +1,24 @@ + FROM golang:1.20-alpine AS build + +RUN apk --no-cache add tzdata + +WORKDIR /src +COPY . . + +# RUN CGO_ENABLED=0 GOOS=linux go build -o /app cmd/klinik-core-service +RUN CGO_ENABLED=0 GOOS=linux go build -o /app main.go + +RUN ls -la / + +FROM gcr.io/distroless/static + +WORKDIR / + +COPY --from=build /usr/share/zoneinfo /usr/share/zoneinfo +COPY --from=build /app /app + +# RUN ls -la / + +ENV TZ=Asia/Jakarta + +ENTRYPOINT ["/app"] diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..5bd11c7 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Pavel Varentsov + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..327abb9 --- /dev/null +++ b/Makefile @@ -0,0 +1,115 @@ +PROJECT_NAME = "furtuna-backend" +DB_USERNAME := furtuna_admin +DB_PASSWORD := Z4G827t9428QFQ%5ESZXW%2343dB%25%214Bmh80 +DB_HOST := 103.96.146.124 +DB_PORT := 1960 +DB_NAME := furtuna-staging + +DB_URL = postgres://$(DB_USERNAME):$(DB_PASSWORD)@$(DB_HOST):$(DB_PORT)/$(DB_NAME)?sslmode=disable + +ifeq ($(OS),Windows_NT) + DETECTED_OS := Windows +else + DETECTED_OS := $(shell sh -c 'uname 2>/dev/null || echo Unknown') +endif + +.SILENT: help +help: + @echo + @echo "Usage: make [command]" + @echo + @echo "Commands:" + @echo " rename-project name={name} Rename project" + @echo + @echo " build-http Build http server" + @echo + @echo " migration-create name={name} Create migration" + @echo " migration-up Up migrations" + @echo " migration-down Down last migration" + @echo + @echo " docker-up Up docker services" + @echo " docker-down Down docker services" + @echo + @echo " fmt Format source code" + @echo " test Run unit tests" + @echo + +# Build + +.SILENT: rename-project +rename-project: + ifeq ($(name),) + @echo 'new project name not set' + else + ifeq ($(DETECTED_OS),Darwin) + @grep -RiIl '$(PROJECT_NAME)' | xargs sed -i '' 's/$(PROJECT_NAME)/$(name)/g' + endif + + ifeq ($(DETECTED_OS),Linux) + @grep -RiIl '$(PROJECT_NAME)' | xargs sed -i 's/$(PROJECT_NAME)/$(name)/g' + endif + + ifeq ($(DETECTED_OS),Windows) + @grep 'target is not implemented on Windows platform' + endif + endif + +.SILENT: build-http +build-http: + @go build -o ./bin/http-server ./cmd/http/main.go + @echo executable file \"http-server\" saved in ./bin/http-server + +# Test + +.SILENT: test +test: + @go test ./... -v + +# Create migration + +.SILENT: migration-create +migration-create: + @migrate create -ext sql -dir ./migrations -seq $(name) + +# Up migration + +.SILENT: migration-up +migration-up: + @migrate -database $(DB_URL) -path ./migrations up + +# Down migration + +.SILENT: migration-down +migration-down: + @migrate -database $(DB_URL) -path ./migrations down 1 + +.SILENT: seeder-create +seeder-create: + @migrate create -ext sql -dir ./seeders -seq $(name) + +.SILENT: seeder-up +seeder-up: + @migrate -database $(DB_URL) -path ./seeders up + +# Docker + +.SILENT: docker-up +docker-up: + @docker-compose up -d + +.SILENT: docker-down +docker-down: + @docker-compose down + +# Format + +.SILENT: fmt +fmt: + @go fmt ./... + +start: + go run main.go --env-path .env + +# Default + +.DEFAULT_GOAL := help \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..f81fd39 --- /dev/null +++ b/README.md @@ -0,0 +1,64 @@ +

+ Go
Backend Template +

+ +> Clean architecture based backend template in Go. + +## Makefile + +Makefile requires installed dependecies: +* [go](https://go.dev/doc/install) +* [docker-compose](https://docs.docker.com/compose/reference) +* [migrate](https://github.com/golang-migrate/migrate) + + +```shell +$ make + +Usage: make [command] + +Commands: + rename-project name={name} Rename project + + build-http Build http server + + migration-create name={name} Create migration + migration-up Up migrations + migration-down Down last migration + + docker-up Up docker services + docker-down Down docker services + + fmt Format source code + test Run unit tests + +``` + +## HTTP Server + +```shell +$ ./bin/http-server --help + +Usage: http-server + +Flags: + -h, --help Show mycontext-sensitive help. + --env-path=STRING Path to env config file +``` + +**Configuration** is based on the environment variables. See [.env.template](.env). + +```shell +# Expose env vars before and start server +$ ./bin/http-server + +# Expose env vars from the file and start server +$ ./bin/http-server --env-path ./config/env/.env +``` + +## API Docs +* [furtuna Backend](https://furtuna-be.app-dev.altru.id/docs/index.html#/) + +## License + +This project is licensed under the [MIT License](https://github.com/pvarentsov/furtuna-be/blob/main/LICENSE). diff --git a/config/configs.go b/config/configs.go new file mode 100644 index 0000000..aa06dc6 --- /dev/null +++ b/config/configs.go @@ -0,0 +1,68 @@ +package config + +import ( + "fmt" + "os" + "sync" + + "github.com/spf13/viper" + _ "gopkg.in/yaml.v3" +) + +const ( + YAML_PATH = "infra/furtuna.%s" + ENV_MODE = "ENV_MODE" + DEFAULT_ENV_MODE = "development" +) + +var ( + validEnvMode = map[string]struct{}{ + "local": {}, + "development": {}, + "production": {}, + } +) + +type Config struct { + Server Server `mapstructure:"server"` + Database Database `mapstructure:"postgresql"` + Jwt Jwt `mapstructure:"jwt"` + OSSConfig OSSConfig `mapstructure:"oss"` +} + +var ( + config *Config + configOnce sync.Once +) + +func LoadConfig() *Config { + envMode := os.Getenv(ENV_MODE) + if _, ok := validEnvMode[envMode]; !ok { + envMode = DEFAULT_ENV_MODE // default env mode + } + cfgFilePath := fmt.Sprintf(YAML_PATH, envMode) + + configOnce.Do(func() { + v := viper.New() + v.SetConfigType("yaml") + v.AddConfigPath(".") + v.SetConfigName(cfgFilePath) + if err := v.ReadInConfig(); err != nil { + panic(fmt.Errorf("failed to read config file: %s", err)) + } + + config = &Config{} + if err := v.Unmarshal(config); err != nil { + panic(fmt.Errorf("failed to unmarshal config: %s", err)) + } + }) + + return config +} + +func (c *Config) Auth() *AuthConfig { + return &AuthConfig{ + jwtTokenSecret: c.Jwt.Token.Secret, + jwtTokenExpiresTTL: c.Jwt.Token.ExpiresTTL, + } +} diff --git a/config/crypto.go b/config/crypto.go new file mode 100644 index 0000000..d3db86a --- /dev/null +++ b/config/crypto.go @@ -0,0 +1,17 @@ +package config + +import "time" + +type AuthConfig struct { + jwtTokenExpiresTTL int + jwtTokenSecret string +} + +func (c *AuthConfig) AccessTokenSecret() string { + return c.jwtTokenSecret +} + +func (c *AuthConfig) AccessTokenExpiresDate() time.Time { + duration := time.Duration(c.jwtTokenExpiresTTL) + return time.Now().UTC().Add(time.Minute * duration) +} diff --git a/config/db.go b/config/db.go new file mode 100644 index 0000000..30fa693 --- /dev/null +++ b/config/db.go @@ -0,0 +1,28 @@ +package config + +import ( + "fmt" + "time" +) + +type Database struct { + Host string `mapstructure:"host"` + Port string `mapstructure:"port"` + DB string `mapstructure:"db"` + Driver string `mapstructure:"driver"` + Username string `mapstructure:"username"` + Password string `mapstructure:"password"` + SslMode string `mapstructure:"ssl-mode"` + Debug bool `mapstructure:"debug"` + MaxIdleConnectionsInSecond int `mapstructure:"max-idle-connections-in-second"` + MaxOpenConnectionsInSecond int `mapstructure:"max-open-connections-in-second"` + ConnectionMaxLifetimeInSecond int64 `mapstructure:"connection-max-life-time-in-second"` +} + +func (c Database) DSN() string { + return fmt.Sprintf("host=%s port=%s dbname=%s user=%s password=%s sslmode=%s", c.Host, c.Port, c.DB, c.Username, c.Password, c.SslMode) +} + +func (c Database) ConnectionMaxLifetime() time.Duration { + return time.Duration(c.ConnectionMaxLifetimeInSecond) * time.Second +} diff --git a/config/http.go b/config/http.go new file mode 100644 index 0000000..21f9b4b --- /dev/null +++ b/config/http.go @@ -0,0 +1,17 @@ +package config + +import "fmt" + +type httpConfig struct { + host string + port int + detailedError bool +} + +func (c *httpConfig) Address() string { + return fmt.Sprintf("%s:%d", c.host, c.port) +} + +func (c *httpConfig) DetailedError() bool { + return c.detailedError +} diff --git a/config/jwt.go b/config/jwt.go new file mode 100644 index 0000000..aec29b2 --- /dev/null +++ b/config/jwt.go @@ -0,0 +1,10 @@ +package config + +type Jwt struct { + Token Token `mapstructure:"token"` +} + +type Token struct { + ExpiresTTL int `mapstructure:"expires-ttl"` + Secret string `mapstructure:"secret"` +} diff --git a/config/oss.go b/config/oss.go new file mode 100644 index 0000000..477ecb4 --- /dev/null +++ b/config/oss.go @@ -0,0 +1,39 @@ +package config + +type OSSConfig struct { + AccessKeyID string `mapstructure:"access_key_id"` + AccessKeySecret string `mapstructure:"access_key_secret"` + Endpoint string `mapstructure:"endpoint"` + BucketName string `mapstructure:"bucket_name"` + PhotoFolder string `mapstructure:"photo_folder"` + LogLevel string `mapstructure:"log_level"` + HostURL string `mapstructure:"host_url"` +} + +func (c OSSConfig) GetAccessKeyID() string { + return c.AccessKeyID +} + +func (c OSSConfig) GetAccessKeySecret() string { + return c.AccessKeySecret +} + +func (c OSSConfig) GetEndpoint() string { + return c.Endpoint +} + +func (c OSSConfig) GetBucketName() string { + return c.BucketName +} + +func (c OSSConfig) GetLogLevel() string { + return c.LogLevel +} + +func (c OSSConfig) GetHostURL() string { + return c.HostURL +} + +func (c OSSConfig) GetPhotoFolder() string { + return c.PhotoFolder +} diff --git a/config/server.go b/config/server.go new file mode 100644 index 0000000..10ee173 --- /dev/null +++ b/config/server.go @@ -0,0 +1,7 @@ +package config + +type Server struct { + Port string `mapstructure:"port"` + BaseUrl string `mapstructure:"common-url"` + LocalUrl string `mapstructure:"local-url"` +} diff --git a/config/tuya.go b/config/tuya.go new file mode 100644 index 0000000..d912156 --- /dev/null +++ b/config/tuya.go @@ -0,0 +1 @@ +package config diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..bc7bb00 --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,8 @@ +version: "3.3" +services: + app: + build: . + ports: + - "3300:3300" + volumes: + - ./:/app/ \ No newline at end of file diff --git a/docs/docs.go b/docs/docs.go new file mode 100644 index 0000000..8eea898 --- /dev/null +++ b/docs/docs.go @@ -0,0 +1,3101 @@ +// Package docs Code generated by swaggo/swag. DO NOT EDIT +package docs + +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/auth/login": { + "post": { + "description": "Authenticates a user based on the provided credentials and returns a JWT token.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Auth Login API's" + ], + "summary": "User login", + "parameters": [ + { + "description": "User login credentials", + "name": "bodyParam", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.LoginRequest" + } + } + ], + "responses": { + "200": { + "description": "Login successful", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.LoginResponse" + } + } + } + ] + } + }, + "400": { + "description": "Bad request", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + } + } + } + }, + "/api/v1/branch": { + "post": { + "description": "Create a new branch based on the provided data.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Branch APIs" + ], + "summary": "Create a new branch", + "parameters": [ + { + "type": "string", + "description": "JWT token", + "name": "Authorization", + "in": "header", + "required": true + }, + { + "description": "New branch details", + "name": "req", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.Branch" + } + } + ], + "responses": { + "200": { + "description": "Branch created successfully", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.Branch" + } + } + } + ] + } + }, + "400": { + "description": "Bad request", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + } + } + } + }, + "/api/v1/branch/list": { + "get": { + "description": "Get a paginated list of branches based on query parameters.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Branch APIs" + ], + "summary": "Get a list of branches", + "parameters": [ + { + "type": "string", + "description": "JWT token", + "name": "Authorization", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Number of items to retrieve (default 10)", + "name": "Limit", + "in": "query" + }, + { + "type": "integer", + "description": "Offset for pagination (default 0)", + "name": "Offset", + "in": "query" + } + ], + "responses": { + "200": { + "description": "List of branches", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.BranchList" + } + } + } + ] + } + }, + "400": { + "description": "Bad request", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + } + } + } + }, + "/api/v1/branch/{id}": { + "get": { + "description": "Get details of a branch based on the provided ID.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Branch APIs" + ], + "summary": "Get details of a branch by ID", + "parameters": [ + { + "type": "string", + "description": "JWT token", + "name": "Authorization", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Branch ID to retrieve", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "Branch details", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.Branch" + } + } + } + ] + } + }, + "400": { + "description": "Bad request", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + } + } + }, + "put": { + "description": "Update the details of an existing branch based on the provided ID.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Branch APIs" + ], + "summary": "Update an existing branch", + "parameters": [ + { + "type": "string", + "description": "JWT token", + "name": "Authorization", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Branch ID to update", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Updated branch details", + "name": "req", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.Branch" + } + } + ], + "responses": { + "200": { + "description": "Branch updated successfully", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.Branch" + } + } + } + ] + } + }, + "400": { + "description": "Bad request", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + } + } + }, + "delete": { + "description": "Delete a branch based on the provided ID.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Branch APIs" + ], + "summary": "Delete a branch by ID", + "parameters": [ + { + "type": "string", + "description": "JWT token", + "name": "Authorization", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Branch ID to delete", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "Branch deleted successfully", + "schema": { + "$ref": "#/definitions/response.BaseResponse" + } + }, + "400": { + "description": "Bad request", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + } + } + } + }, + "/api/v1/file/upload": { + "post": { + "description": "Upload a file to Alibaba Cloud OSS with the provided details.", + "consumes": [ + "multipart/form-data" + ], + "produces": [ + "application/json" + ], + "tags": [ + "File Upload API" + ], + "summary": "Upload a file to OSS", + "parameters": [ + { + "type": "string", + "description": "JWT token", + "name": "Authorization", + "in": "header", + "required": true + }, + { + "type": "file", + "description": "File to upload (max size: 2MB)", + "name": "file", + "in": "formData", + "required": true + } + ], + "responses": { + "200": { + "description": "File uploaded successfully", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/entity.UploadFileResponse" + } + } + } + ] + } + }, + "400": { + "description": "Bad request", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + } + } + } + }, + "/api/v1/order": { + "post": { + "description": "Create a new order with the provided details.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "summary": "Create a new order", + "parameters": [ + { + "type": "string", + "description": "JWT token", + "name": "Authorization", + "in": "header", + "required": true + }, + { + "description": "Order details", + "name": "order", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.Order" + } + } + ], + "responses": { + "200": { + "description": "Order created successfully", + "schema": { + "$ref": "#/definitions/response.BaseResponse" + } + }, + "400": { + "description": "Bad request", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + } + } + } + }, + "/api/v1/order/branch-revenue": { + "get": { + "description": "Retrieve the branch-wise revenue for orders based on the specified parameters.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "summary": "Get branch-wise revenue for orders", + "parameters": [ + { + "type": "string", + "description": "JWT token", + "name": "Authorization", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Branch ID for filtering", + "name": "branch_id", + "in": "query" + }, + { + "type": "string", + "description": "Start date for filtering (format: 'YYYY-MM-DD')", + "name": "start_date", + "in": "query" + }, + { + "type": "string", + "description": "End date for filtering (format: 'YYYY-MM-DD')", + "name": "end_date", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Branch-wise revenue retrieved successfully", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/response.OrderBranchRevenue" + } + } + } + } + ] + } + }, + "400": { + "description": "Bad request", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + }, + "500": { + "description": "Internal server error", + "schema": { + "$ref": "#/definitions/response.BaseResponse" + } + } + } + } + }, + "/api/v1/order/list": { + "get": { + "description": "Retrieve a list of orders based on the specified parameters.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "summary": "Get a list of orders", + "parameters": [ + { + "type": "string", + "description": "JWT token", + "name": "Authorization", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Number of items to retrieve (default: 10)", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "description": "Number of items to skip (default: 0)", + "name": "offset", + "in": "query" + } + ], + "responses": { + "200": { + "description": "List of orders retrieved successfully", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.OrderList" + } + } + } + ] + } + }, + "400": { + "description": "Bad request", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + }, + "500": { + "description": "Internal server error", + "schema": { + "$ref": "#/definitions/response.BaseResponse" + } + } + } + } + }, + "/api/v1/order/total-revenue": { + "get": { + "description": "Retrieve the total revenue and number of transactions for orders based on the specified parameters.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "summary": "Get total revenue and number of transactions for orders", + "parameters": [ + { + "type": "string", + "description": "JWT token", + "name": "Authorization", + "in": "header", + "required": true + }, + { + "type": "string", + "description": "Start date for filtering (format: 'YYYY-MM-DD')", + "name": "start_date", + "in": "query" + }, + { + "type": "string", + "description": "End date for filtering (format: 'YYYY-MM-DD')", + "name": "end_date", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Total revenue and transactions retrieved successfully", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.OrderMonthlyRevenue" + } + } + } + ] + } + }, + "400": { + "description": "Bad request", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + }, + "500": { + "description": "Internal server error", + "schema": { + "$ref": "#/definitions/response.BaseResponse" + } + } + } + } + }, + "/api/v1/order/update-status/{id}": { + "put": { + "description": "Update the status of the specified order with the provided details.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "summary": "Update the status of an order", + "parameters": [ + { + "type": "string", + "description": "JWT token", + "name": "Authorization", + "in": "header", + "required": true + }, + { + "type": "string", + "description": "Order ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Status details", + "name": "status", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.UpdateStatus" + } + } + ], + "responses": { + "200": { + "description": "Order status updated successfully", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.Order" + } + } + } + ] + } + }, + "400": { + "description": "Bad request", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + }, + "500": { + "description": "Internal server error", + "schema": { + "$ref": "#/definitions/response.BaseResponse" + } + } + } + } + }, + "/api/v1/order/yearly-revenue/{year}": { + "get": { + "description": "Retrieve the yearly revenue for orders based on the specified year.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "summary": "Get yearly revenue for orders", + "parameters": [ + { + "type": "string", + "description": "JWT token", + "name": "Authorization", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Year for filtering", + "name": "year", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "Yearly revenue retrieved successfully", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "additionalProperties": { + "type": "object", + "additionalProperties": { + "type": "number" + } + } + } + } + } + ] + } + }, + "400": { + "description": "Bad request", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + }, + "500": { + "description": "Internal server error", + "schema": { + "$ref": "#/definitions/response.BaseResponse" + } + } + } + } + }, + "/api/v1/order/{id}": { + "get": { + "description": "Retrieve the details of the specified order by ID.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "summary": "Get details of an order by ID", + "parameters": [ + { + "type": "string", + "description": "JWT token", + "name": "Authorization", + "in": "header", + "required": true + }, + { + "type": "string", + "description": "Order ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "Order details retrieved successfully", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.Order" + } + } + } + ] + } + }, + "400": { + "description": "Bad request", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + }, + "500": { + "description": "Internal server error", + "schema": { + "$ref": "#/definitions/response.BaseResponse" + } + } + } + } + }, + "/api/v1/product/": { + "post": { + "description": "Create a new product with the provided details.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Product APIs" + ], + "summary": "Create a new product", + "parameters": [ + { + "type": "string", + "description": "JWT token", + "name": "Authorization", + "in": "header", + "required": true + }, + { + "description": "Product details to create", + "name": "req", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.Product" + } + } + ], + "responses": { + "200": { + "description": "Product created successfully", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.Product" + } + } + } + ] + } + }, + "400": { + "description": "Bad request", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + } + } + } + }, + "/api/v1/product/list": { + "get": { + "description": "Get a paginated list of products based on query parameters.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Product APIs" + ], + "summary": "Get a list of products", + "parameters": [ + { + "type": "string", + "description": "JWT token", + "name": "Authorization", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Number of items to retrieve (default 10)", + "name": "Limit", + "in": "query" + }, + { + "type": "integer", + "description": "Offset for pagination (default 0)", + "name": "Offset", + "in": "query" + } + ], + "responses": { + "200": { + "description": "List of products", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.ProductList" + } + } + } + ] + } + }, + "400": { + "description": "Bad request", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + } + } + } + }, + "/api/v1/product/{id}": { + "get": { + "description": "Get details of a product based on the provided ID.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Product APIs" + ], + "summary": "Get details of a product by ID", + "parameters": [ + { + "type": "string", + "description": "JWT token", + "name": "Authorization", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Product ID to retrieve", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "Product details", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.Product" + } + } + } + ] + } + }, + "400": { + "description": "Bad request", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + } + } + }, + "put": { + "description": "Update the details of an existing product based on the provided ID.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Product APIs" + ], + "summary": "Update an existing product", + "parameters": [ + { + "type": "string", + "description": "JWT token", + "name": "Authorization", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Product ID to update", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Updated product details", + "name": "req", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.Product" + } + } + ], + "responses": { + "200": { + "description": "Product updated successfully", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.Product" + } + } + } + ] + } + }, + "400": { + "description": "Bad request", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + } + } + }, + "delete": { + "description": "Delete a product based on the provided ID.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Product APIs" + ], + "summary": "Delete a product by ID", + "parameters": [ + { + "type": "string", + "description": "JWT token", + "name": "Authorization", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Product ID to delete", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "Product deleted successfully", + "schema": { + "$ref": "#/definitions/response.BaseResponse" + } + }, + "400": { + "description": "Bad request", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + } + } + } + }, + "/api/v1/studio": { + "post": { + "description": "Create a new studio based on the provided details.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Studio APIs" + ], + "summary": "Create a new studio", + "parameters": [ + { + "type": "string", + "description": "JWT token", + "name": "Authorization", + "in": "header", + "required": true + }, + { + "description": "New studio details", + "name": "req", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.Studio" + } + } + ], + "responses": { + "200": { + "description": "Studio created successfully", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.Studio" + } + } + } + ] + } + }, + "400": { + "description": "Bad request", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + } + } + } + }, + "/api/v1/studio/search": { + "get": { + "description": "Search for studios based on query parameters.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Studio APIs" + ], + "summary": "Search for studios", + "parameters": [ + { + "type": "string", + "description": "JWT token", + "name": "Authorization", + "in": "header", + "required": true + }, + { + "type": "string", + "description": "Studio name for search", + "name": "Name", + "in": "query" + }, + { + "type": "string", + "description": "Studio status for search", + "name": "Status", + "in": "query" + }, + { + "type": "integer", + "description": "Number of items to retrieve (default 10)", + "name": "Limit", + "in": "query" + }, + { + "type": "integer", + "description": "Offset for pagination (default 0)", + "name": "Offset", + "in": "query" + } + ], + "responses": { + "200": { + "description": "List of studios", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.StudioList" + } + } + } + ] + } + }, + "400": { + "description": "Bad request", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + } + } + } + }, + "/api/v1/studio/{id}": { + "get": { + "description": "Get details of a studio based on the provided ID.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Studio APIs" + ], + "summary": "Get details of a studio by ID", + "parameters": [ + { + "type": "string", + "description": "JWT token", + "name": "Authorization", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Studio ID to retrieve", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "Studio details", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.Studio" + } + } + } + ] + } + }, + "400": { + "description": "Bad request", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + } + } + }, + "put": { + "description": "Update the details of an existing studio based on the provided ID.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Studio APIs" + ], + "summary": "Update an existing studio", + "parameters": [ + { + "type": "string", + "description": "JWT token", + "name": "Authorization", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Studio ID to update", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Updated studio details", + "name": "req", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.Studio" + } + } + ], + "responses": { + "200": { + "description": "Studio updated successfully", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.Studio" + } + } + } + ] + } + }, + "400": { + "description": "Bad request", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + } + } + } + }, + "/api/v1/user": { + "post": { + "description": "Create a new user based on the provided data.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "User APIs" + ], + "summary": "Create a new user", + "parameters": [ + { + "type": "string", + "description": "JWT token", + "name": "Authorization", + "in": "header", + "required": true + }, + { + "description": "New user details", + "name": "req", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.User" + } + } + ], + "responses": { + "200": { + "description": "User created successfully", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.User" + } + } + } + ] + } + }, + "400": { + "description": "Bad request", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + } + } + } + }, + "/api/v1/user/list": { + "get": { + "description": "Get a paginated list of users based on query parameters.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "User APIs" + ], + "summary": "Get a list of users", + "parameters": [ + { + "type": "string", + "description": "JWT token", + "name": "Authorization", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Number of items to retrieve (default 10)", + "name": "Limit", + "in": "query" + }, + { + "type": "integer", + "description": "Offset for pagination (default 0)", + "name": "Offset", + "in": "query" + } + ], + "responses": { + "200": { + "description": "List of users", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.UserList" + } + } + } + ] + } + }, + "400": { + "description": "Bad request", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + } + } + } + }, + "/api/v1/user/{id}": { + "get": { + "description": "Get details of a user based on the provided ID.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "User APIs" + ], + "summary": "Get details of a user by ID", + "parameters": [ + { + "type": "string", + "description": "JWT token", + "name": "Authorization", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "User ID to retrieve", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "User details", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.User" + } + } + } + ] + } + }, + "400": { + "description": "Bad request", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + } + } + }, + "put": { + "description": "Update the details of an existing user based on the provided ID.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "User APIs" + ], + "summary": "Update an existing user", + "parameters": [ + { + "type": "string", + "description": "JWT token", + "name": "Authorization", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "User ID to update", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Updated user details", + "name": "req", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.User" + } + } + ], + "responses": { + "200": { + "description": "User updated successfully", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.User" + } + } + } + ] + } + }, + "400": { + "description": "Bad request", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + } + } + }, + "delete": { + "description": "Delete a user based on the provided ID.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "User APIs" + ], + "summary": "Delete a user by ID", + "parameters": [ + { + "type": "string", + "description": "JWT token", + "name": "Authorization", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "User ID to delete", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "User deleted successfully", + "schema": { + "$ref": "#/definitions/response.BaseResponse" + } + }, + "400": { + "description": "Bad request", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + } + } + } + } + }, + "definitions": { + "branch.BranchStatus": { + "type": "string", + "enum": [ + "Active", + "Inactive" + ], + "x-enum-varnames": [ + "Active", + "Inactive" + ] + }, + "entity.UploadFileResponse": { + "type": "object", + "properties": { + "file_path": { + "type": "string" + }, + "file_url": { + "type": "string" + } + } + }, + "order.ItemType": { + "type": "string", + "enum": [ + "PRODUCT", + "STUDIO" + ], + "x-enum-varnames": [ + "Product", + "Studio" + ] + }, + "order.OrderStatus": { + "type": "string", + "enum": [ + "NEW", + "PAID", + "CANCEL" + ], + "x-enum-varnames": [ + "New", + "Paid", + "Cancel" + ] + }, + "product.ProductStatus": { + "type": "string", + "enum": [ + "Active", + "Inactive" + ], + "x-enum-varnames": [ + "Active", + "Inactive" + ] + }, + "product.ProductType": { + "type": "string", + "enum": [ + "FOOD", + "BEVERAGE" + ], + "x-enum-varnames": [ + "Food", + "Beverage" + ] + }, + "request.Branch": { + "type": "object", + "required": [ + "location", + "name" + ], + "properties": { + "location": { + "type": "string" + }, + "name": { + "type": "string" + }, + "status": { + "$ref": "#/definitions/branch.BranchStatus" + } + } + }, + "request.LoginRequest": { + "type": "object", + "properties": { + "email": { + "type": "string" + }, + "password": { + "type": "string" + } + } + }, + "request.Order": { + "type": "object", + "required": [ + "amount", + "branch_id", + "customer_name", + "customer_phone", + "order_items", + "pax", + "payment_method" + ], + "properties": { + "amount": { + "type": "number" + }, + "branch_id": { + "type": "integer" + }, + "customer_name": { + "type": "string" + }, + "customer_phone": { + "type": "string" + }, + "order_items": { + "type": "array", + "items": { + "$ref": "#/definitions/request.OrderItem" + } + }, + "pax": { + "type": "integer" + }, + "payment_method": { + "$ref": "#/definitions/transaction.PaymentMethod" + } + } + }, + "request.OrderItem": { + "type": "object", + "required": [ + "item_id", + "item_type", + "price", + "qty" + ], + "properties": { + "item_id": { + "type": "integer" + }, + "item_type": { + "$ref": "#/definitions/order.ItemType" + }, + "price": { + "type": "number" + }, + "qty": { + "type": "integer" + } + } + }, + "request.Product": { + "type": "object", + "required": [ + "branch_id", + "name", + "price", + "status", + "type" + ], + "properties": { + "branch_id": { + "type": "integer" + }, + "description": { + "type": "string" + }, + "name": { + "type": "string" + }, + "price": { + "type": "number" + }, + "status": { + "$ref": "#/definitions/product.ProductStatus" + }, + "stock_qty": { + "type": "integer" + }, + "type": { + "$ref": "#/definitions/product.ProductType" + } + } + }, + "request.Studio": { + "type": "object", + "required": [ + "branch_id", + "name", + "price" + ], + "properties": { + "branch_id": { + "type": "integer" + }, + "metadata": { + "type": "object", + "additionalProperties": true + }, + "name": { + "type": "string" + }, + "price": { + "type": "number" + }, + "status": { + "$ref": "#/definitions/studio.StudioStatus" + } + } + }, + "request.UpdateStatus": { + "type": "object", + "properties": { + "status": { + "allOf": [ + { + "$ref": "#/definitions/order.OrderStatus" + } + ], + "example": "NEW,PAID,CANCEL" + } + } + }, + "request.User": { + "type": "object", + "required": [ + "email", + "name", + "password", + "role_id" + ], + "properties": { + "branch_id": { + "type": "integer" + }, + "email": { + "type": "string" + }, + "name": { + "type": "string" + }, + "password": { + "type": "string" + }, + "role_id": { + "type": "integer" + } + } + }, + "response.BaseResponse": { + "type": "object", + "properties": { + "data": {}, + "error_detail": {}, + "error_message": { + "type": "string" + }, + "message": { + "type": "string" + }, + "meta": { + "$ref": "#/definitions/response.PagingMeta" + }, + "response_code": { + "type": "string" + }, + "success": { + "type": "boolean" + } + } + }, + "response.Branch": { + "type": "object", + "properties": { + "created_at": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "location": { + "type": "string" + }, + "name": { + "type": "string" + }, + "status": { + "type": "string" + }, + "updated_at": { + "type": "string" + } + } + }, + "response.BranchList": { + "type": "object", + "properties": { + "branches": { + "type": "array", + "items": { + "$ref": "#/definitions/response.Branch" + } + }, + "limit": { + "type": "integer" + }, + "offset": { + "type": "integer" + }, + "total": { + "type": "integer" + } + } + }, + "response.LoginResponse": { + "type": "object", + "properties": { + "branch": { + "$ref": "#/definitions/response.Branch" + }, + "name": { + "type": "string" + }, + "role": { + "$ref": "#/definitions/response.Role" + }, + "token": { + "type": "string" + } + } + }, + "response.Order": { + "type": "object", + "properties": { + "amount": { + "type": "number" + }, + "branch_id": { + "type": "integer" + }, + "branch_name": { + "type": "string" + }, + "created_at": { + "type": "string" + }, + "customer_name": { + "type": "string" + }, + "customer_phone": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "order_items": { + "type": "array", + "items": { + "$ref": "#/definitions/response.OrderItem" + } + }, + "pax": { + "type": "integer" + }, + "payment_method": { + "$ref": "#/definitions/transaction.PaymentMethod" + }, + "status": { + "$ref": "#/definitions/order.OrderStatus" + }, + "updated_at": { + "type": "string" + } + } + }, + "response.OrderBranchRevenue": { + "type": "object", + "properties": { + "branch_id": { + "type": "string" + }, + "location": { + "type": "string" + }, + "name": { + "type": "string" + }, + "total_amount": { + "type": "number" + }, + "total_trans": { + "type": "integer" + } + } + }, + "response.OrderItem": { + "type": "object", + "properties": { + "created_at": { + "type": "string" + }, + "item_id": { + "type": "integer" + }, + "item_name": { + "type": "string" + }, + "item_type": { + "$ref": "#/definitions/order.ItemType" + }, + "order_item_id": { + "type": "integer" + }, + "price": { + "type": "number" + }, + "qty": { + "type": "integer" + }, + "updated_at": { + "type": "string" + } + } + }, + "response.OrderList": { + "type": "object", + "properties": { + "limit": { + "type": "integer" + }, + "offset": { + "type": "integer" + }, + "orders": { + "type": "array", + "items": { + "$ref": "#/definitions/response.Order" + } + }, + "total": { + "type": "integer" + } + } + }, + "response.OrderMonthlyRevenue": { + "type": "object", + "properties": { + "total_revenue": { + "type": "number" + }, + "total_transaction": { + "type": "integer" + } + } + }, + "response.PagingMeta": { + "type": "object", + "properties": { + "limit": { + "type": "integer" + }, + "page": { + "type": "integer" + }, + "total_data": { + "type": "integer" + } + } + }, + "response.Product": { + "type": "object", + "properties": { + "branch_id": { + "type": "integer" + }, + "created_at": { + "type": "string" + }, + "description": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "price": { + "type": "number" + }, + "status": { + "$ref": "#/definitions/product.ProductStatus" + }, + "stock_qty": { + "type": "integer" + }, + "type": { + "$ref": "#/definitions/product.ProductType" + }, + "updated_at": { + "type": "string" + } + } + }, + "response.ProductList": { + "type": "object", + "properties": { + "limit": { + "type": "integer" + }, + "offset": { + "type": "integer" + }, + "products": { + "type": "array", + "items": { + "$ref": "#/definitions/response.Product" + } + }, + "total": { + "type": "integer" + } + } + }, + "response.Role": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "role_name": { + "type": "string" + } + } + }, + "response.Studio": { + "type": "object", + "properties": { + "branch_id": { + "type": "integer" + }, + "created_at": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "metadata": { + "type": "object", + "additionalProperties": true + }, + "name": { + "type": "string" + }, + "price": { + "type": "number" + }, + "status": { + "type": "string" + }, + "updated_at": { + "type": "string" + } + } + }, + "response.StudioList": { + "type": "object", + "properties": { + "limit": { + "type": "integer" + }, + "offset": { + "type": "integer" + }, + "studios": { + "type": "array", + "items": { + "$ref": "#/definitions/response.Studio" + } + }, + "total": { + "type": "integer" + } + } + }, + "response.User": { + "type": "object", + "properties": { + "branch_id": { + "type": "integer" + }, + "branch_name": { + "type": "string" + }, + "created_at": { + "type": "string" + }, + "email": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "role_id": { + "type": "integer" + }, + "role_name": { + "type": "string" + }, + "status": { + "type": "string" + }, + "updated_at": { + "type": "string" + } + } + }, + "response.UserList": { + "type": "object", + "properties": { + "limit": { + "type": "integer" + }, + "offset": { + "type": "integer" + }, + "total": { + "type": "integer" + }, + "users": { + "type": "array", + "items": { + "$ref": "#/definitions/response.User" + } + } + } + }, + "studio.StudioStatus": { + "type": "string", + "enum": [ + "Active", + "Inactive" + ], + "x-enum-varnames": [ + "Active", + "Inactive" + ] + }, + "transaction.PaymentMethod": { + "type": "string", + "enum": [ + "CASH", + "DEBIT", + "TRANSFER", + "QRIS" + ], + "x-enum-varnames": [ + "Cash", + "Debit", + "Transfer", + "QRIS" + ] + } + } +}` + +// SwaggerInfo holds exported Swagger Info so clients can modify it +var SwaggerInfo = &swag.Spec{ + Version: "", + Host: "", + BasePath: "", + Schemes: []string{}, + Title: "", + Description: "", + InfoInstanceName: "swagger", + SwaggerTemplate: docTemplate, + LeftDelim: "{{", + RightDelim: "}}", +} + +func init() { + swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo) +} diff --git a/docs/swagger.json b/docs/swagger.json new file mode 100644 index 0000000..cfd5d11 --- /dev/null +++ b/docs/swagger.json @@ -0,0 +1,3072 @@ +{ + "swagger": "2.0", + "info": { + "contact": {} + }, + "paths": { + "/api/v1/auth/login": { + "post": { + "description": "Authenticates a user based on the provided credentials and returns a JWT token.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Auth Login API's" + ], + "summary": "User login", + "parameters": [ + { + "description": "User login credentials", + "name": "bodyParam", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.LoginRequest" + } + } + ], + "responses": { + "200": { + "description": "Login successful", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.LoginResponse" + } + } + } + ] + } + }, + "400": { + "description": "Bad request", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + } + } + } + }, + "/api/v1/branch": { + "post": { + "description": "Create a new branch based on the provided data.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Branch APIs" + ], + "summary": "Create a new branch", + "parameters": [ + { + "type": "string", + "description": "JWT token", + "name": "Authorization", + "in": "header", + "required": true + }, + { + "description": "New branch details", + "name": "req", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.Branch" + } + } + ], + "responses": { + "200": { + "description": "Branch created successfully", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.Branch" + } + } + } + ] + } + }, + "400": { + "description": "Bad request", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + } + } + } + }, + "/api/v1/branch/list": { + "get": { + "description": "Get a paginated list of branches based on query parameters.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Branch APIs" + ], + "summary": "Get a list of branches", + "parameters": [ + { + "type": "string", + "description": "JWT token", + "name": "Authorization", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Number of items to retrieve (default 10)", + "name": "Limit", + "in": "query" + }, + { + "type": "integer", + "description": "Offset for pagination (default 0)", + "name": "Offset", + "in": "query" + } + ], + "responses": { + "200": { + "description": "List of branches", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.BranchList" + } + } + } + ] + } + }, + "400": { + "description": "Bad request", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + } + } + } + }, + "/api/v1/branch/{id}": { + "get": { + "description": "Get details of a branch based on the provided ID.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Branch APIs" + ], + "summary": "Get details of a branch by ID", + "parameters": [ + { + "type": "string", + "description": "JWT token", + "name": "Authorization", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Branch ID to retrieve", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "Branch details", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.Branch" + } + } + } + ] + } + }, + "400": { + "description": "Bad request", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + } + } + }, + "put": { + "description": "Update the details of an existing branch based on the provided ID.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Branch APIs" + ], + "summary": "Update an existing branch", + "parameters": [ + { + "type": "string", + "description": "JWT token", + "name": "Authorization", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Branch ID to update", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Updated branch details", + "name": "req", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.Branch" + } + } + ], + "responses": { + "200": { + "description": "Branch updated successfully", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.Branch" + } + } + } + ] + } + }, + "400": { + "description": "Bad request", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + } + } + }, + "delete": { + "description": "Delete a branch based on the provided ID.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Branch APIs" + ], + "summary": "Delete a branch by ID", + "parameters": [ + { + "type": "string", + "description": "JWT token", + "name": "Authorization", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Branch ID to delete", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "Branch deleted successfully", + "schema": { + "$ref": "#/definitions/response.BaseResponse" + } + }, + "400": { + "description": "Bad request", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + } + } + } + }, + "/api/v1/file/upload": { + "post": { + "description": "Upload a file to Alibaba Cloud OSS with the provided details.", + "consumes": [ + "multipart/form-data" + ], + "produces": [ + "application/json" + ], + "tags": [ + "File Upload API" + ], + "summary": "Upload a file to OSS", + "parameters": [ + { + "type": "string", + "description": "JWT token", + "name": "Authorization", + "in": "header", + "required": true + }, + { + "type": "file", + "description": "File to upload (max size: 2MB)", + "name": "file", + "in": "formData", + "required": true + } + ], + "responses": { + "200": { + "description": "File uploaded successfully", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/entity.UploadFileResponse" + } + } + } + ] + } + }, + "400": { + "description": "Bad request", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + } + } + } + }, + "/api/v1/order": { + "post": { + "description": "Create a new order with the provided details.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "summary": "Create a new order", + "parameters": [ + { + "type": "string", + "description": "JWT token", + "name": "Authorization", + "in": "header", + "required": true + }, + { + "description": "Order details", + "name": "order", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.Order" + } + } + ], + "responses": { + "200": { + "description": "Order created successfully", + "schema": { + "$ref": "#/definitions/response.BaseResponse" + } + }, + "400": { + "description": "Bad request", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + } + } + } + }, + "/api/v1/order/branch-revenue": { + "get": { + "description": "Retrieve the branch-wise revenue for orders based on the specified parameters.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "summary": "Get branch-wise revenue for orders", + "parameters": [ + { + "type": "string", + "description": "JWT token", + "name": "Authorization", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Branch ID for filtering", + "name": "branch_id", + "in": "query" + }, + { + "type": "string", + "description": "Start date for filtering (format: 'YYYY-MM-DD')", + "name": "start_date", + "in": "query" + }, + { + "type": "string", + "description": "End date for filtering (format: 'YYYY-MM-DD')", + "name": "end_date", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Branch-wise revenue retrieved successfully", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/response.OrderBranchRevenue" + } + } + } + } + ] + } + }, + "400": { + "description": "Bad request", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + }, + "500": { + "description": "Internal server error", + "schema": { + "$ref": "#/definitions/response.BaseResponse" + } + } + } + } + }, + "/api/v1/order/list": { + "get": { + "description": "Retrieve a list of orders based on the specified parameters.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "summary": "Get a list of orders", + "parameters": [ + { + "type": "string", + "description": "JWT token", + "name": "Authorization", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Number of items to retrieve (default: 10)", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "description": "Number of items to skip (default: 0)", + "name": "offset", + "in": "query" + } + ], + "responses": { + "200": { + "description": "List of orders retrieved successfully", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.OrderList" + } + } + } + ] + } + }, + "400": { + "description": "Bad request", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + }, + "500": { + "description": "Internal server error", + "schema": { + "$ref": "#/definitions/response.BaseResponse" + } + } + } + } + }, + "/api/v1/order/total-revenue": { + "get": { + "description": "Retrieve the total revenue and number of transactions for orders based on the specified parameters.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "summary": "Get total revenue and number of transactions for orders", + "parameters": [ + { + "type": "string", + "description": "JWT token", + "name": "Authorization", + "in": "header", + "required": true + }, + { + "type": "string", + "description": "Start date for filtering (format: 'YYYY-MM-DD')", + "name": "start_date", + "in": "query" + }, + { + "type": "string", + "description": "End date for filtering (format: 'YYYY-MM-DD')", + "name": "end_date", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Total revenue and transactions retrieved successfully", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.OrderMonthlyRevenue" + } + } + } + ] + } + }, + "400": { + "description": "Bad request", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + }, + "500": { + "description": "Internal server error", + "schema": { + "$ref": "#/definitions/response.BaseResponse" + } + } + } + } + }, + "/api/v1/order/update-status/{id}": { + "put": { + "description": "Update the status of the specified order with the provided details.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "summary": "Update the status of an order", + "parameters": [ + { + "type": "string", + "description": "JWT token", + "name": "Authorization", + "in": "header", + "required": true + }, + { + "type": "string", + "description": "Order ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Status details", + "name": "status", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.UpdateStatus" + } + } + ], + "responses": { + "200": { + "description": "Order status updated successfully", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.Order" + } + } + } + ] + } + }, + "400": { + "description": "Bad request", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + }, + "500": { + "description": "Internal server error", + "schema": { + "$ref": "#/definitions/response.BaseResponse" + } + } + } + } + }, + "/api/v1/order/yearly-revenue/{year}": { + "get": { + "description": "Retrieve the yearly revenue for orders based on the specified year.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "summary": "Get yearly revenue for orders", + "parameters": [ + { + "type": "string", + "description": "JWT token", + "name": "Authorization", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Year for filtering", + "name": "year", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "Yearly revenue retrieved successfully", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "additionalProperties": { + "type": "object", + "additionalProperties": { + "type": "number" + } + } + } + } + } + ] + } + }, + "400": { + "description": "Bad request", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + }, + "500": { + "description": "Internal server error", + "schema": { + "$ref": "#/definitions/response.BaseResponse" + } + } + } + } + }, + "/api/v1/order/{id}": { + "get": { + "description": "Retrieve the details of the specified order by ID.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "summary": "Get details of an order by ID", + "parameters": [ + { + "type": "string", + "description": "JWT token", + "name": "Authorization", + "in": "header", + "required": true + }, + { + "type": "string", + "description": "Order ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "Order details retrieved successfully", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.Order" + } + } + } + ] + } + }, + "400": { + "description": "Bad request", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + }, + "500": { + "description": "Internal server error", + "schema": { + "$ref": "#/definitions/response.BaseResponse" + } + } + } + } + }, + "/api/v1/product/": { + "post": { + "description": "Create a new product with the provided details.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Product APIs" + ], + "summary": "Create a new product", + "parameters": [ + { + "type": "string", + "description": "JWT token", + "name": "Authorization", + "in": "header", + "required": true + }, + { + "description": "Product details to create", + "name": "req", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.Product" + } + } + ], + "responses": { + "200": { + "description": "Product created successfully", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.Product" + } + } + } + ] + } + }, + "400": { + "description": "Bad request", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + } + } + } + }, + "/api/v1/product/list": { + "get": { + "description": "Get a paginated list of products based on query parameters.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Product APIs" + ], + "summary": "Get a list of products", + "parameters": [ + { + "type": "string", + "description": "JWT token", + "name": "Authorization", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Number of items to retrieve (default 10)", + "name": "Limit", + "in": "query" + }, + { + "type": "integer", + "description": "Offset for pagination (default 0)", + "name": "Offset", + "in": "query" + } + ], + "responses": { + "200": { + "description": "List of products", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.ProductList" + } + } + } + ] + } + }, + "400": { + "description": "Bad request", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + } + } + } + }, + "/api/v1/product/{id}": { + "get": { + "description": "Get details of a product based on the provided ID.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Product APIs" + ], + "summary": "Get details of a product by ID", + "parameters": [ + { + "type": "string", + "description": "JWT token", + "name": "Authorization", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Product ID to retrieve", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "Product details", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.Product" + } + } + } + ] + } + }, + "400": { + "description": "Bad request", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + } + } + }, + "put": { + "description": "Update the details of an existing product based on the provided ID.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Product APIs" + ], + "summary": "Update an existing product", + "parameters": [ + { + "type": "string", + "description": "JWT token", + "name": "Authorization", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Product ID to update", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Updated product details", + "name": "req", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.Product" + } + } + ], + "responses": { + "200": { + "description": "Product updated successfully", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.Product" + } + } + } + ] + } + }, + "400": { + "description": "Bad request", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + } + } + }, + "delete": { + "description": "Delete a product based on the provided ID.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Product APIs" + ], + "summary": "Delete a product by ID", + "parameters": [ + { + "type": "string", + "description": "JWT token", + "name": "Authorization", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Product ID to delete", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "Product deleted successfully", + "schema": { + "$ref": "#/definitions/response.BaseResponse" + } + }, + "400": { + "description": "Bad request", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + } + } + } + }, + "/api/v1/studio": { + "post": { + "description": "Create a new studio based on the provided details.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Studio APIs" + ], + "summary": "Create a new studio", + "parameters": [ + { + "type": "string", + "description": "JWT token", + "name": "Authorization", + "in": "header", + "required": true + }, + { + "description": "New studio details", + "name": "req", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.Studio" + } + } + ], + "responses": { + "200": { + "description": "Studio created successfully", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.Studio" + } + } + } + ] + } + }, + "400": { + "description": "Bad request", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + } + } + } + }, + "/api/v1/studio/search": { + "get": { + "description": "Search for studios based on query parameters.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Studio APIs" + ], + "summary": "Search for studios", + "parameters": [ + { + "type": "string", + "description": "JWT token", + "name": "Authorization", + "in": "header", + "required": true + }, + { + "type": "string", + "description": "Studio name for search", + "name": "Name", + "in": "query" + }, + { + "type": "string", + "description": "Studio status for search", + "name": "Status", + "in": "query" + }, + { + "type": "integer", + "description": "Number of items to retrieve (default 10)", + "name": "Limit", + "in": "query" + }, + { + "type": "integer", + "description": "Offset for pagination (default 0)", + "name": "Offset", + "in": "query" + } + ], + "responses": { + "200": { + "description": "List of studios", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.StudioList" + } + } + } + ] + } + }, + "400": { + "description": "Bad request", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + } + } + } + }, + "/api/v1/studio/{id}": { + "get": { + "description": "Get details of a studio based on the provided ID.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Studio APIs" + ], + "summary": "Get details of a studio by ID", + "parameters": [ + { + "type": "string", + "description": "JWT token", + "name": "Authorization", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Studio ID to retrieve", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "Studio details", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.Studio" + } + } + } + ] + } + }, + "400": { + "description": "Bad request", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + } + } + }, + "put": { + "description": "Update the details of an existing studio based on the provided ID.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Studio APIs" + ], + "summary": "Update an existing studio", + "parameters": [ + { + "type": "string", + "description": "JWT token", + "name": "Authorization", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Studio ID to update", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Updated studio details", + "name": "req", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.Studio" + } + } + ], + "responses": { + "200": { + "description": "Studio updated successfully", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.Studio" + } + } + } + ] + } + }, + "400": { + "description": "Bad request", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + } + } + } + }, + "/api/v1/user": { + "post": { + "description": "Create a new user based on the provided data.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "User APIs" + ], + "summary": "Create a new user", + "parameters": [ + { + "type": "string", + "description": "JWT token", + "name": "Authorization", + "in": "header", + "required": true + }, + { + "description": "New user details", + "name": "req", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.User" + } + } + ], + "responses": { + "200": { + "description": "User created successfully", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.User" + } + } + } + ] + } + }, + "400": { + "description": "Bad request", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + } + } + } + }, + "/api/v1/user/list": { + "get": { + "description": "Get a paginated list of users based on query parameters.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "User APIs" + ], + "summary": "Get a list of users", + "parameters": [ + { + "type": "string", + "description": "JWT token", + "name": "Authorization", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Number of items to retrieve (default 10)", + "name": "Limit", + "in": "query" + }, + { + "type": "integer", + "description": "Offset for pagination (default 0)", + "name": "Offset", + "in": "query" + } + ], + "responses": { + "200": { + "description": "List of users", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.UserList" + } + } + } + ] + } + }, + "400": { + "description": "Bad request", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + } + } + } + }, + "/api/v1/user/{id}": { + "get": { + "description": "Get details of a user based on the provided ID.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "User APIs" + ], + "summary": "Get details of a user by ID", + "parameters": [ + { + "type": "string", + "description": "JWT token", + "name": "Authorization", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "User ID to retrieve", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "User details", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.User" + } + } + } + ] + } + }, + "400": { + "description": "Bad request", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + } + } + }, + "put": { + "description": "Update the details of an existing user based on the provided ID.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "User APIs" + ], + "summary": "Update an existing user", + "parameters": [ + { + "type": "string", + "description": "JWT token", + "name": "Authorization", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "User ID to update", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Updated user details", + "name": "req", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.User" + } + } + ], + "responses": { + "200": { + "description": "User updated successfully", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.User" + } + } + } + ] + } + }, + "400": { + "description": "Bad request", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + } + } + }, + "delete": { + "description": "Delete a user based on the provided ID.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "User APIs" + ], + "summary": "Delete a user by ID", + "parameters": [ + { + "type": "string", + "description": "JWT token", + "name": "Authorization", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "User ID to delete", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "User deleted successfully", + "schema": { + "$ref": "#/definitions/response.BaseResponse" + } + }, + "400": { + "description": "Bad request", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.BaseResponse" + }, + { + "type": "object", + "properties": { + "data": {} + } + } + ] + } + } + } + } + } + }, + "definitions": { + "branch.BranchStatus": { + "type": "string", + "enum": [ + "Active", + "Inactive" + ], + "x-enum-varnames": [ + "Active", + "Inactive" + ] + }, + "entity.UploadFileResponse": { + "type": "object", + "properties": { + "file_path": { + "type": "string" + }, + "file_url": { + "type": "string" + } + } + }, + "order.ItemType": { + "type": "string", + "enum": [ + "PRODUCT", + "STUDIO" + ], + "x-enum-varnames": [ + "Product", + "Studio" + ] + }, + "order.OrderStatus": { + "type": "string", + "enum": [ + "NEW", + "PAID", + "CANCEL" + ], + "x-enum-varnames": [ + "New", + "Paid", + "Cancel" + ] + }, + "product.ProductStatus": { + "type": "string", + "enum": [ + "Active", + "Inactive" + ], + "x-enum-varnames": [ + "Active", + "Inactive" + ] + }, + "product.ProductType": { + "type": "string", + "enum": [ + "FOOD", + "BEVERAGE" + ], + "x-enum-varnames": [ + "Food", + "Beverage" + ] + }, + "request.Branch": { + "type": "object", + "required": [ + "location", + "name" + ], + "properties": { + "location": { + "type": "string" + }, + "name": { + "type": "string" + }, + "status": { + "$ref": "#/definitions/branch.BranchStatus" + } + } + }, + "request.LoginRequest": { + "type": "object", + "properties": { + "email": { + "type": "string" + }, + "password": { + "type": "string" + } + } + }, + "request.Order": { + "type": "object", + "required": [ + "amount", + "branch_id", + "customer_name", + "customer_phone", + "order_items", + "pax", + "payment_method" + ], + "properties": { + "amount": { + "type": "number" + }, + "branch_id": { + "type": "integer" + }, + "customer_name": { + "type": "string" + }, + "customer_phone": { + "type": "string" + }, + "order_items": { + "type": "array", + "items": { + "$ref": "#/definitions/request.OrderItem" + } + }, + "pax": { + "type": "integer" + }, + "payment_method": { + "$ref": "#/definitions/transaction.PaymentMethod" + } + } + }, + "request.OrderItem": { + "type": "object", + "required": [ + "item_id", + "item_type", + "price", + "qty" + ], + "properties": { + "item_id": { + "type": "integer" + }, + "item_type": { + "$ref": "#/definitions/order.ItemType" + }, + "price": { + "type": "number" + }, + "qty": { + "type": "integer" + } + } + }, + "request.Product": { + "type": "object", + "required": [ + "branch_id", + "name", + "price", + "status", + "type" + ], + "properties": { + "branch_id": { + "type": "integer" + }, + "description": { + "type": "string" + }, + "name": { + "type": "string" + }, + "price": { + "type": "number" + }, + "status": { + "$ref": "#/definitions/product.ProductStatus" + }, + "stock_qty": { + "type": "integer" + }, + "type": { + "$ref": "#/definitions/product.ProductType" + } + } + }, + "request.Studio": { + "type": "object", + "required": [ + "branch_id", + "name", + "price" + ], + "properties": { + "branch_id": { + "type": "integer" + }, + "metadata": { + "type": "object", + "additionalProperties": true + }, + "name": { + "type": "string" + }, + "price": { + "type": "number" + }, + "status": { + "$ref": "#/definitions/studio.StudioStatus" + } + } + }, + "request.UpdateStatus": { + "type": "object", + "properties": { + "status": { + "allOf": [ + { + "$ref": "#/definitions/order.OrderStatus" + } + ], + "example": "NEW,PAID,CANCEL" + } + } + }, + "request.User": { + "type": "object", + "required": [ + "email", + "name", + "password", + "role_id" + ], + "properties": { + "branch_id": { + "type": "integer" + }, + "email": { + "type": "string" + }, + "name": { + "type": "string" + }, + "password": { + "type": "string" + }, + "role_id": { + "type": "integer" + } + } + }, + "response.BaseResponse": { + "type": "object", + "properties": { + "data": {}, + "error_detail": {}, + "error_message": { + "type": "string" + }, + "message": { + "type": "string" + }, + "meta": { + "$ref": "#/definitions/response.PagingMeta" + }, + "response_code": { + "type": "string" + }, + "success": { + "type": "boolean" + } + } + }, + "response.Branch": { + "type": "object", + "properties": { + "created_at": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "location": { + "type": "string" + }, + "name": { + "type": "string" + }, + "status": { + "type": "string" + }, + "updated_at": { + "type": "string" + } + } + }, + "response.BranchList": { + "type": "object", + "properties": { + "branches": { + "type": "array", + "items": { + "$ref": "#/definitions/response.Branch" + } + }, + "limit": { + "type": "integer" + }, + "offset": { + "type": "integer" + }, + "total": { + "type": "integer" + } + } + }, + "response.LoginResponse": { + "type": "object", + "properties": { + "branch": { + "$ref": "#/definitions/response.Branch" + }, + "name": { + "type": "string" + }, + "role": { + "$ref": "#/definitions/response.Role" + }, + "token": { + "type": "string" + } + } + }, + "response.Order": { + "type": "object", + "properties": { + "amount": { + "type": "number" + }, + "branch_id": { + "type": "integer" + }, + "branch_name": { + "type": "string" + }, + "created_at": { + "type": "string" + }, + "customer_name": { + "type": "string" + }, + "customer_phone": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "order_items": { + "type": "array", + "items": { + "$ref": "#/definitions/response.OrderItem" + } + }, + "pax": { + "type": "integer" + }, + "payment_method": { + "$ref": "#/definitions/transaction.PaymentMethod" + }, + "status": { + "$ref": "#/definitions/order.OrderStatus" + }, + "updated_at": { + "type": "string" + } + } + }, + "response.OrderBranchRevenue": { + "type": "object", + "properties": { + "branch_id": { + "type": "string" + }, + "location": { + "type": "string" + }, + "name": { + "type": "string" + }, + "total_amount": { + "type": "number" + }, + "total_trans": { + "type": "integer" + } + } + }, + "response.OrderItem": { + "type": "object", + "properties": { + "created_at": { + "type": "string" + }, + "item_id": { + "type": "integer" + }, + "item_name": { + "type": "string" + }, + "item_type": { + "$ref": "#/definitions/order.ItemType" + }, + "order_item_id": { + "type": "integer" + }, + "price": { + "type": "number" + }, + "qty": { + "type": "integer" + }, + "updated_at": { + "type": "string" + } + } + }, + "response.OrderList": { + "type": "object", + "properties": { + "limit": { + "type": "integer" + }, + "offset": { + "type": "integer" + }, + "orders": { + "type": "array", + "items": { + "$ref": "#/definitions/response.Order" + } + }, + "total": { + "type": "integer" + } + } + }, + "response.OrderMonthlyRevenue": { + "type": "object", + "properties": { + "total_revenue": { + "type": "number" + }, + "total_transaction": { + "type": "integer" + } + } + }, + "response.PagingMeta": { + "type": "object", + "properties": { + "limit": { + "type": "integer" + }, + "page": { + "type": "integer" + }, + "total_data": { + "type": "integer" + } + } + }, + "response.Product": { + "type": "object", + "properties": { + "branch_id": { + "type": "integer" + }, + "created_at": { + "type": "string" + }, + "description": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "price": { + "type": "number" + }, + "status": { + "$ref": "#/definitions/product.ProductStatus" + }, + "stock_qty": { + "type": "integer" + }, + "type": { + "$ref": "#/definitions/product.ProductType" + }, + "updated_at": { + "type": "string" + } + } + }, + "response.ProductList": { + "type": "object", + "properties": { + "limit": { + "type": "integer" + }, + "offset": { + "type": "integer" + }, + "products": { + "type": "array", + "items": { + "$ref": "#/definitions/response.Product" + } + }, + "total": { + "type": "integer" + } + } + }, + "response.Role": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "role_name": { + "type": "string" + } + } + }, + "response.Studio": { + "type": "object", + "properties": { + "branch_id": { + "type": "integer" + }, + "created_at": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "metadata": { + "type": "object", + "additionalProperties": true + }, + "name": { + "type": "string" + }, + "price": { + "type": "number" + }, + "status": { + "type": "string" + }, + "updated_at": { + "type": "string" + } + } + }, + "response.StudioList": { + "type": "object", + "properties": { + "limit": { + "type": "integer" + }, + "offset": { + "type": "integer" + }, + "studios": { + "type": "array", + "items": { + "$ref": "#/definitions/response.Studio" + } + }, + "total": { + "type": "integer" + } + } + }, + "response.User": { + "type": "object", + "properties": { + "branch_id": { + "type": "integer" + }, + "branch_name": { + "type": "string" + }, + "created_at": { + "type": "string" + }, + "email": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "role_id": { + "type": "integer" + }, + "role_name": { + "type": "string" + }, + "status": { + "type": "string" + }, + "updated_at": { + "type": "string" + } + } + }, + "response.UserList": { + "type": "object", + "properties": { + "limit": { + "type": "integer" + }, + "offset": { + "type": "integer" + }, + "total": { + "type": "integer" + }, + "users": { + "type": "array", + "items": { + "$ref": "#/definitions/response.User" + } + } + } + }, + "studio.StudioStatus": { + "type": "string", + "enum": [ + "Active", + "Inactive" + ], + "x-enum-varnames": [ + "Active", + "Inactive" + ] + }, + "transaction.PaymentMethod": { + "type": "string", + "enum": [ + "CASH", + "DEBIT", + "TRANSFER", + "QRIS" + ], + "x-enum-varnames": [ + "Cash", + "Debit", + "Transfer", + "QRIS" + ] + } + } +} \ No newline at end of file diff --git a/docs/swagger.yaml b/docs/swagger.yaml new file mode 100644 index 0000000..726cecd --- /dev/null +++ b/docs/swagger.yaml @@ -0,0 +1,1833 @@ +definitions: + branch.BranchStatus: + enum: + - Active + - Inactive + type: string + x-enum-varnames: + - Active + - Inactive + entity.UploadFileResponse: + properties: + file_path: + type: string + file_url: + type: string + type: object + order.ItemType: + enum: + - PRODUCT + - STUDIO + type: string + x-enum-varnames: + - Product + - Studio + order.OrderStatus: + enum: + - NEW + - PAID + - CANCEL + type: string + x-enum-varnames: + - New + - Paid + - Cancel + product.ProductStatus: + enum: + - Active + - Inactive + type: string + x-enum-varnames: + - Active + - Inactive + product.ProductType: + enum: + - FOOD + - BEVERAGE + type: string + x-enum-varnames: + - Food + - Beverage + request.Branch: + properties: + location: + type: string + name: + type: string + status: + $ref: '#/definitions/branch.BranchStatus' + required: + - location + - name + type: object + request.LoginRequest: + properties: + email: + type: string + password: + type: string + type: object + request.Order: + properties: + amount: + type: number + branch_id: + type: integer + customer_name: + type: string + customer_phone: + type: string + order_items: + items: + $ref: '#/definitions/request.OrderItem' + type: array + pax: + type: integer + payment_method: + $ref: '#/definitions/transaction.PaymentMethod' + required: + - amount + - branch_id + - customer_name + - customer_phone + - order_items + - pax + - payment_method + type: object + request.OrderItem: + properties: + item_id: + type: integer + item_type: + $ref: '#/definitions/order.ItemType' + price: + type: number + qty: + type: integer + required: + - item_id + - item_type + - price + - qty + type: object + request.Product: + properties: + branch_id: + type: integer + description: + type: string + name: + type: string + price: + type: number + status: + $ref: '#/definitions/product.ProductStatus' + stock_qty: + type: integer + type: + $ref: '#/definitions/product.ProductType' + required: + - branch_id + - name + - price + - status + - type + type: object + request.Studio: + properties: + branch_id: + type: integer + metadata: + additionalProperties: true + type: object + name: + type: string + price: + type: number + status: + $ref: '#/definitions/studio.StudioStatus' + required: + - branch_id + - name + - price + type: object + request.UpdateStatus: + properties: + status: + allOf: + - $ref: '#/definitions/order.OrderStatus' + example: NEW,PAID,CANCEL + type: object + request.User: + properties: + branch_id: + type: integer + email: + type: string + name: + type: string + password: + type: string + role_id: + type: integer + required: + - email + - name + - password + - role_id + type: object + response.BaseResponse: + properties: + data: {} + error_detail: {} + error_message: + type: string + message: + type: string + meta: + $ref: '#/definitions/response.PagingMeta' + response_code: + type: string + success: + type: boolean + type: object + response.Branch: + properties: + created_at: + type: string + id: + type: integer + location: + type: string + name: + type: string + status: + type: string + updated_at: + type: string + type: object + response.BranchList: + properties: + branches: + items: + $ref: '#/definitions/response.Branch' + type: array + limit: + type: integer + offset: + type: integer + total: + type: integer + type: object + response.LoginResponse: + properties: + branch: + $ref: '#/definitions/response.Branch' + name: + type: string + role: + $ref: '#/definitions/response.Role' + token: + type: string + type: object + response.Order: + properties: + amount: + type: number + branch_id: + type: integer + branch_name: + type: string + created_at: + type: string + customer_name: + type: string + customer_phone: + type: string + id: + type: integer + order_items: + items: + $ref: '#/definitions/response.OrderItem' + type: array + pax: + type: integer + payment_method: + $ref: '#/definitions/transaction.PaymentMethod' + status: + $ref: '#/definitions/order.OrderStatus' + updated_at: + type: string + type: object + response.OrderBranchRevenue: + properties: + branch_id: + type: string + location: + type: string + name: + type: string + total_amount: + type: number + total_trans: + type: integer + type: object + response.OrderItem: + properties: + created_at: + type: string + item_id: + type: integer + item_name: + type: string + item_type: + $ref: '#/definitions/order.ItemType' + order_item_id: + type: integer + price: + type: number + qty: + type: integer + updated_at: + type: string + type: object + response.OrderList: + properties: + limit: + type: integer + offset: + type: integer + orders: + items: + $ref: '#/definitions/response.Order' + type: array + total: + type: integer + type: object + response.OrderMonthlyRevenue: + properties: + total_revenue: + type: number + total_transaction: + type: integer + type: object + response.PagingMeta: + properties: + limit: + type: integer + page: + type: integer + total_data: + type: integer + type: object + response.Product: + properties: + branch_id: + type: integer + created_at: + type: string + description: + type: string + id: + type: integer + name: + type: string + price: + type: number + status: + $ref: '#/definitions/product.ProductStatus' + stock_qty: + type: integer + type: + $ref: '#/definitions/product.ProductType' + updated_at: + type: string + type: object + response.ProductList: + properties: + limit: + type: integer + offset: + type: integer + products: + items: + $ref: '#/definitions/response.Product' + type: array + total: + type: integer + type: object + response.Role: + properties: + id: + type: integer + role_name: + type: string + type: object + response.Studio: + properties: + branch_id: + type: integer + created_at: + type: string + id: + type: integer + metadata: + additionalProperties: true + type: object + name: + type: string + price: + type: number + status: + type: string + updated_at: + type: string + type: object + response.StudioList: + properties: + limit: + type: integer + offset: + type: integer + studios: + items: + $ref: '#/definitions/response.Studio' + type: array + total: + type: integer + type: object + response.User: + properties: + branch_id: + type: integer + branch_name: + type: string + created_at: + type: string + email: + type: string + id: + type: integer + name: + type: string + role_id: + type: integer + role_name: + type: string + status: + type: string + updated_at: + type: string + type: object + response.UserList: + properties: + limit: + type: integer + offset: + type: integer + total: + type: integer + users: + items: + $ref: '#/definitions/response.User' + type: array + type: object + studio.StudioStatus: + enum: + - Active + - Inactive + type: string + x-enum-varnames: + - Active + - Inactive + transaction.PaymentMethod: + enum: + - CASH + - DEBIT + - TRANSFER + - QRIS + type: string + x-enum-varnames: + - Cash + - Debit + - Transfer + - QRIS +info: + contact: {} +paths: + /api/v1/auth/login: + post: + consumes: + - application/json + description: Authenticates a user based on the provided credentials and returns + a JWT token. + parameters: + - description: User login credentials + in: body + name: bodyParam + required: true + schema: + $ref: '#/definitions/request.LoginRequest' + produces: + - application/json + responses: + "200": + description: Login successful + schema: + allOf: + - $ref: '#/definitions/response.BaseResponse' + - properties: + data: + $ref: '#/definitions/response.LoginResponse' + type: object + "400": + description: Bad request + schema: + allOf: + - $ref: '#/definitions/response.BaseResponse' + - properties: + data: {} + type: object + "401": + description: Unauthorized + schema: + allOf: + - $ref: '#/definitions/response.BaseResponse' + - properties: + data: {} + type: object + summary: User login + tags: + - Auth Login API's + /api/v1/branch: + post: + consumes: + - application/json + description: Create a new branch based on the provided data. + parameters: + - description: JWT token + in: header + name: Authorization + required: true + type: string + - description: New branch details + in: body + name: req + required: true + schema: + $ref: '#/definitions/request.Branch' + produces: + - application/json + responses: + "200": + description: Branch created successfully + schema: + allOf: + - $ref: '#/definitions/response.BaseResponse' + - properties: + data: + $ref: '#/definitions/response.Branch' + type: object + "400": + description: Bad request + schema: + allOf: + - $ref: '#/definitions/response.BaseResponse' + - properties: + data: {} + type: object + "401": + description: Unauthorized + schema: + allOf: + - $ref: '#/definitions/response.BaseResponse' + - properties: + data: {} + type: object + summary: Create a new branch + tags: + - Branch APIs + /api/v1/branch/{id}: + delete: + consumes: + - application/json + description: Delete a branch based on the provided ID. + parameters: + - description: JWT token + in: header + name: Authorization + required: true + type: string + - description: Branch ID to delete + in: path + name: id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: Branch deleted successfully + schema: + $ref: '#/definitions/response.BaseResponse' + "400": + description: Bad request + schema: + allOf: + - $ref: '#/definitions/response.BaseResponse' + - properties: + data: {} + type: object + "401": + description: Unauthorized + schema: + allOf: + - $ref: '#/definitions/response.BaseResponse' + - properties: + data: {} + type: object + summary: Delete a branch by ID + tags: + - Branch APIs + get: + consumes: + - application/json + description: Get details of a branch based on the provided ID. + parameters: + - description: JWT token + in: header + name: Authorization + required: true + type: string + - description: Branch ID to retrieve + in: path + name: id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: Branch details + schema: + allOf: + - $ref: '#/definitions/response.BaseResponse' + - properties: + data: + $ref: '#/definitions/response.Branch' + type: object + "400": + description: Bad request + schema: + allOf: + - $ref: '#/definitions/response.BaseResponse' + - properties: + data: {} + type: object + "401": + description: Unauthorized + schema: + allOf: + - $ref: '#/definitions/response.BaseResponse' + - properties: + data: {} + type: object + summary: Get details of a branch by ID + tags: + - Branch APIs + put: + consumes: + - application/json + description: Update the details of an existing branch based on the provided + ID. + parameters: + - description: JWT token + in: header + name: Authorization + required: true + type: string + - description: Branch ID to update + in: path + name: id + required: true + type: integer + - description: Updated branch details + in: body + name: req + required: true + schema: + $ref: '#/definitions/request.Branch' + produces: + - application/json + responses: + "200": + description: Branch updated successfully + schema: + allOf: + - $ref: '#/definitions/response.BaseResponse' + - properties: + data: + $ref: '#/definitions/response.Branch' + type: object + "400": + description: Bad request + schema: + allOf: + - $ref: '#/definitions/response.BaseResponse' + - properties: + data: {} + type: object + "401": + description: Unauthorized + schema: + allOf: + - $ref: '#/definitions/response.BaseResponse' + - properties: + data: {} + type: object + summary: Update an existing branch + tags: + - Branch APIs + /api/v1/branch/list: + get: + consumes: + - application/json + description: Get a paginated list of branches based on query parameters. + parameters: + - description: JWT token + in: header + name: Authorization + required: true + type: string + - description: Number of items to retrieve (default 10) + in: query + name: Limit + type: integer + - description: Offset for pagination (default 0) + in: query + name: Offset + type: integer + produces: + - application/json + responses: + "200": + description: List of branches + schema: + allOf: + - $ref: '#/definitions/response.BaseResponse' + - properties: + data: + $ref: '#/definitions/response.BranchList' + type: object + "400": + description: Bad request + schema: + allOf: + - $ref: '#/definitions/response.BaseResponse' + - properties: + data: {} + type: object + "401": + description: Unauthorized + schema: + allOf: + - $ref: '#/definitions/response.BaseResponse' + - properties: + data: {} + type: object + summary: Get a list of branches + tags: + - Branch APIs + /api/v1/file/upload: + post: + consumes: + - multipart/form-data + description: Upload a file to Alibaba Cloud OSS with the provided details. + parameters: + - description: JWT token + in: header + name: Authorization + required: true + type: string + - description: 'File to upload (max size: 2MB)' + in: formData + name: file + required: true + type: file + produces: + - application/json + responses: + "200": + description: File uploaded successfully + schema: + allOf: + - $ref: '#/definitions/response.BaseResponse' + - properties: + data: + $ref: '#/definitions/entity.UploadFileResponse' + type: object + "400": + description: Bad request + schema: + allOf: + - $ref: '#/definitions/response.BaseResponse' + - properties: + data: {} + type: object + "401": + description: Unauthorized + schema: + allOf: + - $ref: '#/definitions/response.BaseResponse' + - properties: + data: {} + type: object + summary: Upload a file to OSS + tags: + - File Upload API + /api/v1/order: + post: + consumes: + - application/json + description: Create a new order with the provided details. + parameters: + - description: JWT token + in: header + name: Authorization + required: true + type: string + - description: Order details + in: body + name: order + required: true + schema: + $ref: '#/definitions/request.Order' + produces: + - application/json + responses: + "200": + description: Order created successfully + schema: + $ref: '#/definitions/response.BaseResponse' + "400": + description: Bad request + schema: + allOf: + - $ref: '#/definitions/response.BaseResponse' + - properties: + data: {} + type: object + "401": + description: Unauthorized + schema: + allOf: + - $ref: '#/definitions/response.BaseResponse' + - properties: + data: {} + type: object + summary: Create a new order + /api/v1/order/{id}: + get: + consumes: + - application/json + description: Retrieve the details of the specified order by ID. + parameters: + - description: JWT token + in: header + name: Authorization + required: true + type: string + - description: Order ID + in: path + name: id + required: true + type: string + produces: + - application/json + responses: + "200": + description: Order details retrieved successfully + schema: + allOf: + - $ref: '#/definitions/response.BaseResponse' + - properties: + data: + $ref: '#/definitions/response.Order' + type: object + "400": + description: Bad request + schema: + allOf: + - $ref: '#/definitions/response.BaseResponse' + - properties: + data: {} + type: object + "401": + description: Unauthorized + schema: + allOf: + - $ref: '#/definitions/response.BaseResponse' + - properties: + data: {} + type: object + "500": + description: Internal server error + schema: + $ref: '#/definitions/response.BaseResponse' + summary: Get details of an order by ID + /api/v1/order/branch-revenue: + get: + consumes: + - application/json + description: Retrieve the branch-wise revenue for orders based on the specified + parameters. + parameters: + - description: JWT token + in: header + name: Authorization + required: true + type: string + - description: Branch ID for filtering + in: query + name: branch_id + type: integer + - description: 'Start date for filtering (format: ''YYYY-MM-DD'')' + in: query + name: start_date + type: string + - description: 'End date for filtering (format: ''YYYY-MM-DD'')' + in: query + name: end_date + type: string + produces: + - application/json + responses: + "200": + description: Branch-wise revenue retrieved successfully + schema: + allOf: + - $ref: '#/definitions/response.BaseResponse' + - properties: + data: + items: + $ref: '#/definitions/response.OrderBranchRevenue' + type: array + type: object + "400": + description: Bad request + schema: + allOf: + - $ref: '#/definitions/response.BaseResponse' + - properties: + data: {} + type: object + "401": + description: Unauthorized + schema: + allOf: + - $ref: '#/definitions/response.BaseResponse' + - properties: + data: {} + type: object + "500": + description: Internal server error + schema: + $ref: '#/definitions/response.BaseResponse' + summary: Get branch-wise revenue for orders + /api/v1/order/list: + get: + consumes: + - application/json + description: Retrieve a list of orders based on the specified parameters. + parameters: + - description: JWT token + in: header + name: Authorization + required: true + type: string + - description: 'Number of items to retrieve (default: 10)' + in: query + name: limit + type: integer + - description: 'Number of items to skip (default: 0)' + in: query + name: offset + type: integer + produces: + - application/json + responses: + "200": + description: List of orders retrieved successfully + schema: + allOf: + - $ref: '#/definitions/response.BaseResponse' + - properties: + data: + $ref: '#/definitions/response.OrderList' + type: object + "400": + description: Bad request + schema: + allOf: + - $ref: '#/definitions/response.BaseResponse' + - properties: + data: {} + type: object + "401": + description: Unauthorized + schema: + allOf: + - $ref: '#/definitions/response.BaseResponse' + - properties: + data: {} + type: object + "500": + description: Internal server error + schema: + $ref: '#/definitions/response.BaseResponse' + summary: Get a list of orders + /api/v1/order/total-revenue: + get: + consumes: + - application/json + description: Retrieve the total revenue and number of transactions for orders + based on the specified parameters. + parameters: + - description: JWT token + in: header + name: Authorization + required: true + type: string + - description: 'Start date for filtering (format: ''YYYY-MM-DD'')' + in: query + name: start_date + type: string + - description: 'End date for filtering (format: ''YYYY-MM-DD'')' + in: query + name: end_date + type: string + produces: + - application/json + responses: + "200": + description: Total revenue and transactions retrieved successfully + schema: + allOf: + - $ref: '#/definitions/response.BaseResponse' + - properties: + data: + $ref: '#/definitions/response.OrderMonthlyRevenue' + type: object + "400": + description: Bad request + schema: + allOf: + - $ref: '#/definitions/response.BaseResponse' + - properties: + data: {} + type: object + "401": + description: Unauthorized + schema: + allOf: + - $ref: '#/definitions/response.BaseResponse' + - properties: + data: {} + type: object + "500": + description: Internal server error + schema: + $ref: '#/definitions/response.BaseResponse' + summary: Get total revenue and number of transactions for orders + /api/v1/order/update-status/{id}: + put: + consumes: + - application/json + description: Update the status of the specified order with the provided details. + parameters: + - description: JWT token + in: header + name: Authorization + required: true + type: string + - description: Order ID + in: path + name: id + required: true + type: string + - description: Status details + in: body + name: status + required: true + schema: + $ref: '#/definitions/request.UpdateStatus' + produces: + - application/json + responses: + "200": + description: Order status updated successfully + schema: + allOf: + - $ref: '#/definitions/response.BaseResponse' + - properties: + data: + $ref: '#/definitions/response.Order' + type: object + "400": + description: Bad request + schema: + allOf: + - $ref: '#/definitions/response.BaseResponse' + - properties: + data: {} + type: object + "401": + description: Unauthorized + schema: + allOf: + - $ref: '#/definitions/response.BaseResponse' + - properties: + data: {} + type: object + "500": + description: Internal server error + schema: + $ref: '#/definitions/response.BaseResponse' + summary: Update the status of an order + /api/v1/order/yearly-revenue/{year}: + get: + consumes: + - application/json + description: Retrieve the yearly revenue for orders based on the specified year. + parameters: + - description: JWT token + in: header + name: Authorization + required: true + type: string + - description: Year for filtering + in: path + name: year + required: true + type: integer + produces: + - application/json + responses: + "200": + description: Yearly revenue retrieved successfully + schema: + allOf: + - $ref: '#/definitions/response.BaseResponse' + - properties: + data: + additionalProperties: + additionalProperties: + type: number + type: object + type: object + type: object + "400": + description: Bad request + schema: + allOf: + - $ref: '#/definitions/response.BaseResponse' + - properties: + data: {} + type: object + "401": + description: Unauthorized + schema: + allOf: + - $ref: '#/definitions/response.BaseResponse' + - properties: + data: {} + type: object + "500": + description: Internal server error + schema: + $ref: '#/definitions/response.BaseResponse' + summary: Get yearly revenue for orders + /api/v1/product/: + post: + consumes: + - application/json + description: Create a new product with the provided details. + parameters: + - description: JWT token + in: header + name: Authorization + required: true + type: string + - description: Product details to create + in: body + name: req + required: true + schema: + $ref: '#/definitions/request.Product' + produces: + - application/json + responses: + "200": + description: Product created successfully + schema: + allOf: + - $ref: '#/definitions/response.BaseResponse' + - properties: + data: + $ref: '#/definitions/response.Product' + type: object + "400": + description: Bad request + schema: + allOf: + - $ref: '#/definitions/response.BaseResponse' + - properties: + data: {} + type: object + "401": + description: Unauthorized + schema: + allOf: + - $ref: '#/definitions/response.BaseResponse' + - properties: + data: {} + type: object + summary: Create a new product + tags: + - Product APIs + /api/v1/product/{id}: + delete: + consumes: + - application/json + description: Delete a product based on the provided ID. + parameters: + - description: JWT token + in: header + name: Authorization + required: true + type: string + - description: Product ID to delete + in: path + name: id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: Product deleted successfully + schema: + $ref: '#/definitions/response.BaseResponse' + "400": + description: Bad request + schema: + allOf: + - $ref: '#/definitions/response.BaseResponse' + - properties: + data: {} + type: object + "401": + description: Unauthorized + schema: + allOf: + - $ref: '#/definitions/response.BaseResponse' + - properties: + data: {} + type: object + summary: Delete a product by ID + tags: + - Product APIs + get: + consumes: + - application/json + description: Get details of a product based on the provided ID. + parameters: + - description: JWT token + in: header + name: Authorization + required: true + type: string + - description: Product ID to retrieve + in: path + name: id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: Product details + schema: + allOf: + - $ref: '#/definitions/response.BaseResponse' + - properties: + data: + $ref: '#/definitions/response.Product' + type: object + "400": + description: Bad request + schema: + allOf: + - $ref: '#/definitions/response.BaseResponse' + - properties: + data: {} + type: object + "401": + description: Unauthorized + schema: + allOf: + - $ref: '#/definitions/response.BaseResponse' + - properties: + data: {} + type: object + summary: Get details of a product by ID + tags: + - Product APIs + put: + consumes: + - application/json + description: Update the details of an existing product based on the provided + ID. + parameters: + - description: JWT token + in: header + name: Authorization + required: true + type: string + - description: Product ID to update + in: path + name: id + required: true + type: integer + - description: Updated product details + in: body + name: req + required: true + schema: + $ref: '#/definitions/request.Product' + produces: + - application/json + responses: + "200": + description: Product updated successfully + schema: + allOf: + - $ref: '#/definitions/response.BaseResponse' + - properties: + data: + $ref: '#/definitions/response.Product' + type: object + "400": + description: Bad request + schema: + allOf: + - $ref: '#/definitions/response.BaseResponse' + - properties: + data: {} + type: object + "401": + description: Unauthorized + schema: + allOf: + - $ref: '#/definitions/response.BaseResponse' + - properties: + data: {} + type: object + summary: Update an existing product + tags: + - Product APIs + /api/v1/product/list: + get: + consumes: + - application/json + description: Get a paginated list of products based on query parameters. + parameters: + - description: JWT token + in: header + name: Authorization + required: true + type: string + - description: Number of items to retrieve (default 10) + in: query + name: Limit + type: integer + - description: Offset for pagination (default 0) + in: query + name: Offset + type: integer + produces: + - application/json + responses: + "200": + description: List of products + schema: + allOf: + - $ref: '#/definitions/response.BaseResponse' + - properties: + data: + $ref: '#/definitions/response.ProductList' + type: object + "400": + description: Bad request + schema: + allOf: + - $ref: '#/definitions/response.BaseResponse' + - properties: + data: {} + type: object + "401": + description: Unauthorized + schema: + allOf: + - $ref: '#/definitions/response.BaseResponse' + - properties: + data: {} + type: object + summary: Get a list of products + tags: + - Product APIs + /api/v1/studio: + post: + consumes: + - application/json + description: Create a new studio based on the provided details. + parameters: + - description: JWT token + in: header + name: Authorization + required: true + type: string + - description: New studio details + in: body + name: req + required: true + schema: + $ref: '#/definitions/request.Studio' + produces: + - application/json + responses: + "200": + description: Studio created successfully + schema: + allOf: + - $ref: '#/definitions/response.BaseResponse' + - properties: + data: + $ref: '#/definitions/response.Studio' + type: object + "400": + description: Bad request + schema: + allOf: + - $ref: '#/definitions/response.BaseResponse' + - properties: + data: {} + type: object + "401": + description: Unauthorized + schema: + allOf: + - $ref: '#/definitions/response.BaseResponse' + - properties: + data: {} + type: object + summary: Create a new studio + tags: + - Studio APIs + /api/v1/studio/{id}: + get: + consumes: + - application/json + description: Get details of a studio based on the provided ID. + parameters: + - description: JWT token + in: header + name: Authorization + required: true + type: string + - description: Studio ID to retrieve + in: path + name: id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: Studio details + schema: + allOf: + - $ref: '#/definitions/response.BaseResponse' + - properties: + data: + $ref: '#/definitions/response.Studio' + type: object + "400": + description: Bad request + schema: + allOf: + - $ref: '#/definitions/response.BaseResponse' + - properties: + data: {} + type: object + "401": + description: Unauthorized + schema: + allOf: + - $ref: '#/definitions/response.BaseResponse' + - properties: + data: {} + type: object + summary: Get details of a studio by ID + tags: + - Studio APIs + put: + consumes: + - application/json + description: Update the details of an existing studio based on the provided + ID. + parameters: + - description: JWT token + in: header + name: Authorization + required: true + type: string + - description: Studio ID to update + in: path + name: id + required: true + type: integer + - description: Updated studio details + in: body + name: req + required: true + schema: + $ref: '#/definitions/request.Studio' + produces: + - application/json + responses: + "200": + description: Studio updated successfully + schema: + allOf: + - $ref: '#/definitions/response.BaseResponse' + - properties: + data: + $ref: '#/definitions/response.Studio' + type: object + "400": + description: Bad request + schema: + allOf: + - $ref: '#/definitions/response.BaseResponse' + - properties: + data: {} + type: object + "401": + description: Unauthorized + schema: + allOf: + - $ref: '#/definitions/response.BaseResponse' + - properties: + data: {} + type: object + summary: Update an existing studio + tags: + - Studio APIs + /api/v1/studio/search: + get: + consumes: + - application/json + description: Search for studios based on query parameters. + parameters: + - description: JWT token + in: header + name: Authorization + required: true + type: string + - description: Studio name for search + in: query + name: Name + type: string + - description: Studio status for search + in: query + name: Status + type: string + - description: Number of items to retrieve (default 10) + in: query + name: Limit + type: integer + - description: Offset for pagination (default 0) + in: query + name: Offset + type: integer + produces: + - application/json + responses: + "200": + description: List of studios + schema: + allOf: + - $ref: '#/definitions/response.BaseResponse' + - properties: + data: + $ref: '#/definitions/response.StudioList' + type: object + "400": + description: Bad request + schema: + allOf: + - $ref: '#/definitions/response.BaseResponse' + - properties: + data: {} + type: object + "401": + description: Unauthorized + schema: + allOf: + - $ref: '#/definitions/response.BaseResponse' + - properties: + data: {} + type: object + summary: Search for studios + tags: + - Studio APIs + /api/v1/user: + post: + consumes: + - application/json + description: Create a new user based on the provided data. + parameters: + - description: JWT token + in: header + name: Authorization + required: true + type: string + - description: New user details + in: body + name: req + required: true + schema: + $ref: '#/definitions/request.User' + produces: + - application/json + responses: + "200": + description: User created successfully + schema: + allOf: + - $ref: '#/definitions/response.BaseResponse' + - properties: + data: + $ref: '#/definitions/response.User' + type: object + "400": + description: Bad request + schema: + allOf: + - $ref: '#/definitions/response.BaseResponse' + - properties: + data: {} + type: object + "401": + description: Unauthorized + schema: + allOf: + - $ref: '#/definitions/response.BaseResponse' + - properties: + data: {} + type: object + summary: Create a new user + tags: + - User APIs + /api/v1/user/{id}: + delete: + consumes: + - application/json + description: Delete a user based on the provided ID. + parameters: + - description: JWT token + in: header + name: Authorization + required: true + type: string + - description: User ID to delete + in: path + name: id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: User deleted successfully + schema: + $ref: '#/definitions/response.BaseResponse' + "400": + description: Bad request + schema: + allOf: + - $ref: '#/definitions/response.BaseResponse' + - properties: + data: {} + type: object + "401": + description: Unauthorized + schema: + allOf: + - $ref: '#/definitions/response.BaseResponse' + - properties: + data: {} + type: object + summary: Delete a user by ID + tags: + - User APIs + get: + consumes: + - application/json + description: Get details of a user based on the provided ID. + parameters: + - description: JWT token + in: header + name: Authorization + required: true + type: string + - description: User ID to retrieve + in: path + name: id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: User details + schema: + allOf: + - $ref: '#/definitions/response.BaseResponse' + - properties: + data: + $ref: '#/definitions/response.User' + type: object + "400": + description: Bad request + schema: + allOf: + - $ref: '#/definitions/response.BaseResponse' + - properties: + data: {} + type: object + "401": + description: Unauthorized + schema: + allOf: + - $ref: '#/definitions/response.BaseResponse' + - properties: + data: {} + type: object + summary: Get details of a user by ID + tags: + - User APIs + put: + consumes: + - application/json + description: Update the details of an existing user based on the provided ID. + parameters: + - description: JWT token + in: header + name: Authorization + required: true + type: string + - description: User ID to update + in: path + name: id + required: true + type: integer + - description: Updated user details + in: body + name: req + required: true + schema: + $ref: '#/definitions/request.User' + produces: + - application/json + responses: + "200": + description: User updated successfully + schema: + allOf: + - $ref: '#/definitions/response.BaseResponse' + - properties: + data: + $ref: '#/definitions/response.User' + type: object + "400": + description: Bad request + schema: + allOf: + - $ref: '#/definitions/response.BaseResponse' + - properties: + data: {} + type: object + "401": + description: Unauthorized + schema: + allOf: + - $ref: '#/definitions/response.BaseResponse' + - properties: + data: {} + type: object + summary: Update an existing user + tags: + - User APIs + /api/v1/user/list: + get: + consumes: + - application/json + description: Get a paginated list of users based on query parameters. + parameters: + - description: JWT token + in: header + name: Authorization + required: true + type: string + - description: Number of items to retrieve (default 10) + in: query + name: Limit + type: integer + - description: Offset for pagination (default 0) + in: query + name: Offset + type: integer + produces: + - application/json + responses: + "200": + description: List of users + schema: + allOf: + - $ref: '#/definitions/response.BaseResponse' + - properties: + data: + $ref: '#/definitions/response.UserList' + type: object + "400": + description: Bad request + schema: + allOf: + - $ref: '#/definitions/response.BaseResponse' + - properties: + data: {} + type: object + "401": + description: Unauthorized + schema: + allOf: + - $ref: '#/definitions/response.BaseResponse' + - properties: + data: {} + type: object + summary: Get a list of users + tags: + - User APIs +swagger: "2.0" diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..9a7281b --- /dev/null +++ b/go.mod @@ -0,0 +1,94 @@ +module furtuna-be + +go 1.19 + +require ( + github.com/aliyun/aliyun-oss-go-sdk v2.2.8+incompatible + github.com/gin-gonic/gin v1.9.1 + github.com/go-playground/validator/v10 v10.17.0 + github.com/gofrs/uuid v4.2.0+incompatible + github.com/golang-jwt/jwt v3.2.2+incompatible + github.com/google/uuid v1.1.2 + github.com/hashicorp/go-multierror v1.1.1 + github.com/jackc/pgconn v1.10.1 + github.com/jackc/pgerrcode v0.0.0-20201024163028-a0d42d470451 + github.com/lib/pq v1.2.0 + github.com/spf13/viper v1.16.0 + golang.org/x/crypto v0.18.0 + gopkg.in/yaml.v3 v3.0.1 +) + +require ( + github.com/KyleBanks/depth v1.2.1 // indirect + github.com/PuerkitoBio/purell v1.2.1 // indirect + github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect + github.com/bytedance/sonic v1.10.2 // indirect + github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect + github.com/chenzhuoyu/iasm v0.9.1 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect + github.com/fsnotify/fsnotify v1.6.0 // indirect + github.com/gabriel-vasile/mimetype v1.4.3 // indirect + github.com/gin-contrib/sse v0.1.0 // indirect + github.com/go-openapi/jsonpointer v0.20.2 // indirect + github.com/go-openapi/jsonreference v0.20.4 // indirect + github.com/go-openapi/spec v0.20.14 // indirect + github.com/go-openapi/swag v0.22.8 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/goccy/go-json v0.10.2 // indirect + github.com/hashicorp/errwrap v1.0.0 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/jackc/chunkreader/v2 v2.0.1 // indirect + github.com/jackc/pgio v1.0.0 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgproto3/v2 v2.1.1 // indirect + github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect + github.com/jackc/pgx/v5 v5.2.0 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect + github.com/jmespath/go-jmespath v0.4.0 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.2.6 // indirect + github.com/leodido/go-urn v1.2.4 // indirect + github.com/magiconair/properties v1.8.7 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pelletier/go-toml/v2 v2.1.1 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect + github.com/spf13/afero v1.9.5 // indirect + github.com/spf13/cast v1.5.1 // indirect + github.com/spf13/jwalterweatherman v1.1.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/subosito/gotenv v1.4.2 // indirect + github.com/swaggo/files v1.0.1 // indirect + github.com/swaggo/gin-swagger v1.6.0 // indirect + github.com/swaggo/swag v1.16.2 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.2.12 // indirect + github.com/urfave/cli/v2 v2.27.1 // indirect + github.com/xrash/smetrics v0.0.0-20231213231151-1d8dd44e695e // indirect + go.uber.org/atomic v1.10.0 // indirect + go.uber.org/multierr v1.8.0 // indirect + golang.org/x/arch v0.7.0 // indirect + golang.org/x/sys v0.16.0 // indirect + golang.org/x/text v0.14.0 // indirect + golang.org/x/time v0.1.0 // indirect + golang.org/x/tools v0.17.0 // indirect + google.golang.org/protobuf v1.32.0 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + sigs.k8s.io/yaml v1.4.0 // indirect +) + +require ( + github.com/aws/aws-sdk-go v1.50.0 + go.uber.org/zap v1.21.0 + golang.org/x/net v0.20.0 // indirect + gorm.io/driver/postgres v1.4.6 + gorm.io/gorm v1.24.5 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..318a640 --- /dev/null +++ b/go.sum @@ -0,0 +1,797 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= +github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= +github.com/PuerkitoBio/purell v1.2.1 h1:QsZ4TjvwiMpat6gBCBxEQI0rcS9ehtkKtSpiUnd9N28= +github.com/PuerkitoBio/purell v1.2.1/go.mod h1:ZwHcC/82TOaovDi//J/804umJFFmbOHPngi8iYYv/Eo= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/aliyun/aliyun-oss-go-sdk v2.2.8+incompatible h1:6JF1bjhT0WN2srEmijfOFtVWwV91KZ6dJY1/JbdtGrI= +github.com/aliyun/aliyun-oss-go-sdk v2.2.8+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8= +github.com/aws/aws-sdk-go v1.50.0 h1:HBtrLeO+QyDKnc3t1+5DR1RxodOHCGr8ZcrHudpv7jI= +github.com/aws/aws-sdk-go v1.50.0/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= +github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= +github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= +github.com/bytedance/sonic v1.8.0 h1:ea0Xadu+sHlu7x5O3gKhRpQ1IKiMrSiHttPF0ybECuA= +github.com/bytedance/sonic v1.8.0/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= +github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM= +github.com/bytedance/sonic v1.10.2 h1:GQebETVBxYB7JGWJtLBi07OVzWwt+8dWA00gEVW2ZFE= +github.com/bytedance/sonic v1.10.2/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= +github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= +github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= +github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0= +github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA= +github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog= +github.com/chenzhuoyu/iasm v0.9.1 h1:tUHQJXo3NhBqw6s33wkGn9SP3bvrWLdlVIJ3hQBL7P0= +github.com/chenzhuoyu/iasm v0.9.1/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM= +github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= +github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= +github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= +github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= +github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.9.0 h1:OjyFBKICoexlu99ctXNR2gg+c5pKrKMuyjgARg9qeY8= +github.com/gin-gonic/gin v1.9.0/go.mod h1:W1Me9+hsUSyj3CePGrd1/QrKJMSJ1Tu/0hFEH89961k= +github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= +github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-openapi/jsonpointer v0.20.2 h1:mQc3nmndL8ZBzStEo3JYF8wzmeWffDH4VbXz58sAx6Q= +github.com/go-openapi/jsonpointer v0.20.2/go.mod h1:bHen+N0u1KEO3YlmqOjTT9Adn1RfD91Ar825/PuiRVs= +github.com/go-openapi/jsonreference v0.20.4 h1:bKlDxQxQJgwpUSgOENiMPzCTBVuc7vTdXSSgNeAhojU= +github.com/go-openapi/jsonreference v0.20.4/go.mod h1:5pZJyJP2MnYCpoeoMAql78cCHauHj0V9Lhc506VOpw4= +github.com/go-openapi/spec v0.20.14 h1:7CBlRnw+mtjFGlPDRZmAMnq35cRzI91xj03HVyUi/Do= +github.com/go-openapi/spec v0.20.14/go.mod h1:8EOhTpBoFiask8rrgwbLC3zmJfz4zsCUueRuPM6GNkw= +github.com/go-openapi/swag v0.22.8 h1:/9RjDSQ0vbFR+NyjGMkFTsA1IA0fmhKSThmfGZjicbw= +github.com/go-openapi/swag v0.22.8/go.mod h1:6QT22icPLEqAM/z/TChgb4WAveCHF92+2gF0CNjHpPI= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.15.1 h1:BSe8uhN+xQ4r5guV/ywQI4gO59C2raYcGffYWZEjZzM= +github.com/go-playground/validator/v10 v10.15.1/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= +github.com/go-playground/validator/v10 v10.17.0 h1:SmVVlfAOtlZncTxRuinDPomC2DkXJ4E5T9gDA0AIH74= +github.com/go-playground/validator/v10 v10.17.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/goccy/go-json v0.10.0 h1:mXKd9Qw4NuzShiRlOXKews24ufknHO7gx30lsDyokKA= +github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= +github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/gofrs/uuid v4.2.0+incompatible h1:yyYWMnhkhrKwwr8gAOcOCYxOOscHgDS9yZgBrnJfGa0= +github.com/gofrs/uuid v4.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= +github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= +github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= +github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= +github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= +github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= +github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= +github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= +github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= +github.com/jackc/pgconn v1.10.1 h1:DzdIHIjG1AxGwoEEqS+mGsURyjt4enSmqzACXvVzOT8= +github.com/jackc/pgconn v1.10.1/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= +github.com/jackc/pgerrcode v0.0.0-20201024163028-a0d42d470451 h1:WAvSpGf7MsFuzAtK4Vk7R4EVe+liW4x83r4oWu0WHKw= +github.com/jackc/pgerrcode v0.0.0-20201024163028-a0d42d470451/go.mod h1:a/s9Lp5W7n/DD0VrVoyJ00FbP2ytTPDVOivvn2bMlds= +github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= +github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= +github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= +github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c= +github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc= +github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= +github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.1.1 h1:7PQ/4gLoqnl87ZxL7xjO0DR5gYuviDCZxQJsUlFW1eI= +github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= +github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= +github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= +github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= +github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= +github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= +github.com/jackc/pgx/v5 v5.2.0 h1:NdPpngX0Y6z6XDFKqmFQaE+bCtkqzvQIOt1wvBlAqs8= +github.com/jackc/pgx/v5 v5.2.0/go.mod h1:Ptn7zmohNsWEsdxRawMzk3gaKma2obW+NWTnKa0S4nk= +github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle/v2 v2.1.2/go.mod h1:2lpufsF5mRHO6SuZkm0fNYxM6SWHfvyFj62KwNzgels= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= +github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc= +github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= +github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= +github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0= +github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= +github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= +github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= +github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= +github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI= +github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= +github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= +github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= +github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM= +github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= +github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA= +github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48= +github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= +github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.16.0 h1:rGGH0XDZhdUOryiDWjmIvUSWpbNqisK8Wk0Vyefw8hc= +github.com/spf13/viper v1.16.0/go.mod h1:yg78JgCJcbrQOvV9YLXgkLaZqUidkY9K+Dd1FofRzQg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= +github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= +github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= +github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE= +github.com/swaggo/files v1.0.1/go.mod h1:0qXmMNH6sXNf+73t65aKeB+ApmgxdnkQzVTAj2uaMUg= +github.com/swaggo/gin-swagger v1.6.0 h1:y8sxvQ3E20/RCyrXeFfg60r6H0Z+SwpTjMYsMm+zy8M= +github.com/swaggo/gin-swagger v1.6.0/go.mod h1:BG00cCEy294xtVpyIAHG6+e2Qzj/xKlRdOqDkvq0uzo= +github.com/swaggo/swag v1.16.2 h1:28Pp+8DkQoV+HLzLx8RGJZXNGKbFqnuvSbAAtoxiY04= +github.com/swaggo/swag v1.16.2/go.mod h1:6YzXnDcpr0767iOejs318CwYkCQqyGer6BizOg03f+E= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go/codec v1.2.9 h1:rmenucSohSTiyL09Y+l2OCk+FrMxGMzho2+tjr5ticU= +github.com/ugorji/go/codec v1.2.9/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= +github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +github.com/urfave/cli/v2 v2.27.1 h1:8xSQ6szndafKVRmfyeUMxkNUJQMjL1F2zmsZ+qHpfho= +github.com/urfave/cli/v2 v2.27.1/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ= +github.com/xrash/smetrics v0.0.0-20231213231151-1d8dd44e695e h1:+SOyEddqYF09QP7vr7CgJ1eti3pY9Fn3LHO1M1r/0sI= +github.com/xrash/smetrics v0.0.0-20231213231151-1d8dd44e695e/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= +go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= +go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8= +go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= +go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8= +go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/arch v0.7.0 h1:pskyeJh/3AmoQ8CPE95vxHLqp1G1GfGNXTmcl9NEKTc= +golang.org/x/arch v0.7.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.4.0/go.mod h1:3quD/ATkf6oY+rnes5c3ExXTbLc8mueNue5/DoinL80= +golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= +golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= +golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= +golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= +golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220923202941-7f9b1623fab7/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.1.0 h1:xYY+Bajn2a7VBmTM5GikTmnK8ZuX8YgnQCqZpbBNtmA= +golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc= +golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= +golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= +google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/driver/postgres v1.4.6 h1:1FPESNXqIKG5JmraaH2bfCVlMQ7paLoCreFxDtqzwdc= +gorm.io/driver/postgres v1.4.6/go.mod h1:UJChCNLFKeBqQRE+HrkFUbKbq9idPXmTOk2u4Wok8S4= +gorm.io/gorm v1.24.2/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA= +gorm.io/gorm v1.24.5 h1:g6OPREKqqlWq4kh/3MCQbZKImeB9e6Xgc4zD+JgNZGE= +gorm.io/gorm v1.24.5/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= +sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/infra/fortuna.development.yaml b/infra/fortuna.development.yaml new file mode 100644 index 0000000..b53ecfe --- /dev/null +++ b/infra/fortuna.development.yaml @@ -0,0 +1,30 @@ +server: + base-url: https://api.furtuna.id/core + local-url: http://localhost:3300 + port: 3300 + +jwt: + token: + expires-ttl: 1440 + secret: "5Lm25V3Qd7aut8dr4QUxm5PZUrSFs" + +postgresql: + host: 103.96.146.124 + port: 1960 + driver: postgres + db: fortuna-staging + username: fortuna_admin + password: 'Z4G827t9428QFQ^SZXW#43dB%!4Bmh80' + ssl-mode: disable + max-idle-connections-in-second: 600 + max-open-connections-in-second: 600 + connection-max-life-time-in-second: 600 + debug: false + +oss: + access_key_id: e50b31e5eddf63c0ZKB2 + access_key_secret: GAyX9jiCWyTwgJMuqzun2x0zHS3kjQt26kyzY21S + endpoint: obs.eranyacloud.com + bucket_name: furtuna-dev + log_level: Error # type: LogOff, Debug, Error, Warn, Info + host_url: https://obs.eranyacloud.com \ No newline at end of file diff --git a/internal/app/server.go b/internal/app/server.go new file mode 100644 index 0000000..b3ea9d9 --- /dev/null +++ b/internal/app/server.go @@ -0,0 +1,48 @@ +package app + +import ( + "fmt" + + "github.com/gin-gonic/gin" + "github.com/gofrs/uuid" + + "furtuna-be/internal/middlewares" +) + +func NewServer() *Server { + gin.SetMode(gin.ReleaseMode) + + server := &Server{ + gin.New(), + } + + server.Use(middlewares.Cors()) + server.Use(middlewares.LogCorsError()) + server.Use(middlewares.Trace()) + server.Use(middlewares.Logger()) + server.Use(middlewares.RequestMiddleware()) + + return server +} + +type Server struct { + *gin.Engine +} + +func (*Server) GenerateUUID() (string, error) { + id, err := uuid.NewV4() + if err != nil { + return "", err + } + + return id.String(), nil +} + +func (s Server) Listen(address string) error { + fmt.Printf("API server listening at: %s\n\n", address) + return s.Run(address) +} + +func (s Server) StartScheduler() { + fmt.Printf("Scheduler started\n") +} diff --git a/internal/common/database/database.go b/internal/common/database/database.go new file mode 100644 index 0000000..a227844 --- /dev/null +++ b/internal/common/database/database.go @@ -0,0 +1,5 @@ +package database + +type Config interface { + ConnString() string +} diff --git a/internal/common/db/database.go b/internal/common/db/database.go new file mode 100644 index 0000000..7e3fa7d --- /dev/null +++ b/internal/common/db/database.go @@ -0,0 +1,52 @@ +package db + +import ( + "fmt" + + _ "github.com/lib/pq" + "go.uber.org/zap" + _ "gopkg.in/yaml.v3" + "gorm.io/driver/postgres" + "gorm.io/gorm" + + "furtuna-be/config" +) + +func NewPostgres(c config.Database) (*gorm.DB, error) { + dialector := postgres.New(postgres.Config{ + DSN: c.DSN(), + }) + + db, err := gorm.Open(dialector, &gorm.Config{}) + + //db, err := gorm.Open(dialector, &gorm.Config{ + // Logger: logger.Default.LogMode(logger.Info), // Enable GORM logging + //}) + + if err != nil { + return nil, err + } + + zapCfg := zap.NewProductionConfig() + zapCfg.Level = zap.NewAtomicLevelAt(zap.DebugLevel) // whatever minimum level + zapCfg.DisableCaller = true + // logger, _ := zapCfg.Build() + // db = gorm.Open(sqldblogger.New(logger), db) + + // ping the database to test the connection + sqlDB, err := db.DB() + if err != nil { + return nil, err + } + if err := sqlDB.Ping(); err != nil { + return nil, err + } + + sqlDB.SetMaxIdleConns(c.MaxIdleConnectionsInSecond) + sqlDB.SetMaxOpenConns(c.MaxOpenConnectionsInSecond) + sqlDB.SetConnMaxLifetime(c.ConnectionMaxLifetime()) + + fmt.Println("Successfully connected to PostgreSQL database") + + return db, nil +} diff --git a/internal/common/errors/code.go b/internal/common/errors/code.go new file mode 100644 index 0000000..060cb3e --- /dev/null +++ b/internal/common/errors/code.go @@ -0,0 +1,49 @@ +package errors + +import "net/http" + +const ( + Success Code = "20000" + ServerError Code = "50000" + BadRequest Code = "40000" + InvalidRequest Code = "40001" + Unauthorized Code = "40100" + Forbidden Code = "40300" + Timeout Code = "50400" +) + +type Code string + +var ( + codeMap = map[Code]string{ + Success: "Success", + BadRequest: "Bad or invalid request", + Unauthorized: "Unauthorized Token", + Timeout: "Gateway Timeout", + ServerError: "Internal Server Error", + Forbidden: "Forbidden", + InvalidRequest: "Invalid Request", + } + + codeHTTPMap = map[Code]int{ + Success: http.StatusOK, + BadRequest: http.StatusBadRequest, + Unauthorized: http.StatusUnauthorized, + Timeout: http.StatusGatewayTimeout, + ServerError: http.StatusInternalServerError, + Forbidden: http.StatusForbidden, + InvalidRequest: http.StatusUnprocessableEntity, + } +) + +func (c Code) GetMessage() string { + return codeMap[c] +} + +func (c Code) GetHTTPCode() int { + return codeHTTPMap[c] +} + +func (c Code) GetCode() string { + return string(c) +} diff --git a/internal/common/errors/errors.go b/internal/common/errors/errors.go new file mode 100644 index 0000000..31c3820 --- /dev/null +++ b/internal/common/errors/errors.go @@ -0,0 +1,95 @@ +package errors + +import "net/http" + +type ErrType string + +const ( + errRequestTimeOut ErrType = "Request Timeout to 3rd Party" + errConnectTimeOut ErrType = "Connect Timeout to 3rd Party" + errFailedExternalCall ErrType = "Failed response from 3rd Party call" + errExternalCall ErrType = "error on 3rd Party call" + errInvalidRequest ErrType = "Invalid Request" + errBadRequest ErrType = "Bad Request" + errOrderNotFound ErrType = "Astria order is not found" + errCheckoutIDNotDefined ErrType = "Checkout client id not found" + errInternalServer ErrType = "Internal Server error" + errUserIsNotFound ErrType = "User is not found" + errInvalidLogin ErrType = "User email or password is invalid" + errUnauthorized ErrType = "Unauthorized" +) + +var ( + ErrorBadRequest = NewServiceException(errBadRequest) + ErrorInvalidRequest = NewServiceException(errInvalidRequest) + ErrorUnauthorized = NewServiceException(errUnauthorized) + ErrorOrderNotFound = NewServiceException(errOrderNotFound) + ErrorClientIDNotDefined = NewServiceException(errCheckoutIDNotDefined) + ErrorRequestTimeout = NewServiceException(errRequestTimeOut) + ErrorExternalCall = NewServiceException(errExternalCall) + ErrorFailedExternalCall = NewServiceException(errFailedExternalCall) + ErrorConnectionTimeOut = NewServiceException(errConnectTimeOut) + ErrorInternalServer = NewServiceException(errInternalServer) + ErrorUserIsNotFound = NewServiceException(errUserIsNotFound) + ErrorUserInvalidLogin = NewServiceException(errInvalidLogin) +) + +type Error interface { + ErrorType() ErrType + MapErrorsToHTTPCode() int + MapErrorsToCode() Code + error +} + +type ServiceException struct { + errorType ErrType + message string +} + +func NewServiceException(errType ErrType) *ServiceException { + return &ServiceException{ + errorType: errType, + message: string(errType), + } +} + +func (s *ServiceException) ErrorType() ErrType { + return s.errorType +} + +func (s *ServiceException) Error() string { + return s.message +} + +func (s *ServiceException) MapErrorsToHTTPCode() int { + switch s.ErrorType() { + case errBadRequest: + return http.StatusBadRequest + + case errInvalidRequest: + return http.StatusBadRequest + + case errInvalidLogin: + return http.StatusBadRequest + + default: + return http.StatusInternalServerError + } +} + +func (s *ServiceException) MapErrorsToCode() Code { + switch s.ErrorType() { + + case errUnauthorized: + return Unauthorized + + case errConnectTimeOut: + return Timeout + + case errBadRequest: + return BadRequest + + default: + return ServerError + } +} diff --git a/internal/common/http/http.go b/internal/common/http/http.go new file mode 100644 index 0000000..2dd9f1f --- /dev/null +++ b/internal/common/http/http.go @@ -0,0 +1,46 @@ +package http + +import ( + "context" + "fmt" + "io/ioutil" + "net/http" + "time" + + "go.uber.org/zap" + + "furtuna-be/internal/common/logger" +) + +type HttpClient struct { + Client *http.Client +} + +func NewHttpClient() *HttpClient { + return &HttpClient{ + Client: &http.Client{}, + } +} + +func (c *HttpClient) Do(ctx context.Context, req *http.Request) (int, []byte, error) { + start := time.Now() + logger.ContextLogger(ctx).Info(fmt.Sprintf("Sending request: %v %v", req.Method, req.URL)) + resp, err := c.Client.Do(req) + if err != nil { + logger.ContextLogger(ctx).Error(" Failed to send request:", zap.Error(err)) + return 0, nil, err + } + + logger.ContextLogger(ctx).Info(fmt.Sprintf("Received Response: : %v", resp.StatusCode)) + + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + logger.ContextLogger(ctx).Error(" Failed to read response:", zap.Error(err)) + return 0, nil, err + } + + logger.ContextLogger(ctx).Info(fmt.Sprintf("Latency : %v", time.Since(start))) + + return resp.StatusCode, body, nil +} diff --git a/internal/common/logger/logger.go b/internal/common/logger/logger.go new file mode 100644 index 0000000..9263fdf --- /dev/null +++ b/internal/common/logger/logger.go @@ -0,0 +1,56 @@ +package logger + +import ( + "context" + "fmt" + "furtuna-be/internal/constants" + "sync" + + "go.uber.org/zap" +) + +var mainLogger *zap.Logger = nil + +var mainLoggerInit sync.Once + +func NewMainLoggerSingleton() *zap.Logger { + mainLoggerInit.Do(func() { + logger, err := zap.NewProduction() + if err != nil { + logger.Error("logger initialization failed", zap.Any("error", err)) + panic(fmt.Sprintf("logger initialization failed %v", err)) + } + logger.Info("logger started") + mainLogger = logger + }) + + return mainLogger +} + +func NewMainNoOpLoggerSingleton() *zap.Logger { + mainLoggerInit.Do(func() { + logger := zap.NewNop() + logger.Info("logger started") + mainLogger = logger + }) + + return mainLogger +} + +func NewNoOp() *zap.Logger { + return zap.NewNop() +} + +func GetLogger() *zap.Logger { + return mainLogger +} + +func ContextLogger(ctx context.Context) *zap.Logger { + logger := GetLogger() + + if ctxRqID, ok := ctx.Value(constants.ContextRequestID).(string); ok { + return logger.With(zap.String(constants.ContextRequestID, ctxRqID)) + } + + return logger +} diff --git a/internal/common/mycontext/kinoscontext.go b/internal/common/mycontext/kinoscontext.go new file mode 100644 index 0000000..9c3e5eb --- /dev/null +++ b/internal/common/mycontext/kinoscontext.go @@ -0,0 +1,48 @@ +package mycontext + +import ( + "context" + "furtuna-be/internal/constants/role" + "furtuna-be/internal/entity" +) + +type ContextKey string + +type Context interface { + context.Context + + RequestedBy() int64 + IsSuperAdmin() bool +} + +type MyContextImpl struct { + context.Context + + requestedBy int64 + requestID string + branchID int64 + roleID int +} + +func (m *MyContextImpl) RequestedBy() int64 { + return m.requestedBy +} + +func (m *MyContextImpl) IsSuperAdmin() bool { + return m.roleID == int(role.SuperAdmin) +} + +func NewMyContext(parent context.Context, claims *entity.JWTAuthClaims) (*MyContextImpl, error) { + return &MyContextImpl{ + Context: parent, + requestedBy: claims.UserID, + branchID: claims.BranchID, + roleID: claims.Role, + }, nil +} + +func NewContext(parent context.Context) *MyContextImpl { + return &MyContextImpl{ + Context: parent, + } +} diff --git a/internal/common/request/context.go b/internal/common/request/context.go new file mode 100644 index 0000000..22c0a83 --- /dev/null +++ b/internal/common/request/context.go @@ -0,0 +1,53 @@ +package request + +import ( + "context" + "github.com/gin-gonic/gin" +) + +const ( + ReqInfoKey reqInfoKeyType = "request-info" +) + +func SetTraceId(c *gin.Context, traceId string) { + info, exists := c.Get(ReqInfoKey) + if exists { + parsedInfo := info.(RequestInfo) + parsedInfo.TraceId = traceId + + c.Set(ReqInfoKey, parsedInfo) + + return + } + c.Set(ReqInfoKey, RequestInfo{TraceId: traceId}) +} + +func SetUserId(c *gin.Context, userId int64) { + info, exists := c.Get(ReqInfoKey) + if exists { + parsedInfo := info.(RequestInfo) + parsedInfo.UserId = userId + + c.Set(ReqInfoKey, parsedInfo) + + return + } + + c.Set(ReqInfoKey, RequestInfo{UserId: userId}) +} + +func SetUserContext(c *gin.Context, payload map[string]interface{}) { + c.Set(ReqInfoKey, RequestInfo{ + UserId: int64(payload["userId"].(float64)), + Role: payload["role"].(string), + }) +} + +func ContextWithReqInfo(c *gin.Context) context.Context { + info, ok := c.Get(ReqInfoKey) + if ok { + return WithRequestInfo(c, info.(RequestInfo)) + } + + return WithRequestInfo(c, RequestInfo{}) +} diff --git a/internal/common/request/request.go b/internal/common/request/request.go new file mode 100644 index 0000000..721fd67 --- /dev/null +++ b/internal/common/request/request.go @@ -0,0 +1,40 @@ +package request + +import "context" + +type requestInfoKey int + +const ( + key requestInfoKey = iota +) + +type RequestInfo struct { + UserId int64 + TraceId string + Permissions map[string]bool + Role string +} + +func WithRequestInfo(ctx context.Context, info RequestInfo) context.Context { + return context.WithValue(ctx, key, info) +} + +func GetRequestInfo(ctx context.Context) (requestInfo RequestInfo, ok bool) { + requestInfo, ok = ctx.Value(key).(RequestInfo) + return +} + +type reqInfoKeyType = string + +const ( + reqInfoKey reqInfoKeyType = "request-info" +) + +func GetReqInfo(c context.Context) RequestInfo { + info := c.Value(reqInfoKey) + if info != nil { + return info.(RequestInfo) + } + + return RequestInfo{} +} diff --git a/internal/constants/branch/branch.go b/internal/constants/branch/branch.go new file mode 100644 index 0000000..cc03929 --- /dev/null +++ b/internal/constants/branch/branch.go @@ -0,0 +1,12 @@ +package branch + +type BranchStatus string + +const ( + Active BranchStatus = "Active" + Inactive BranchStatus = "Inactive" +) + +func (b BranchStatus) toString() string { + return string(b) +} diff --git a/internal/constants/constants.go b/internal/constants/constants.go new file mode 100644 index 0000000..3564685 --- /dev/null +++ b/internal/constants/constants.go @@ -0,0 +1,11 @@ +package constants + +const ( + ContextRequestID string = "requestId" +) + +type UserType string + +func (u UserType) toString() string { + return string(u) +} diff --git a/internal/constants/device/device.go b/internal/constants/device/device.go new file mode 100644 index 0000000..569fac6 --- /dev/null +++ b/internal/constants/device/device.go @@ -0,0 +1,12 @@ +package device + +type DeviceStatus string + +const ( + On DeviceStatus = "On" + Off DeviceStatus = "Off" +) + +func (b DeviceStatus) toString() string { + return string(b) +} diff --git a/internal/constants/device/device_connection.go b/internal/constants/device/device_connection.go new file mode 100644 index 0000000..0c5128b --- /dev/null +++ b/internal/constants/device/device_connection.go @@ -0,0 +1,12 @@ +package device + +type DeviceConnectionStatus string + +const ( + Connected DeviceConnectionStatus = "Connected" + Disconnected DeviceConnectionStatus = "Disconnected" +) + +func (b DeviceConnectionStatus) toString() string { + return string(b) +} diff --git a/internal/constants/order/order.go b/internal/constants/order/order.go new file mode 100644 index 0000000..97d370d --- /dev/null +++ b/internal/constants/order/order.go @@ -0,0 +1,79 @@ +package order + +type OrderStatus string + +const ( + New OrderStatus = "NEW" + Paid OrderStatus = "PAID" + Cancel OrderStatus = "CANCEL" +) + +func (b OrderStatus) toString() string { + return string(b) +} + +func (i *OrderStatus) IsNew() bool { + if i == nil { + return false + } + + if *i == New { + return true + } + + return false +} + +type ItemType string + +const ( + Product ItemType = "PRODUCT" + Studio ItemType = "STUDIO" +) + +func (b ItemType) toString() string { + return string(b) +} + +func (i *ItemType) IsProduct() bool { + if i == nil { + return false + } + + if *i == Product { + return true + } + + return false +} + +func (i *ItemType) IsStudio() bool { + if i == nil { + return false + } + + if *i == Studio { + return true + } + + return false +} + +type OrderSearchStatus string + +const ( + Active OrderSearchStatus = "ACTIVE" + Inactive OrderSearchStatus = "INACTIVE" +) + +func (i *OrderSearchStatus) IsActive() bool { + if i == nil { + return false + } + + if *i == Active { + return true + } + + return false +} diff --git a/internal/constants/oss.go b/internal/constants/oss.go new file mode 100644 index 0000000..7e2a9ae --- /dev/null +++ b/internal/constants/oss.go @@ -0,0 +1,9 @@ +package constants + +const ( + OssLogLevelLogOff = "LogOff" + OssLogLevelDebug = "Debug" + OssLogLevelError = "Error" + OssLogLevelWarn = "Warn" + OssLogLevelInfo = "Info" +) diff --git a/internal/constants/product/product.go b/internal/constants/product/product.go new file mode 100644 index 0000000..623ea82 --- /dev/null +++ b/internal/constants/product/product.go @@ -0,0 +1,58 @@ +package product + +type ProductStatus string + +const ( + Active ProductStatus = "Active" + Inactive ProductStatus = "Inactive" +) + +func (b ProductStatus) toString() string { + return string(b) +} + +type ProductType string + +const ( + Food ProductType = "FOOD" + Beverage ProductType = "BEVERAGE" +) + +func (b ProductType) toString() string { + return string(b) +} + +type ProductStock string + +const ( + Available ProductStock = "AVAILABLE" + Unavailable ProductStock = "UNAVAILABLE" +) + +func (b ProductStock) toString() string { + return string(b) +} + +func (i *ProductStock) IsAvailable() bool { + if i == nil { + return false + } + + if *i == Available { + return true + } + + return false +} + +func (i *ProductStock) IsUnavailable() bool { + if i == nil { + return false + } + + if *i == Unavailable { + return true + } + + return false +} diff --git a/internal/constants/role/role.go b/internal/constants/role/role.go new file mode 100644 index 0000000..b21f368 --- /dev/null +++ b/internal/constants/role/role.go @@ -0,0 +1,9 @@ +package role + +type Role int64 + +const ( + SuperAdmin Role = 1 + BranchAdmin Role = 2 + CasheerAdmin Role = 3 +) diff --git a/internal/constants/studio/studio.go b/internal/constants/studio/studio.go new file mode 100644 index 0000000..02eed3c --- /dev/null +++ b/internal/constants/studio/studio.go @@ -0,0 +1,12 @@ +package studio + +type StudioStatus string + +const ( + Active StudioStatus = "Active" + Inactive StudioStatus = "Inactive" +) + +func (b StudioStatus) toString() string { + return string(b) +} diff --git a/internal/constants/transaction/transaction.go b/internal/constants/transaction/transaction.go new file mode 100644 index 0000000..d034ea4 --- /dev/null +++ b/internal/constants/transaction/transaction.go @@ -0,0 +1,26 @@ +package transaction + +type PaymentStatus string + +const ( + New PaymentStatus = "NEW" + Paid PaymentStatus = "PAID" + Cancel PaymentStatus = "CANCEL" +) + +func (b PaymentStatus) toString() string { + return string(b) +} + +type PaymentMethod string + +const ( + Cash PaymentMethod = "CASH" + Debit PaymentMethod = "DEBIT" + Transfer PaymentMethod = "TRANSFER" + QRIS PaymentMethod = "QRIS" +) + +func (b PaymentMethod) toString() string { + return string(b) +} diff --git a/internal/constants/userstatus/userstatus.go b/internal/constants/userstatus/userstatus.go new file mode 100644 index 0000000..d8f2f8b --- /dev/null +++ b/internal/constants/userstatus/userstatus.go @@ -0,0 +1,12 @@ +package userstatus + +type UserStatus string + +const ( + Active UserStatus = "Active" + Inactive UserStatus = "Inactive" +) + +func (u UserStatus) toString() string { + return string(u) +} diff --git a/internal/entity/auth.go b/internal/entity/auth.go new file mode 100644 index 0000000..db5f4bb --- /dev/null +++ b/internal/entity/auth.go @@ -0,0 +1,143 @@ +package entity + +import ( + "furtuna-be/internal/constants/role" + "furtuna-be/internal/constants/userstatus" + "time" +) + +type AuthData struct { + Token string `json:"token"` + UserID int64 `gorm:"column:user_id"` + RoleID int `gorm:"column:role_id"` + OrganizationID int64 `gorm:"column:organization_id"` +} + +type UserDB struct { + ID int64 `gorm:"primary_key;column:id" json:"id"` + Name string `gorm:"column:name" json:"name"` + Email string `gorm:"column:email" json:"email"` + Password string `gorm:"column:password" json:"-"` + Status userstatus.UserStatus `gorm:"column:status" json:"status"` + UserType string `gorm:"column:user_type" json:"user_type"` + PhoneNumber string `gorm:"column:phone_number" json:"phone_number"` + NIK string `gorm:"column:nik" json:"nik"` + RoleID int64 `gorm:"column:role_id" json:"role_id"` + RoleName string `gorm:"column:role_name" json:"role_name"` + BranchID *int64 `gorm:"column:partner_id" json:"partner_id"` + BranchName string `gorm:"column:partner_name" json:"partner_name"` + CreatedAt time.Time `gorm:"column:created_at" json:"created_at"` + UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at"` + DeletedAt *time.Time `gorm:"column:deleted_at" json:"deleted_at"` + CreatedBy int64 `gorm:"column:created_by" json:"created_by"` + UpdatedBy int64 `gorm:"column:updated_by" json:"updated_by"` +} + +func (u *UserDB) ToUser() *User { + if u == nil { + return &User{} + } + + userEntity := &User{ + ID: u.ID, + Name: u.Name, + Email: u.Email, + Status: u.Status, + CreatedAt: u.CreatedAt, + UpdatedAt: u.UpdatedAt, + RoleID: role.Role(u.RoleID), + RoleName: u.RoleName, + PartnerID: u.BranchID, + BranchName: u.BranchName, + } + + return userEntity +} + +func (u *UserDB) ToUserRoleDB() *UserRoleDB { + if u == nil { + return &UserRoleDB{} + } + + userRole := &UserRoleDB{ + ID: 0, + UserID: u.ID, + RoleID: u.RoleID, + PartnerID: u.BranchID, + CreatedAt: u.CreatedAt, + UpdatedAt: u.UpdatedAt, + } + + return userRole +} + +func (UserDB) TableName() string { + return "users" +} + +func (u *UserDB) ToUserAuthenticate(signedToken string) *AuthenticateUser { + return &AuthenticateUser{ + Token: signedToken, + Name: u.Name, + RoleID: role.Role(u.RoleID), + RoleName: u.RoleName, + BranchID: u.BranchID, + BranchName: u.BranchName, + } +} + +type UserSearch struct { + Search string + Name string + RoleID int64 + PartnerID int64 + Limit int + Offset int +} + +type UserList []*UserDB + +func (b *UserList) ToUserList() []*User { + var users []*User + for _, user := range *b { + users = append(users, user.ToUser()) + } + return users +} + +func (u *UserDB) ToUpdatedUser(req User) error { + + if req.Name != "" { + u.Name = req.Name + } + + if req.Email != "" { + u.Email = req.Email + } + + if *req.PartnerID > 0 { + u.BranchID = req.PartnerID + } + + if req.RoleID > 0 { + u.RoleID = int64(req.RoleID) + } + + if req.Password != "" { + hashedPassword, err := req.HashedPassword(req.Password) + + if err != nil { + return err + } + u.Password = hashedPassword + } + + return nil +} + +func (o *UserDB) SetDeleted(updatedby int64) { + currentTime := time.Now() + o.DeletedAt = ¤tTime + o.UpdatedBy = updatedby + o.Status = userstatus.Inactive +} diff --git a/internal/entity/branch.go b/internal/entity/branch.go new file mode 100644 index 0000000..4a56c3c --- /dev/null +++ b/internal/entity/branch.go @@ -0,0 +1,83 @@ +package entity + +import ( + "furtuna-be/internal/constants/branch" + "time" +) + +type Branch struct { + ID int64 + Name string + Status branch.BranchStatus + Location string + CreatedAt time.Time + UpdatedAt time.Time + DeletedAt *time.Time + CreatedBy int64 + UpdatedBy int64 +} + +type BranchSearch struct { + Search string + Name string + Limit int + Offset int +} + +type BranchList []*BranchDB + +type BranchDB struct { + Branch +} + +func (b *Branch) ToBranchDB() *BranchDB { + return &BranchDB{ + Branch: *b, + } +} + +func (BranchDB) TableName() string { + return "branches" +} + +func (e *BranchDB) ToBranch() *Branch { + return &Branch{ + ID: e.ID, + Name: e.Name, + Status: e.Status, + Location: e.Location, + CreatedAt: e.CreatedAt, + UpdatedAt: e.UpdatedAt, + CreatedBy: e.CreatedBy, + } +} + +func (b *BranchList) ToBranchList() []*Branch { + var branches []*Branch + for _, branch := range *b { + branches = append(branches, branch.ToBranch()) + } + return branches +} + +func (o *BranchDB) ToUpdatedBranch(updatedby int64, req Branch) { + o.UpdatedBy = updatedby + + if req.Name != "" { + o.Name = req.Name + } + + if req.Status != "" { + o.Status = req.Status + } + + if req.Location != "" { + o.Location = req.Location + } +} + +func (o *BranchDB) SetDeleted(updatedby int64) { + currentTime := time.Now() + o.DeletedAt = ¤tTime + o.UpdatedBy = updatedby +} diff --git a/internal/entity/event.go b/internal/entity/event.go new file mode 100644 index 0000000..915c3bb --- /dev/null +++ b/internal/entity/event.go @@ -0,0 +1,158 @@ +package entity + +import ( + "database/sql/driver" + "errors" + "strings" + "time" +) + +type Status string + +type Event struct { + ID int64 + Name string + Description string + StartDate time.Time + EndDate time.Time + Location string + Level string + Included StringArray `gorm:"type:text[]"` + Price float64 + Paid bool + LocationID *int64 + Status Status + CreatedAt time.Time + UpdatedAt time.Time + DeletedAt *time.Time +} + +type StringArray []string + +func (a StringArray) Value() (driver.Value, error) { + if a == nil { + return nil, nil + } + + joined := "{" + strings.Join(a, ",") + "}" + + return []byte(joined), nil +} + +func (a *StringArray) Scan(src interface{}) error { + if src == nil { + *a = nil + return nil + } + + srcStr, ok := src.(string) + if !ok { + return errors.New("failed to scan StringArray") + } + + // Remove the curly braces and split the string into elements + if len(srcStr) < 2 || srcStr[0] != '{' || srcStr[len(srcStr)-1] != '}' { + return errors.New("invalid format for StringArray") + } + srcStr = srcStr[1 : len(srcStr)-1] + + *a = strings.Split(srcStr, ",") + + return nil +} + +type EventSearch struct { + Name string + Limit int + Offset int +} + +type EventList []*EventDB + +type EventDB struct { + Event +} + +func (e *Event) ToEventDB() *EventDB { + return &EventDB{ + Event: *e, + } +} + +func (e *EventDB) ToEvent() *Event { + return &Event{ + ID: e.ID, + Name: e.Name, + Description: e.Description, + StartDate: e.StartDate, + EndDate: e.EndDate, + Location: e.Location, + Level: e.Level, + Included: e.Included, + Price: e.Price, + Paid: e.Paid, + LocationID: e.LocationID, + CreatedAt: e.CreatedAt, + UpdatedAt: e.UpdatedAt, + Status: e.Status, + } +} + +func (e *EventList) ToEventList() []*Event { + var events []*Event + for _, event := range *e { + events = append(events, event.ToEvent()) + } + return events +} + +func (EventDB) TableName() string { + return "events" +} + +func (o *EventDB) ToUpdatedEvent(req Event) { + if req.Name != "" { + o.Name = req.Name + } + + if req.Description != "" { + o.Description = req.Description + } + + if !req.StartDate.IsZero() { + o.StartDate = req.StartDate + } + + if !req.EndDate.IsZero() { + o.EndDate = req.EndDate + } + + if req.Location != "" { + o.Location = req.Location + } + + if req.Level != "" { + o.Level = req.Level + } + + if req.Included != nil && len(req.Included) > 0 { + o.Included = req.Included + } + + if req.Price != 0 { + o.Price = req.Price + } + + if req.LocationID != nil { + o.LocationID = req.LocationID + } + + if req.Status != "" { + o.Status = req.Status + } +} + +func (o *EventDB) SetDeleted() { + currentTime := time.Now() + o.DeletedAt = ¤tTime +} diff --git a/internal/entity/jwt.go b/internal/entity/jwt.go new file mode 100644 index 0000000..3946991 --- /dev/null +++ b/internal/entity/jwt.go @@ -0,0 +1,12 @@ +package entity + +import "github.com/golang-jwt/jwt" + +type JWTAuthClaims struct { + UserID int64 `json:"id"` + Name string `json:"name"` + Email string `json:"email"` + Role int `json:"role"` + BranchID int64 `json:"branch_id"` + jwt.StandardClaims +} diff --git a/internal/entity/order.go b/internal/entity/order.go new file mode 100644 index 0000000..2767882 --- /dev/null +++ b/internal/entity/order.go @@ -0,0 +1,166 @@ +package entity + +import ( + "furtuna-be/internal/constants/order" + "time" +) + +type Order struct { + ID int64 + BranchID int64 + BranchName string + Status order.OrderStatus + Amount float64 + CustomerName string `gorm:"column:customer_name" json:"customer_name"` + CustomerPhone string `gorm:"column:customer_phone" json:"customer_phone"` + Pax int `gorm:"column:pax" json:"pax"` + CreatedAt time.Time + UpdatedAt time.Time + CreatedBy int64 + UpdatedBy int64 + Transaction Transaction + OrderItem []OrderItem +} + +type OrderSearch struct { + Search string + StatusActive order.OrderSearchStatus + Status order.OrderStatus + BranchID int64 + Limit int + Offset int +} + +type OrderTotalRevenueSearch struct { + Year int + Month int + BranchID int64 + DateStart *time.Time + DateEnd *time.Time +} + +type OrderYearlyRevenueList []OrderYearlyRevenue +type OrderYearlyRevenue struct { + ItemType string `gorm:"column:item_type"` + Month int `gorm:"column:month_number"` + Amount float64 `gorm:"column:total_amount"` +} + +type OrderBranchRevenueSearch struct { + DateStart *time.Time + DateEnd *time.Time +} + +type OrderBranchRevenueList []OrderBranchRevenue +type OrderBranchRevenue struct { + BranchID string `gorm:"column:branch_id"` + BranchName string `gorm:"column:name"` + BranchLocation string `gorm:"column:location"` + TotalTransaction int `gorm:"column:total_trans"` + TotalAmount float64 `gorm:"column:total_amount"` +} + +type OrderList []*OrderDB + +type OrderDB struct { + Order +} + +func (b *Order) ToOrderDB() *OrderDB { + var amount float64 + for _, i := range b.OrderItem { + amount = amount + (i.Price * float64(i.Qty)) + } + b.Amount = amount + b.Transaction.Amount = amount + + return &OrderDB{ + Order: *b, + } +} + +func (OrderDB) TableName() string { + return "orders" +} + +func (e *OrderDB) ToOrder() *Order { + return &Order{ + ID: e.ID, + Status: e.Status, + BranchID: e.BranchID, + BranchName: e.BranchName, + Amount: e.Amount, + CustomerName: e.CustomerName, + CustomerPhone: e.CustomerPhone, + Pax: e.Pax, + Transaction: e.Transaction, + OrderItem: e.OrderItem, + CreatedAt: e.CreatedAt, + UpdatedAt: e.UpdatedAt, + CreatedBy: e.CreatedBy, + UpdatedBy: e.UpdatedBy, + } +} + +func (b *OrderList) ToOrderList() []*Order { + var Orders []*Order + for _, order := range *b { + Orders = append(Orders, order.ToOrder()) + } + return Orders +} + +func (o *OrderDB) ToUpdatedOrder(updatedby int64, req Order) { + o.UpdatedBy = updatedby + + if req.Amount > 0 { + o.Amount = req.Amount + } + + if req.Status != "" { + o.Status = req.Status + } +} + +type OrderItem struct { + OrderItemID int64 + OrderID int64 + ItemID int64 + ItemType order.ItemType + ItemName string + Price float64 + Qty int64 + CreatedAt time.Time + UpdatedAt time.Time + CreatedBy int64 + UpdatedBy int64 +} + +type OrderItemDB struct { + OrderItem +} + +func (b *OrderItem) ToOrderItemDB() *OrderItemDB { + return &OrderItemDB{ + OrderItem: *b, + } +} + +func (OrderItemDB) TableName() string { + return "order_items" +} + +func (e *OrderItemDB) ToOrderItem() *OrderItem { + return &OrderItem{ + OrderItemID: e.OrderItemID, + OrderID: e.OrderID, + ItemID: e.ItemID, + ItemType: e.ItemType, + Price: e.Price, + Qty: e.Qty, + CreatedAt: e.CreatedAt, + UpdatedAt: e.UpdatedAt, + CreatedBy: e.CreatedBy, + UpdatedBy: e.UpdatedBy, + } +} diff --git a/internal/entity/oss.go b/internal/entity/oss.go new file mode 100644 index 0000000..0e916db --- /dev/null +++ b/internal/entity/oss.go @@ -0,0 +1,24 @@ +package entity + +import "mime/multipart" + +type UploadFileRequest struct { + FileHeader *multipart.FileHeader + FolderName string + FileSize int64 `validate:"max=10000000"` // 10Mb = 10000000 byte + Ext string `validate:"oneof=.png .jpeg .jpg .pdf .xlsx .csv"` +} + +type DownloadFileRequest struct { + FileName string `query:"file_name" validate:"required"` + FolderName string `query:"folder_name" validate:"required"` +} + +type UploadFileResponse struct { + FilePath string `json:"file_path"` + FileUrl string `json:"file_url"` +} + +type DownloadFileResponse struct { + FileUrl string `json:"file_url"` +} diff --git a/internal/entity/partner.go b/internal/entity/partner.go new file mode 100644 index 0000000..222ceaf --- /dev/null +++ b/internal/entity/partner.go @@ -0,0 +1,82 @@ +package entity + +import ( + "time" +) + +type Partner struct { + ID int64 + Name string + Status string + Address string + CreatedAt time.Time + UpdatedAt time.Time + DeletedAt *time.Time + CreatedBy int64 + UpdatedBy int64 +} + +type PartnerSearch struct { + Search string + Name string + Limit int + Offset int +} + +type PartnerList []*PartnerDB + +type PartnerDB struct { + Partner +} + +func (p *Partner) ToPartnerDB() *PartnerDB { + return &PartnerDB{ + Partner: *p, + } +} + +func (PartnerDB) TableName() string { + return "partners" +} + +func (e *PartnerDB) ToPartner() *Partner { + return &Partner{ + ID: e.ID, + Name: e.Name, + Status: e.Status, + Address: e.Address, + CreatedAt: e.CreatedAt, + UpdatedAt: e.UpdatedAt, + CreatedBy: e.CreatedBy, + } +} + +func (p *PartnerList) ToPartnerList() []*Partner { + var partners []*Partner + for _, partner := range *p { + partners = append(partners, partner.ToPartner()) + } + return partners +} + +func (o *PartnerDB) ToUpdatedPartner(updatedBy int64, req Partner) { + o.UpdatedBy = updatedBy + + if req.Name != "" { + o.Name = req.Name + } + + if req.Status != "" { + o.Status = req.Status + } + + if req.Address != "" { + o.Address = req.Address + } +} + +func (o *PartnerDB) SetDeleted(updatedBy int64) { + currentTime := time.Now() + o.DeletedAt = ¤tTime + o.UpdatedBy = updatedBy +} diff --git a/internal/entity/product.go b/internal/entity/product.go new file mode 100644 index 0000000..cf8d55c --- /dev/null +++ b/internal/entity/product.go @@ -0,0 +1,114 @@ +package entity + +import ( + "furtuna-be/internal/constants/product" + "time" +) + +type Product struct { + ID int64 + Name string + Type product.ProductType + Price float64 + Status product.ProductStatus + Description string + Image string + BranchID int64 + StockQty int64 + CreatedAt time.Time + UpdatedAt time.Time + DeletedAt *time.Time + CreatedBy int64 + UpdatedBy int64 +} + +type ProductSearch struct { + Search string + Name string + Type product.ProductType + BranchID int64 + Available product.ProductStock + Limit int + Offset int +} + +type ProductList []*ProductDB + +type ProductDB struct { + Product +} + +func (b *Product) ToProductDB() *ProductDB { + return &ProductDB{ + Product: *b, + } +} + +func (ProductDB) TableName() string { + return "products" +} + +func (e *ProductDB) ToProduct() *Product { + return &Product{ + ID: e.ID, + Name: e.Name, + Type: e.Type, + Price: e.Price, + Status: e.Status, + Description: e.Description, + Image: e.Image, + BranchID: e.BranchID, + StockQty: e.StockQty, + CreatedAt: e.CreatedAt, + UpdatedAt: e.UpdatedAt, + DeletedAt: e.DeletedAt, + CreatedBy: e.CreatedBy, + UpdatedBy: e.UpdatedBy, + } +} + +func (b *ProductList) ToProductList() []*Product { + var Products []*Product + for _, product := range *b { + Products = append(Products, product.ToProduct()) + } + return Products +} + +func (o *ProductDB) ToUpdatedProduct(updatedby int64, req Product) { + o.UpdatedBy = updatedby + + if req.Name != "" { + o.Name = req.Name + } + + if req.Type != "" { + o.Type = req.Type + } + + if req.Price > 0 { + o.Price = req.Price + } + + if req.Status != "" { + o.Status = req.Status + } + + if req.Description != "" { + o.Description = req.Description + } + + if req.Image != "" { + o.Image = req.Image + } + + if req.StockQty > 0 { + o.StockQty = req.StockQty + } +} + +func (o *ProductDB) SetDeleted(updatedby int64) { + currentTime := time.Now() + o.DeletedAt = ¤tTime + o.UpdatedBy = updatedby +} diff --git a/internal/entity/studio.go b/internal/entity/studio.go new file mode 100644 index 0000000..fa2cb89 --- /dev/null +++ b/internal/entity/studio.go @@ -0,0 +1,95 @@ +package entity + +import ( + "furtuna-be/internal/constants/studio" + "time" +) + +type Studio struct { + ID int64 + BranchId int64 + Name string + Status studio.StudioStatus + Price float64 + Metadata []byte `gorm:"type:jsonb"` // Use jsonb data type for JSON data + CreatedAt time.Time + UpdatedAt time.Time + CreatedBy int64 + UpdatedBy int64 +} + +func (s *Studio) TableName() string { + return "studios" +} + +func (s *Studio) NewStudiosDB() *StudioDB { + return &StudioDB{ + Studio: *s, + } +} + +type StudioList []*StudioDB + +type StudioDB struct { + Studio +} + +func (s *StudioDB) ToStudio() *Studio { + return &Studio{ + ID: s.ID, + BranchId: s.BranchId, + Name: s.Name, + Status: s.Status, + Price: s.Price, + Metadata: s.Metadata, + CreatedAt: s.CreatedAt, + UpdatedAt: s.UpdatedAt, + CreatedBy: s.CreatedBy, + UpdatedBy: s.UpdatedBy, + } +} + +func (s *StudioList) ToStudioList() []*Studio { + var studios []*Studio + for _, studio := range *s { + studios = append(studios, studio.ToStudio()) + } + return studios +} + +func (s *StudioDB) ToUpdatedStudio(updatedBy int64, req Studio) { + s.UpdatedBy = updatedBy + + if req.BranchId != 0 { + s.BranchId = req.BranchId + } + + if req.Name != "" { + s.Name = req.Name + } + + if req.Status != "" { + s.Status = req.Status + } + + if req.Price != 0 { + s.Price = req.Price + } + + if req.Metadata != nil { + s.Metadata = req.Metadata + } +} + +func (s *StudioDB) ToStudioDB() *StudioDB { + return s +} + +type StudioSearch struct { + Id int64 + Name string + Status studio.StudioStatus + BranchId int64 + Limit int + Offset int +} diff --git a/internal/entity/transaction.go b/internal/entity/transaction.go new file mode 100644 index 0000000..bd76d90 --- /dev/null +++ b/internal/entity/transaction.go @@ -0,0 +1,35 @@ +package entity + +import ( + "furtuna-be/internal/constants/transaction" + "time" +) + +type Transaction struct { + ID int64 + BranchID int64 + Status transaction.PaymentStatus + Amount float64 + OrderID int64 + PaymentMethod transaction.PaymentMethod + CustomerName string + CustomerPhone string + CreatedAt time.Time + UpdatedAt time.Time + CreatedBy int64 + UpdatedBy int64 +} + +type TransactionDB struct { + Transaction +} + +func (b *Transaction) ToTransactionDB() *TransactionDB { + return &TransactionDB{ + Transaction: *b, + } +} + +func (TransactionDB) TableName() string { + return "transactions" +} diff --git a/internal/entity/user.go b/internal/entity/user.go new file mode 100644 index 0000000..1583898 --- /dev/null +++ b/internal/entity/user.go @@ -0,0 +1,78 @@ +package entity + +import ( + "errors" + "furtuna-be/internal/constants/role" + "furtuna-be/internal/constants/userstatus" + "time" + + "golang.org/x/crypto/bcrypt" +) + +type User struct { + ID int64 + Name string + Email string + Password string + Status userstatus.UserStatus + NIK string + CreatedAt time.Time + UpdatedAt time.Time + RoleID role.Role + RoleName string + PartnerID *int64 + BranchName string +} + +type AuthenticateUser struct { + Token string + Name string + RoleID role.Role + RoleName string + BranchID *int64 + BranchName string +} + +type UserRoleDB struct { + ID int64 `gorm:"primary_key;column:user_role_id" ` + UserID int64 `gorm:"column:user_id"` + RoleID int64 `gorm:"column:role_id"` + PartnerID *int64 `gorm:"column:partner_id"` + CreatedAt time.Time `gorm:"column:created_at"` + UpdatedAt time.Time `gorm:"column:updated_at"` +} + +func (UserRoleDB) TableName() string { + return "user_roles" +} + +func (u *User) ToUserDB(createdBy int64) (*UserDB, error) { + hashedPassword, err := u.HashedPassword(u.Password) + + if err != nil { + return nil, err + } + + if u.RoleID == role.BranchAdmin && u.PartnerID == nil { + return nil, errors.New("invalid request") + } + + return &UserDB{ + Name: u.Name, + Email: u.Email, + Password: hashedPassword, + RoleID: int64(u.RoleID), + BranchID: u.PartnerID, + Status: userstatus.Active, + CreatedBy: createdBy, + }, nil +} + +func (u User) HashedPassword(password string) (string, error) { + hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) + if err != nil { + return "", err + } + + return string(hashedPassword), nil +} diff --git a/internal/handlers/http/auth/auth.go b/internal/handlers/http/auth/auth.go new file mode 100644 index 0000000..6ac2d5b --- /dev/null +++ b/internal/handlers/http/auth/auth.go @@ -0,0 +1,79 @@ +package auth + +import ( + "furtuna-be/internal/constants/role" + "net/http" + + "github.com/gin-gonic/gin" + + "furtuna-be/internal/common/errors" + auth2 "furtuna-be/internal/handlers/request" + "furtuna-be/internal/handlers/response" + "furtuna-be/internal/services" +) + +type AuthHandler struct { + service services.Auth +} + +func (a *AuthHandler) Route(group *gin.RouterGroup, jwt gin.HandlerFunc) { + authRoute := group.Group("/auth") + authRoute.POST("/login", a.AuthLogin) +} + +func NewAuthHandler(service services.Auth) *AuthHandler { + return &AuthHandler{ + service: service, + } +} + +// AuthLogin handles the authentication process for user login. +// @Summary User login +// @Description Authenticates a user based on the provided credentials and returns a JWT token. +// @Accept json +// @Produce json +// @Param bodyParam body auth2.LoginRequest true "User login credentials" +// @Success 200 {object} response.BaseResponse{data=response.LoginResponse} "Login successful" +// @Failure 400 {object} response.BaseResponse{data=errors.Error} "Bad request" +// @Failure 401 {object} response.BaseResponse{data=errors.Error} "Unauthorized" +// @Router /api/v1/auth/login [post] +// @Tags Auth Login API's +func (h *AuthHandler) AuthLogin(c *gin.Context) { + var bodyParam auth2.LoginRequest + if err := c.ShouldBindJSON(&bodyParam); err != nil { + response.ErrorWrapper(c, errors.ErrorBadRequest) + return + } + + authUser, err := h.service.AuthenticateUser(c, bodyParam.Email, bodyParam.Password) + if err != nil { + response.ErrorWrapper(c, err) + return + } + + var branch *response.Branch + + if authUser.RoleID != role.SuperAdmin { + branch = &response.Branch{ + ID: authUser.BranchID, + Name: authUser.BranchName, + } + } + + resp := response.LoginResponse{ + Token: authUser.Token, + Branch: branch, + Name: authUser.Name, + Role: response.Role{ + ID: int64(authUser.RoleID), + Role: authUser.RoleName, + }, + } + + c.JSON(http.StatusOK, response.BaseResponse{ + Success: true, + Status: http.StatusOK, + Message: "Login Success", + Data: resp, + }) +} diff --git a/internal/handlers/http/branch/branch.go b/internal/handlers/http/branch/branch.go new file mode 100644 index 0000000..bbfae6f --- /dev/null +++ b/internal/handlers/http/branch/branch.go @@ -0,0 +1,264 @@ +package branch + +import ( + "furtuna-be/internal/common/errors" + "furtuna-be/internal/entity" + "furtuna-be/internal/handlers/request" + "furtuna-be/internal/handlers/response" + "furtuna-be/internal/services" + "net/http" + "strconv" + "time" + + "github.com/gin-gonic/gin" + "github.com/go-playground/validator/v10" +) + +type Handler struct { + service services.Branch +} + +func (h *Handler) Route(group *gin.RouterGroup, jwt gin.HandlerFunc) { + route := group.Group("/branch") + + route.POST("/", jwt, h.Create) + route.GET("/list", jwt, h.GetAll) + route.PUT("/:id", jwt, h.Update) + route.GET("/:id", jwt, h.GetByID) + route.DELETE("/:id", jwt, h.Delete) +} + +func NewHandler(service services.Branch) *Handler { + return &Handler{ + service: service, + } +} + +// Create handles the creation of a new branch. +// @Summary Create a new branch +// @Description Create a new branch based on the provided data. +// @Accept json +// @Produce json +// @Param Authorization header string true "JWT token" +// @Param req body request.Branch true "New branch details" +// @Success 200 {object} response.BaseResponse{data=response.Branch} "Branch created successfully" +// @Failure 400 {object} response.BaseResponse{data=errors.Error} "Bad request" +// @Failure 401 {object} response.BaseResponse{data=errors.Error} "Unauthorized" +// @Router /api/v1/branch [post] +// @Tags Branch APIs +func (h *Handler) Create(c *gin.Context) { + ctx := request.GetMyContext(c) + + var req request.Branch + if err := c.ShouldBindJSON(&req); err != nil { + response.ErrorWrapper(c, errors.ErrorBadRequest) + return + } + + validate := validator.New() + if err := validate.Struct(req); err != nil { + response.ErrorWrapper(c, err) + return + } + + res, err := h.service.Create(ctx, req.ToEntity()) + + if err != nil { + response.ErrorWrapper(c, err) + return + } + + c.JSON(http.StatusOK, response.BaseResponse{ + Success: true, + Status: http.StatusOK, + Data: h.toBranchResponse(res), + }) +} + +// Update handles the update of an existing branch. +// @Summary Update an existing branch +// @Description Update the details of an existing branch based on the provided ID. +// @Accept json +// @Produce json +// @Param Authorization header string true "JWT token" +// @Param id path int64 true "Branch ID to update" +// @Param req body request.Branch true "Updated branch details" +// @Success 200 {object} response.BaseResponse{data=response.Branch} "Branch updated successfully" +// @Failure 400 {object} response.BaseResponse{data=errors.Error} "Bad request" +// @Failure 401 {object} response.BaseResponse{data=errors.Error} "Unauthorized" +// @Router /api/v1/branch/{id} [put] +// @Tags Branch APIs +func (h *Handler) Update(c *gin.Context) { + ctx := request.GetMyContext(c) + + id := c.Param("id") + + branchID, err := strconv.ParseInt(id, 10, 64) + if err != nil { + response.ErrorWrapper(c, errors.ErrorBadRequest) + return + } + + var req request.Branch + if err := c.ShouldBindJSON(&req); err != nil { + response.ErrorWrapper(c, errors.ErrorBadRequest) + return + } + + validate := validator.New() + if err := validate.Struct(req); err != nil { + response.ErrorWrapper(c, err) + return + } + + updatedBranch, err := h.service.Update(ctx, branchID, req.ToEntity()) + if err != nil { + response.ErrorWrapper(c, err) + return + } + + c.JSON(http.StatusOK, response.BaseResponse{ + Success: true, + Status: http.StatusOK, + Data: h.toBranchResponse(updatedBranch), + }) +} + +// GetAll retrieves a list of branches. +// @Summary Get a list of branches +// @Description Get a paginated list of branches based on query parameters. +// @Accept json +// @Produce json +// @Param Authorization header string true "JWT token" +// @Param Limit query int false "Number of items to retrieve (default 10)" +// @Param Offset query int false "Offset for pagination (default 0)" +// @Success 200 {object} response.BaseResponse{data=response.BranchList} "List of branches" +// @Failure 400 {object} response.BaseResponse{data=errors.Error} "Bad request" +// @Failure 401 {object} response.BaseResponse{data=errors.Error} "Unauthorized" +// @Router /api/v1/branch/list [get] +// @Tags Branch APIs +func (h *Handler) GetAll(c *gin.Context) { + var req request.BranchParam + if err := c.ShouldBindQuery(&req); err != nil { + response.ErrorWrapper(c, errors.ErrorBadRequest) + return + } + + branchs, total, err := h.service.GetAll(c.Request.Context(), req.ToEntity()) + if err != nil { + response.ErrorWrapper(c, err) + return + } + + c.JSON(http.StatusOK, response.BaseResponse{ + Success: true, + Status: http.StatusOK, + Data: h.toBranchResponseList(branchs, int64(total), req), + }) +} + +// Delete handles the deletion of a branch by ID. +// @Summary Delete a branch by ID +// @Description Delete a branch based on the provided ID. +// @Accept json +// @Produce json +// @Param Authorization header string true "JWT token" +// @Param id path int64 true "Branch ID to delete" +// @Success 200 {object} response.BaseResponse "Branch deleted successfully" +// @Failure 400 {object} response.BaseResponse{data=errors.Error} "Bad request" +// @Failure 401 {object} response.BaseResponse{data=errors.Error} "Unauthorized" +// @Router /api/v1/branch/{id} [delete] +// @Tags Branch APIs +func (h *Handler) Delete(c *gin.Context) { + ctx := request.GetMyContext(c) + id := c.Param("id") + + // Parse the ID into a uint + branchID, err := strconv.ParseInt(id, 10, 64) + if err != nil { + response.ErrorWrapper(c, errors.ErrorBadRequest) + return + } + + err = h.service.Delete(ctx, branchID) + if err != nil { + c.JSON(http.StatusInternalServerError, response.BaseResponse{ + Success: false, + Status: http.StatusInternalServerError, + Message: err.Error(), + Data: nil, + }) + return + } + + c.JSON(http.StatusOK, response.BaseResponse{ + Success: true, + Status: http.StatusOK, + Data: nil, + }) +} + +// GetByID retrieves details of a specific branch by ID. +// @Summary Get details of a branch by ID +// @Description Get details of a branch based on the provided ID. +// @Accept json +// @Produce json +// @Param Authorization header string true "JWT token" +// @Param id path int64 true "Branch ID to retrieve" +// @Success 200 {object} response.BaseResponse{data=response.Branch} "Branch details" +// @Failure 400 {object} response.BaseResponse{data=errors.Error} "Bad request" +// @Failure 401 {object} response.BaseResponse{data=errors.Error} "Unauthorized" +// @Router /api/v1/branch/{id} [get] +// @Tags Branch APIs +func (h *Handler) GetByID(c *gin.Context) { + id := c.Param("id") + + // Parse the ID into a uint + branchID, err := strconv.ParseInt(id, 10, 64) + if err != nil { + response.ErrorWrapper(c, errors.ErrorBadRequest) + return + } + + res, err := h.service.GetByID(c.Request.Context(), branchID) + if err != nil { + c.JSON(http.StatusInternalServerError, response.BaseResponse{ + Success: false, + Status: http.StatusInternalServerError, + Message: err.Error(), + Data: nil, + }) + return + } + + c.JSON(http.StatusOK, response.BaseResponse{ + Success: true, + Status: http.StatusOK, + Data: h.toBranchResponse(res), + }) +} + +func (h *Handler) toBranchResponse(resp *entity.Branch) response.Branch { + return response.Branch{ + ID: &resp.ID, + Name: resp.Name, + Status: string(resp.Status), + Location: resp.Location, + CreatedAt: resp.CreatedAt.Format(time.RFC3339), + UpdatedAt: resp.CreatedAt.Format(time.RFC3339), + } +} + +func (h *Handler) toBranchResponseList(resp []*entity.Branch, total int64, req request.BranchParam) response.BranchList { + var branches []response.Branch + for _, b := range resp { + branches = append(branches, h.toBranchResponse(b)) + } + + return response.BranchList{ + Branches: branches, + Total: total, + Limit: req.Limit, + Offset: req.Offset, + } +} diff --git a/internal/handlers/http/event/event.go b/internal/handlers/http/event/event.go new file mode 100644 index 0000000..0074a3f --- /dev/null +++ b/internal/handlers/http/event/event.go @@ -0,0 +1,205 @@ +package event + +import ( + "net/http" + "strconv" + "time" + + "github.com/gin-gonic/gin" + + "furtuna-be/internal/common/errors" + "furtuna-be/internal/entity" + "furtuna-be/internal/handlers/request" + "furtuna-be/internal/handlers/response" + "furtuna-be/internal/services" +) + +type Handler struct { + service services.Event +} + +func (h *Handler) Route(group *gin.RouterGroup, jwt gin.HandlerFunc) { + route := group.Group("/event") + + route.POST("/", jwt, h.Create) + route.GET("/list", jwt, h.GetAll) + route.PUT("/:id", jwt, h.Update) + route.GET("/:id", jwt, h.GetByID) + route.DELETE("/:id", jwt, h.Delete) +} + +func NewHandler(service services.Event) *Handler { + return &Handler{ + service: service, + } +} + +func (h *Handler) Create(c *gin.Context) { + var req request.Event + if err := c.ShouldBindJSON(&req); err != nil { + response.ErrorWrapper(c, errors.ErrorBadRequest) + return + } + + if err := req.Validate(); err != nil { + response.ErrorWrapper(c, errors.ErrorInvalidRequest) + return + } + + res, err := h.service.Create(c.Request.Context(), req.ToEntity()) + + if err != nil { + response.ErrorWrapper(c, err) + return + } + + c.JSON(http.StatusOK, response.BaseResponse{ + Success: true, + Status: http.StatusOK, + Data: h.toEventResponse(res), + }) +} + +func (h *Handler) Update(c *gin.Context) { + id := c.Param("id") + + eventID, err := strconv.ParseInt(id, 10, 64) + if err != nil { + response.ErrorWrapper(c, errors.ErrorBadRequest) + return + } + + var req request.Event + if err := c.ShouldBindJSON(&req); err != nil { + response.ErrorWrapper(c, errors.ErrorBadRequest) + return + } + + if err := req.Validate(); err != nil { + response.ErrorWrapper(c, errors.ErrorBadRequest) + return + } + + updatedEvent, err := h.service.Update(c.Request.Context(), eventID, req.ToEntity()) + if err != nil { + response.ErrorWrapper(c, err) + return + } + + c.JSON(http.StatusOK, response.BaseResponse{ + Success: true, + Status: http.StatusOK, + Data: h.toEventResponse(updatedEvent), + }) +} + +func (h *Handler) GetAll(c *gin.Context) { + var req request.EventParam + if err := c.ShouldBindQuery(&req); err != nil { + response.ErrorWrapper(c, errors.ErrorBadRequest) + return + } + + events, total, err := h.service.GetAll(c.Request.Context(), req.ToEntity()) + if err != nil { + response.ErrorWrapper(c, err) + return + } + + c.JSON(http.StatusOK, response.BaseResponse{ + Success: true, + Status: http.StatusOK, + Data: h.toEventResponseList(events, int64(total), req), + }) +} + +func (h *Handler) Delete(c *gin.Context) { + id := c.Param("id") + + // Parse the ID into a uint + eventID, err := strconv.ParseInt(id, 10, 64) + if err != nil { + response.ErrorWrapper(c, errors.ErrorBadRequest) + return + } + + err = h.service.Delete(c.Request.Context(), eventID) + if err != nil { + c.JSON(http.StatusInternalServerError, response.BaseResponse{ + Success: false, + Status: http.StatusInternalServerError, + Message: err.Error(), + Data: nil, + }) + return + } + + c.JSON(http.StatusOK, response.BaseResponse{ + Success: true, + Status: http.StatusOK, + Data: nil, + }) +} + +func (h *Handler) GetByID(c *gin.Context) { + id := c.Param("id") + + // Parse the ID into a uint + eventID, err := strconv.ParseInt(id, 10, 64) + if err != nil { + response.ErrorWrapper(c, errors.ErrorBadRequest) + return + } + + res, err := h.service.GetByID(c.Request.Context(), eventID) + if err != nil { + c.JSON(http.StatusInternalServerError, response.BaseResponse{ + Success: false, + Status: http.StatusInternalServerError, + Message: err.Error(), + Data: nil, + }) + return + } + + c.JSON(http.StatusOK, response.BaseResponse{ + Success: true, + Status: http.StatusOK, + Data: h.toEventResponse(res), + }) +} + +func (h *Handler) toEventResponse(resp *entity.Event) response.Event { + return response.Event{ + ID: resp.ID, + Name: resp.Name, + Description: resp.Description, + StartDate: resp.StartDate.Format("2006-01-02"), + EndDate: resp.EndDate.Format("2006-01-02"), + StartTime: resp.StartDate.Format("15:04:05"), + EndTime: resp.EndDate.Format("15:04:05"), + Location: resp.Location, + Level: resp.Level, + Included: resp.Included, + Price: resp.Price, + Paid: resp.Paid, + LocationID: resp.LocationID, + CreatedAt: resp.CreatedAt.Format(time.RFC3339), + UpdatedAt: resp.CreatedAt.Format(time.RFC3339), + Status: string(resp.Status), + } +} + +func (h *Handler) toEventResponseList(resp []*entity.Event, total int64, req request.EventParam) response.EventList { + var events []response.Event + for _, evt := range resp { + events = append(events, h.toEventResponse(evt)) + } + + return response.EventList{ + Events: events, + Total: total, + Limit: req.Limit, + Offset: req.Offset, + } +} diff --git a/internal/handlers/http/order/order.go b/internal/handlers/http/order/order.go new file mode 100644 index 0000000..a6ad9e1 --- /dev/null +++ b/internal/handlers/http/order/order.go @@ -0,0 +1,409 @@ +package order + +import ( + "furtuna-be/internal/common/errors" + "furtuna-be/internal/entity" + "furtuna-be/internal/handlers/request" + "furtuna-be/internal/handlers/response" + "furtuna-be/internal/services" + "net/http" + "strconv" + "time" + + "github.com/gin-gonic/gin" + "github.com/go-playground/validator/v10" +) + +type Handler struct { + service services.Order +} + +func (h *Handler) Route(group *gin.RouterGroup, jwt gin.HandlerFunc) { + route := group.Group("/order") + + route.POST("/", jwt, h.Create) + route.GET("/list", jwt, h.GetAll) + route.GET("/:id", jwt, h.GetByID) + route.GET("/total-revenue", jwt, h.GetTotalRevenue) + route.GET("/yearly-revenue/:year", jwt, h.GetYearlyRevenue) + route.GET("/branch-revenue", jwt, h.GetBranchRevenue) + route.PUT("/update-status/:id", jwt, h.UpdateStatus) +} + +func NewHandler(service services.Order) *Handler { + return &Handler{ + service: service, + } +} + +// Create handles the creation of a new order. +// @Summary Create a new order +// @Description Create a new order with the provided details. +// @Accept json +// @Produce json +// @Param Authorization header string true "JWT token" +// @Param order body request.Order true "Order details" +// @Success 200 {object} response.BaseResponse "Order created successfully" +// @Failure 400 {object} response.BaseResponse{data=errors.Error} "Bad request" +// @Failure 401 {object} response.BaseResponse{data=errors.Error} "Unauthorized" +// @Router /api/v1/order [post] +// @Tag Order APIs +func (h *Handler) Create(c *gin.Context) { + ctx := request.GetMyContext(c) + + var req request.Order + if err := c.ShouldBindJSON(&req); err != nil { + response.ErrorWrapper(c, errors.ErrorBadRequest) + return + } + + validate := validator.New() + if err := validate.Struct(req); err != nil { + response.ErrorWrapper(c, err) + return + } + + err := h.service.Create(ctx, req.ToEntity()) + + if err != nil { + response.ErrorWrapper(c, err) + return + } + + c.JSON(http.StatusOK, response.BaseResponse{ + Success: true, + Status: http.StatusOK, + }) +} + +// UpdateStatus handles the update of the order status. +// @Summary Update the status of an order +// @Description Update the status of the specified order with the provided details. +// @Accept json +// @Produce json +// @Param Authorization header string true "JWT token" +// @Param id path string true "Order ID" +// @Param status body request.UpdateStatus true "Status details" +// @Success 200 {object} response.BaseResponse{data=response.Order} "Order status updated successfully" +// @Failure 400 {object} response.BaseResponse{data=errors.Error} "Bad request" +// @Failure 401 {object} response.BaseResponse{data=errors.Error} "Unauthorized" +// @Failure 500 {object} response.BaseResponse "Internal server error" +// @Router /api/v1/order/update-status/{id} [put] +// @Tag Order APIs +func (h *Handler) UpdateStatus(c *gin.Context) { + ctx := request.GetMyContext(c) + + var req request.UpdateStatus + if err := c.ShouldBindJSON(&req); err != nil { + response.ErrorWrapper(c, errors.ErrorBadRequest) + return + } + + id := c.Param("id") + + // Parse the ID into a uint + orderID, err := strconv.ParseInt(id, 10, 64) + if err != nil { + response.ErrorWrapper(c, errors.ErrorBadRequest) + return + } + + res, err := h.service.UpdateStatus(ctx, orderID, req.ToEntity()) + if err != nil { + c.JSON(http.StatusInternalServerError, response.BaseResponse{ + Success: false, + Status: http.StatusInternalServerError, + Message: err.Error(), + Data: nil, + }) + return + } + + c.JSON(http.StatusOK, response.BaseResponse{ + Success: true, + Status: http.StatusOK, + Data: h.toOrderResponse(res), + }) +} + +// GetByID retrieves the details of a specific order by ID. +// @Summary Get details of an order by ID +// @Description Retrieve the details of the specified order by ID. +// @Accept json +// @Produce json +// @Param Authorization header string true "JWT token" +// @Param id path string true "Order ID" +// @Success 200 {object} response.BaseResponse{data=response.Order} "Order details retrieved successfully" +// @Failure 400 {object} response.BaseResponse{data=errors.Error} "Bad request" +// @Failure 401 {object} response.BaseResponse{data=errors.Error} "Unauthorized" +// @Failure 500 {object} response.BaseResponse "Internal server error" +// @Router /api/v1/order/{id} [get] +// @Tag Order APIs +func (h *Handler) GetByID(c *gin.Context) { + id := c.Param("id") + + // Parse the ID into a uint + orderID, err := strconv.ParseInt(id, 10, 64) + if err != nil { + response.ErrorWrapper(c, errors.ErrorBadRequest) + return + } + + res, err := h.service.GetByID(c.Request.Context(), orderID) + if err != nil { + c.JSON(http.StatusInternalServerError, response.BaseResponse{ + Success: false, + Status: http.StatusInternalServerError, + Message: err.Error(), + Data: nil, + }) + return + } + + c.JSON(http.StatusOK, response.BaseResponse{ + Success: true, + Status: http.StatusOK, + Data: h.toOrderResponse(res), + }) +} + +// GetAll retrieves a list of orders based on the specified parameters. +// @Summary Get a list of orders +// @Description Retrieve a list of orders based on the specified parameters. +// @Accept json +// @Produce json +// @Param Authorization header string true "JWT token" +// @Param limit query int false "Number of items to retrieve (default: 10)" +// @Param offset query int false "Number of items to skip (default: 0)" +// @Success 200 {object} response.BaseResponse{data=response.OrderList} "List of orders retrieved successfully" +// @Failure 400 {object} response.BaseResponse{data=errors.Error} "Bad request" +// @Failure 401 {object} response.BaseResponse{data=errors.Error} "Unauthorized" +// @Failure 500 {object} response.BaseResponse "Internal server error" +// @Router /api/v1/order/list [get] +// @Tag Order APIs +func (h *Handler) GetAll(c *gin.Context) { + var req request.OrderParam + if err := c.ShouldBindQuery(&req); err != nil { + response.ErrorWrapper(c, errors.ErrorBadRequest) + return + } + + orders, total, err := h.service.GetAll(c.Request.Context(), req.ToEntity()) + if err != nil { + response.ErrorWrapper(c, err) + return + } + + c.JSON(http.StatusOK, response.BaseResponse{ + Success: true, + Status: http.StatusOK, + Data: h.toOrderResponseList(orders, int64(total), req), + }) +} + +// GetTotalRevenue retrieves the total revenue and number of transactions for orders. +// @Summary Get total revenue and number of transactions for orders +// @Description Retrieve the total revenue and number of transactions for orders based on the specified parameters. +// @Accept json +// @Produce json +// @Param Authorization header string true "JWT token" +// @Param start_date query string false "Start date for filtering (format: 'YYYY-MM-DD')" +// @Param end_date query string false "End date for filtering (format: 'YYYY-MM-DD')" +// @Success 200 {object} response.BaseResponse{data=response.OrderMonthlyRevenue} "Total revenue and transactions retrieved successfully" +// @Failure 400 {object} response.BaseResponse{data=errors.Error} "Bad request" +// @Failure 401 {object} response.BaseResponse{data=errors.Error} "Unauthorized" +// @Failure 500 {object} response.BaseResponse "Internal server error" +// @Router /api/v1/order/total-revenue [get] +// @Tag Order APIs +func (h *Handler) GetTotalRevenue(c *gin.Context) { + var req request.OrderTotalRevenueParam + if err := c.ShouldBindQuery(&req); err != nil { + response.ErrorWrapper(c, errors.ErrorBadRequest) + return + } + + rev, trans, err := h.service.GetTotalRevenue(c.Request.Context(), req.ToEntity()) + if err != nil { + c.JSON(http.StatusInternalServerError, response.BaseResponse{ + Success: false, + Status: http.StatusInternalServerError, + Message: err.Error(), + Data: nil, + }) + return + } + + c.JSON(http.StatusOK, response.BaseResponse{ + Success: true, + Status: http.StatusOK, + Data: h.toOrderTotalRevenueResponse(rev, trans), + }) +} + +// GetYearlyRevenue retrieves the yearly revenue for orders. +// @Summary Get yearly revenue for orders +// @Description Retrieve the yearly revenue for orders based on the specified year. +// @Accept json +// @Produce json +// @Param Authorization header string true "JWT token" +// @Param year path int true "Year for filtering" +// @Success 200 {object} response.BaseResponse{data=map[int]map[string]float64} "Yearly revenue retrieved successfully" +// @Failure 400 {object} response.BaseResponse{data=errors.Error} "Bad request" +// @Failure 401 {object} response.BaseResponse{data=errors.Error} "Unauthorized" +// @Failure 500 {object} response.BaseResponse "Internal server error" +// @Router /api/v1/order/yearly-revenue/{year} [get] +// @Tag Order APIs +func (h *Handler) GetYearlyRevenue(c *gin.Context) { + yearParam := c.Param("year") + + // Parse the ID into a uint + year, err := strconv.Atoi(yearParam) + if err != nil { + response.ErrorWrapper(c, errors.ErrorBadRequest) + return + } + + rev, err := h.service.GetYearlyRevenue(c.Request.Context(), year) + if err != nil { + c.JSON(http.StatusInternalServerError, response.BaseResponse{ + Success: false, + Status: http.StatusInternalServerError, + Message: err.Error(), + Data: nil, + }) + return + } + + c.JSON(http.StatusOK, response.BaseResponse{ + Success: true, + Status: http.StatusOK, + Data: h.toOrderYearlyRevenueResponse(rev), + }) +} + +// GetBranchRevenue retrieves the branch-wise revenue for orders. +// @Summary Get branch-wise revenue for orders +// @Description Retrieve the branch-wise revenue for orders based on the specified parameters. +// @Accept json +// @Produce json +// @Param Authorization header string true "JWT token" +// @Param branch_id query int false "Branch ID for filtering" +// @Param start_date query string false "Start date for filtering (format: 'YYYY-MM-DD')" +// @Param end_date query string false "End date for filtering (format: 'YYYY-MM-DD')" +// @Success 200 {object} response.BaseResponse{data=[]response.OrderBranchRevenue} "Branch-wise revenue retrieved successfully" +// @Failure 400 {object} response.BaseResponse{data=errors.Error} "Bad request" +// @Failure 401 {object} response.BaseResponse{data=errors.Error} "Unauthorized" +// @Failure 500 {object} response.BaseResponse "Internal server error" +// @Router /api/v1/order/branch-revenue [get] +// @Tag Order APIs +func (h *Handler) GetBranchRevenue(c *gin.Context) { + var req request.OrderBranchRevenueParam + if err := c.ShouldBindQuery(&req); err != nil { + response.ErrorWrapper(c, errors.ErrorBadRequest) + return + } + + rev, err := h.service.GetBranchRevenue(c.Request.Context(), req.ToEntity()) + if err != nil { + c.JSON(http.StatusInternalServerError, response.BaseResponse{ + Success: false, + Status: http.StatusInternalServerError, + Message: err.Error(), + Data: nil, + }) + return + } + + c.JSON(http.StatusOK, response.BaseResponse{ + Success: true, + Status: http.StatusOK, + Data: h.toOrderBranchRevenueResponse(rev), + }) +} + +func (h *Handler) toOrderResponse(resp *entity.Order) response.Order { + orderItems := []response.OrderItem{} + for _, i := range resp.OrderItem { + orderItems = append(orderItems, response.OrderItem{ + OrderItemID: i.OrderItemID, + ItemID: i.ItemID, + ItemType: i.ItemType, + ItemName: i.ItemName, + Price: i.Price, + Qty: i.Qty, + CreatedAt: i.CreatedAt.Format(time.RFC3339), + UpdatedAt: i.CreatedAt.Format(time.RFC3339), + }) + } + + return response.Order{ + ID: resp.ID, + BranchID: resp.BranchID, + BranchName: resp.BranchName, + Amount: resp.Amount, + OrderItem: orderItems, + Status: resp.Status, + CustomerName: resp.CustomerName, + CustomerPhone: resp.CustomerPhone, + Pax: resp.Pax, + PaymentMethod: resp.Transaction.PaymentMethod, + CreatedAt: resp.CreatedAt.Format(time.RFC3339), + UpdatedAt: resp.CreatedAt.Format(time.RFC3339), + } +} + +func (h *Handler) toOrderResponseList(resp []*entity.Order, total int64, req request.OrderParam) response.OrderList { + var orders []response.Order + for _, b := range resp { + orders = append(orders, h.toOrderResponse(b)) + } + + return response.OrderList{ + Orders: orders, + Total: total, + Limit: req.Limit, + Offset: req.Offset, + } +} + +func (h *Handler) toOrderTotalRevenueResponse(rev float64, trans int64) response.OrderMonthlyRevenue { + return response.OrderMonthlyRevenue{ + TotalRevenue: rev, + TotalTransaction: trans, + } +} + +func (h *Handler) toOrderYearlyRevenueResponse(data entity.OrderYearlyRevenueList) map[int]map[string]float64 { + result := make(map[int]map[string]float64) + + // Initialize result map with 0 values for all months and item types + for i := 1; i <= 12; i++ { + result[i] = map[string]float64{ + "PRODUCT": 0, + "STUDIO": 0, + } + } + + // Populate result map with actual data + for _, v := range data { + result[v.Month][v.ItemType] = v.Amount + } + + return result +} + +func (h *Handler) toOrderBranchRevenueResponse(data entity.OrderBranchRevenueList) []response.OrderBranchRevenue { + var resp []response.OrderBranchRevenue + + for _, v := range data { + resp = append(resp, response.OrderBranchRevenue{ + BranchID: v.BranchID, + BranchName: v.BranchName, + BranchLocation: v.BranchLocation, + TotalTransaction: v.TotalTransaction, + TotalAmount: v.TotalAmount, + }) + } + + return resp +} diff --git a/internal/handlers/http/oss/oss.go b/internal/handlers/http/oss/oss.go new file mode 100644 index 0000000..95d3a7f --- /dev/null +++ b/internal/handlers/http/oss/oss.go @@ -0,0 +1,92 @@ +package oss + +import ( + "fmt" + "furtuna-be/internal/entity" + "furtuna-be/internal/handlers/response" + "furtuna-be/internal/services" + "mime/multipart" + "net/http" + + "github.com/gin-gonic/gin" +) + +const _oneMB = 1 << 20 // 1MB +const _maxUploadSizeMB = 2 * _oneMB +const _folderName = "/file" + +type OssHandler struct { + ossService services.OSSService +} + +func (h *OssHandler) Route(group *gin.RouterGroup, jwt gin.HandlerFunc) { + route := group.Group("/file") + + route.POST("/upload", h.UploadFile, jwt) +} + +func NewOssHandler(ossService services.OSSService) *OssHandler { + return &OssHandler{ + ossService: ossService, + } +} + +// UploadFile handles the uploading of a file to OSS. +// @Summary Upload a file to OSS +// @Description Upload a file to Alibaba Cloud OSS with the provided details. +// @Accept mpfd +// @Produce json +// @Param Authorization header string true "JWT token" +// @Param file formData file true "File to upload (max size: 2MB)" +// @Success 200 {object} response.BaseResponse{data=entity.UploadFileResponse} "File uploaded successfully" +// @Failure 400 {object} response.BaseResponse{data=errors.Error} "Bad request" +// @Failure 401 {object} response.BaseResponse{data=errors.Error} "Unauthorized" +// @Tags File Upload API +// @Router /api/v1/file/upload [post] +func (h *OssHandler) UploadFile(c *gin.Context) { + // Get the oss file from the request form + file, err := c.FormFile("file") + if err != nil { + c.JSON(http.StatusBadRequest, response.BaseResponse{ + ErrorMessage: "Failed to retrieve the file", + }) + return + } + + // Check if the uploaded file is an image (photo) + //if !isPDFFile(file) { + // c.JSON(http.StatusBadRequest, response.BaseResponse{ + // ErrorMessage: "Only image files are allowed", + // }) + // return + //} + + // Check if the file size is not greater than the maximum allowed size + if file.Size > _maxUploadSizeMB { + c.JSON(http.StatusBadRequest, response.BaseResponse{ + ErrorMessage: fmt.Sprintf("The file is too big. The maximum size is %d", _maxUploadSizeMB/_oneMB), + }) + return + } + + // Call the service to oss the file to Alibaba Cloud OSS + ret, err := h.ossService.UploadFile(c, &entity.UploadFileRequest{ + FileHeader: file, + FolderName: _folderName, + }) + + if err != nil { + response.ErrorWrapper(c, err) + return + } + + c.JSON(http.StatusOK, response.BaseResponse{ + Data: ret, + Success: true, + }) +} + +func isPDFFile(file *multipart.FileHeader) bool { + contentType := file.Header.Get("Content-Type") + return contentType == "application/pdf" +} diff --git a/internal/handlers/http/partner/partner.go b/internal/handlers/http/partner/partner.go new file mode 100644 index 0000000..5d1b1e4 --- /dev/null +++ b/internal/handlers/http/partner/partner.go @@ -0,0 +1,265 @@ +package partner + +import ( + "furtuna-be/internal/common/errors" + "furtuna-be/internal/entity" + "furtuna-be/internal/handlers/request" + "furtuna-be/internal/handlers/response" + "furtuna-be/internal/middlewares" + "furtuna-be/internal/services" + "net/http" + "strconv" + "time" + + "github.com/gin-gonic/gin" + "github.com/go-playground/validator/v10" +) + +type Handler struct { + service services.Partner +} + +func (h *Handler) Route(group *gin.RouterGroup, jwt gin.HandlerFunc) { + route := group.Group("/partner") + isSuperAdmin := middlewares.SuperAdminMiddleware() + + route.POST("/", jwt, isSuperAdmin, h.Create) + route.GET("/list", jwt, isSuperAdmin, h.GetAll) + route.PUT("/:id", jwt, isSuperAdmin, h.Update) + route.GET("/:id", jwt, isSuperAdmin, h.GetByID) + route.DELETE("/:id", jwt, isSuperAdmin, h.Delete) +} + +func NewHandler(service services.Partner) *Handler { + return &Handler{ + service: service, + } +} + +// Create handles the creation of a new Partner. +// @Summary Create a new Partner +// @Description Create a new Partner based on the provided data. +// @Accept json +// @Produce json +// @Param Authorization header string true "JWT token" +// @Param req body request.Partner true "New Partner details" +// @Success 200 {object} response.BaseResponse{data=response.Partner} "Partner created successfully" +// @Failure 400 {object} response.BaseResponse{data=errors.Error} "Bad request" +// @Failure 401 {object} response.BaseResponse{data=errors.Error} "Unauthorized" +// @Router /api/v1/Partner [post] +// @Tags Partner APIs +func (h *Handler) Create(c *gin.Context) { + ctx := request.GetMyContext(c) + + var req request.Partner + if err := c.ShouldBindJSON(&req); err != nil { + response.ErrorWrapper(c, errors.ErrorBadRequest) + return + } + + validate := validator.New() + if err := validate.Struct(req); err != nil { + response.ErrorWrapper(c, err) + return + } + + res, err := h.service.Create(ctx, req.ToEntity()) + + if err != nil { + response.ErrorWrapper(c, err) + return + } + + c.JSON(http.StatusOK, response.BaseResponse{ + Success: true, + Status: http.StatusOK, + Data: h.toPartnerResponse(res), + }) +} + +// Update handles the update of an existing Partner. +// @Summary Update an existing Partner +// @Description Update the details of an existing Partner based on the provided ID. +// @Accept json +// @Produce json +// @Param Authorization header string true "JWT token" +// @Param id path int64 true "Partner ID to update" +// @Param req body request.Partner true "Updated Partner details" +// @Success 200 {object} response.BaseResponse{data=response.Partner} "Partner updated successfully" +// @Failure 400 {object} response.BaseResponse{data=errors.Error} "Bad request" +// @Failure 401 {object} response.BaseResponse{data=errors.Error} "Unauthorized" +// @Router /api/v1/Partner/{id} [put] +// @Tags Partner APIs +func (h *Handler) Update(c *gin.Context) { + ctx := request.GetMyContext(c) + + id := c.Param("id") + + PartnerID, err := strconv.ParseInt(id, 10, 64) + if err != nil { + response.ErrorWrapper(c, errors.ErrorBadRequest) + return + } + + var req request.Partner + if err := c.ShouldBindJSON(&req); err != nil { + response.ErrorWrapper(c, errors.ErrorBadRequest) + return + } + + validate := validator.New() + if err := validate.Struct(req); err != nil { + response.ErrorWrapper(c, err) + return + } + + updatedPartner, err := h.service.Update(ctx, PartnerID, req.ToEntity()) + if err != nil { + response.ErrorWrapper(c, err) + return + } + + c.JSON(http.StatusOK, response.BaseResponse{ + Success: true, + Status: http.StatusOK, + Data: h.toPartnerResponse(updatedPartner), + }) +} + +// GetAll retrieves a list of Partneres. +// @Summary Get a list of Partneres +// @Description Get a paginated list of Partneres based on query parameters. +// @Accept json +// @Produce json +// @Param Authorization header string true "JWT token" +// @Param Limit query int false "Number of items to retrieve (default 10)" +// @Param Offset query int false "Offset for pagination (default 0)" +// @Success 200 {object} response.BaseResponse{data=response.PartnerList} "List of Partneres" +// @Failure 400 {object} response.BaseResponse{data=errors.Error} "Bad request" +// @Failure 401 {object} response.BaseResponse{data=errors.Error} "Unauthorized" +// @Router /api/v1/Partner/list [get] +// @Tags Partner APIs +func (h *Handler) GetAll(c *gin.Context) { + var req request.PartnerParam + if err := c.ShouldBindQuery(&req); err != nil { + response.ErrorWrapper(c, errors.ErrorBadRequest) + return + } + + Partners, total, err := h.service.GetAll(c.Request.Context(), req.ToEntity()) + if err != nil { + response.ErrorWrapper(c, err) + return + } + + c.JSON(http.StatusOK, response.BaseResponse{ + Success: true, + Status: http.StatusOK, + Data: h.toPartnerResponseList(Partners, int64(total), req), + }) +} + +// Delete handles the deletion of a Partner by ID. +// @Summary Delete a Partner by ID +// @Description Delete a Partner based on the provided ID. +// @Accept json +// @Produce json +// @Param Authorization header string true "JWT token" +// @Param id path int64 true "Partner ID to delete" +// @Success 200 {object} response.BaseResponse "Partner deleted successfully" +// @Failure 400 {object} response.BaseResponse{data=errors.Error} "Bad request" +// @Failure 401 {object} response.BaseResponse{data=errors.Error} "Unauthorized" +// @Router /api/v1/Partner/{id} [delete] +// @Tags Partner APIs +func (h *Handler) Delete(c *gin.Context) { + ctx := request.GetMyContext(c) + id := c.Param("id") + + // Parse the ID into a uint + PartnerID, err := strconv.ParseInt(id, 10, 64) + if err != nil { + response.ErrorWrapper(c, errors.ErrorBadRequest) + return + } + + err = h.service.Delete(ctx, PartnerID) + if err != nil { + c.JSON(http.StatusInternalServerError, response.BaseResponse{ + Success: false, + Status: http.StatusInternalServerError, + Message: err.Error(), + Data: nil, + }) + return + } + + c.JSON(http.StatusOK, response.BaseResponse{ + Success: true, + Status: http.StatusOK, + Data: nil, + }) +} + +// GetByID retrieves details of a specific Partner by ID. +// @Summary Get details of a Partner by ID +// @Description Get details of a Partner based on the provided ID. +// @Accept json +// @Produce json +// @Param Authorization header string true "JWT token" +// @Param id path int64 true "Partner ID to retrieve" +// @Success 200 {object} response.BaseResponse{data=response.Partner} "Partner details" +// @Failure 400 {object} response.BaseResponse{data=errors.Error} "Bad request" +// @Failure 401 {object} response.BaseResponse{data=errors.Error} "Unauthorized" +// @Router /api/v1/Partner/{id} [get] +// @Tags Partner APIs +func (h *Handler) GetByID(c *gin.Context) { + id := c.Param("id") + + // Parse the ID into a uint + PartnerID, err := strconv.ParseInt(id, 10, 64) + if err != nil { + response.ErrorWrapper(c, errors.ErrorBadRequest) + return + } + + res, err := h.service.GetByID(c.Request.Context(), PartnerID) + if err != nil { + c.JSON(http.StatusInternalServerError, response.BaseResponse{ + Success: false, + Status: http.StatusInternalServerError, + Message: err.Error(), + Data: nil, + }) + return + } + + c.JSON(http.StatusOK, response.BaseResponse{ + Success: true, + Status: http.StatusOK, + Data: h.toPartnerResponse(res), + }) +} + +func (h *Handler) toPartnerResponse(resp *entity.Partner) response.Partner { + return response.Partner{ + ID: &resp.ID, + Name: resp.Name, + Status: resp.Status, + CreatedAt: resp.CreatedAt.Format(time.RFC3339), + UpdatedAt: resp.CreatedAt.Format(time.RFC3339), + } +} + +func (h *Handler) toPartnerResponseList(resp []*entity.Partner, total int64, req request.PartnerParam) response.PartnerList { + var Partneres []response.Partner + for _, b := range resp { + Partneres = append(Partneres, h.toPartnerResponse(b)) + } + + return response.PartnerList{ + Partners: Partneres, + Total: total, + Limit: req.Limit, + Offset: req.Offset, + } +} diff --git a/internal/handlers/http/product/product.go b/internal/handlers/http/product/product.go new file mode 100644 index 0000000..7cc6dbd --- /dev/null +++ b/internal/handlers/http/product/product.go @@ -0,0 +1,269 @@ +package product + +import ( + "furtuna-be/internal/common/errors" + "furtuna-be/internal/entity" + "furtuna-be/internal/handlers/request" + "furtuna-be/internal/handlers/response" + "furtuna-be/internal/services" + "net/http" + "strconv" + "time" + + "github.com/gin-gonic/gin" + "github.com/go-playground/validator/v10" +) + +type Handler struct { + service services.Product +} + +func (h *Handler) Route(group *gin.RouterGroup, jwt gin.HandlerFunc) { + route := group.Group("/product") + + route.POST("/", jwt, h.Create) + route.GET("/list", jwt, h.GetAll) + route.PUT("/:id", jwt, h.Update) + route.GET("/:id", jwt, h.GetByID) + route.DELETE("/:id", jwt, h.Delete) +} + +func NewHandler(service services.Product) *Handler { + return &Handler{ + service: service, + } +} + +// Create handles the creation of a new product. +// @Summary Create a new product +// @Description Create a new product with the provided details. +// @Accept json +// @Produce json +// @Param Authorization header string true "JWT token" +// @Param req body request.Product true "Product details to create" +// @Success 200 {object} response.BaseResponse{data=response.Product} "Product created successfully" +// @Failure 400 {object} response.BaseResponse{data=errors.Error} "Bad request" +// @Failure 401 {object} response.BaseResponse{data=errors.Error} "Unauthorized" +// @Tags Product APIs +// @Router /api/v1/product/ [post] +func (h *Handler) Create(c *gin.Context) { + ctx := request.GetMyContext(c) + + var req request.Product + if err := c.ShouldBindJSON(&req); err != nil { + response.ErrorWrapper(c, errors.ErrorBadRequest) + return + } + + validate := validator.New() + if err := validate.Struct(req); err != nil { + response.ErrorWrapper(c, err) + return + } + + res, err := h.service.Create(ctx, req.ToEntity()) + + if err != nil { + response.ErrorWrapper(c, err) + return + } + + c.JSON(http.StatusOK, response.BaseResponse{ + Success: true, + Status: http.StatusOK, + Data: h.toProductResponse(res), + }) +} + +// Update handles the update of an existing product. +// @Summary Update an existing product +// @Description Update the details of an existing product based on the provided ID. +// @Accept json +// @Produce json +// @Param Authorization header string true "JWT token" +// @Param id path int64 true "Product ID to update" +// @Param req body request.Product true "Updated product details" +// @Success 200 {object} response.BaseResponse{data=response.Product} "Product updated successfully" +// @Failure 400 {object} response.BaseResponse{data=errors.Error} "Bad request" +// @Failure 401 {object} response.BaseResponse{data=errors.Error} "Unauthorized" +// @Tags Product APIs +// @Router /api/v1/product/{id} [put] +func (h *Handler) Update(c *gin.Context) { + ctx := request.GetMyContext(c) + + id := c.Param("id") + + productID, err := strconv.ParseInt(id, 10, 64) + if err != nil { + response.ErrorWrapper(c, errors.ErrorBadRequest) + return + } + + var req request.Product + if err := c.ShouldBindJSON(&req); err != nil { + response.ErrorWrapper(c, errors.ErrorBadRequest) + return + } + + validate := validator.New() + if err := validate.Struct(req); err != nil { + response.ErrorWrapper(c, err) + return + } + + updatedProduct, err := h.service.Update(ctx, productID, req.ToEntity()) + if err != nil { + response.ErrorWrapper(c, err) + return + } + + c.JSON(http.StatusOK, response.BaseResponse{ + Success: true, + Status: http.StatusOK, + Data: h.toProductResponse(updatedProduct), + }) +} + +// GetAll retrieves a list of products. +// @Summary Get a list of products +// @Description Get a paginated list of products based on query parameters. +// @Accept json +// @Produce json +// @Param Authorization header string true "JWT token" +// @Param Limit query int false "Number of items to retrieve (default 10)" +// @Param Offset query int false "Offset for pagination (default 0)" +// @Success 200 {object} response.BaseResponse{data=response.ProductList} "List of products" +// @Failure 400 {object} response.BaseResponse{data=errors.Error} "Bad request" +// @Failure 401 {object} response.BaseResponse{data=errors.Error} "Unauthorized" +// @Router /api/v1/product/list [get] +// @Tags Product APIs +func (h *Handler) GetAll(c *gin.Context) { + var req request.ProductParam + if err := c.ShouldBindQuery(&req); err != nil { + response.ErrorWrapper(c, errors.ErrorBadRequest) + return + } + + products, total, err := h.service.GetAll(c.Request.Context(), req.ToEntity()) + if err != nil { + response.ErrorWrapper(c, err) + return + } + + c.JSON(http.StatusOK, response.BaseResponse{ + Success: true, + Status: http.StatusOK, + Data: h.toProductResponseList(products, int64(total), req), + }) +} + +// Delete handles the deletion of a product by ID. +// @Summary Delete a product by ID +// @Description Delete a product based on the provided ID. +// @Accept json +// @Produce json +// @Param Authorization header string true "JWT token" +// @Param id path int64 true "Product ID to delete" +// @Success 200 {object} response.BaseResponse "Product deleted successfully" +// @Failure 400 {object} response.BaseResponse{data=errors.Error} "Bad request" +// @Failure 401 {object} response.BaseResponse{data=errors.Error} "Unauthorized" +// @Router /api/v1/product/{id} [delete] +// @Tags Product APIs +func (h *Handler) Delete(c *gin.Context) { + ctx := request.GetMyContext(c) + id := c.Param("id") + + // Parse the ID into a uint + productID, err := strconv.ParseInt(id, 10, 64) + if err != nil { + response.ErrorWrapper(c, errors.ErrorBadRequest) + return + } + + err = h.service.Delete(ctx, productID) + if err != nil { + c.JSON(http.StatusInternalServerError, response.BaseResponse{ + Success: false, + Status: http.StatusInternalServerError, + Message: err.Error(), + Data: nil, + }) + return + } + + c.JSON(http.StatusOK, response.BaseResponse{ + Success: true, + Status: http.StatusOK, + Data: nil, + }) +} + +// GetByID retrieves details of a specific product by ID. +// @Summary Get details of a product by ID +// @Description Get details of a product based on the provided ID. +// @Accept json +// @Produce json +// @Param Authorization header string true "JWT token" +// @Param id path int64 true "Product ID to retrieve" +// @Success 200 {object} response.BaseResponse{data=response.Product} "Product details" +// @Failure 400 {object} response.BaseResponse{data=errors.Error} "Bad request" +// @Failure 401 {object} response.BaseResponse{data=errors.Error} "Unauthorized" +// @Router /api/v1/product/{id} [get] +// @Tags Product APIs +func (h *Handler) GetByID(c *gin.Context) { + id := c.Param("id") + + // Parse the ID into a uint + productID, err := strconv.ParseInt(id, 10, 64) + if err != nil { + response.ErrorWrapper(c, errors.ErrorBadRequest) + return + } + + res, err := h.service.GetByID(c.Request.Context(), productID) + if err != nil { + c.JSON(http.StatusInternalServerError, response.BaseResponse{ + Success: false, + Status: http.StatusInternalServerError, + Message: err.Error(), + Data: nil, + }) + return + } + + c.JSON(http.StatusOK, response.BaseResponse{ + Success: true, + Status: http.StatusOK, + Data: h.toProductResponse(res), + }) +} + +func (h *Handler) toProductResponse(resp *entity.Product) response.Product { + return response.Product{ + ID: resp.ID, + Name: resp.Name, + Type: resp.Type, + Price: resp.Price, + Status: resp.Status, + Description: resp.Description, + Image: resp.Image, + BranchID: resp.BranchID, + StockQty: resp.StockQty, + CreatedAt: resp.CreatedAt.Format(time.RFC3339), + UpdatedAt: resp.CreatedAt.Format(time.RFC3339), + } +} + +func (h *Handler) toProductResponseList(resp []*entity.Product, total int64, req request.ProductParam) response.ProductList { + var products []response.Product + for _, b := range resp { + products = append(products, h.toProductResponse(b)) + } + + return response.ProductList{ + Products: products, + Total: total, + Limit: req.Limit, + Offset: req.Offset, + } +} diff --git a/internal/handlers/http/studio/studio.go b/internal/handlers/http/studio/studio.go new file mode 100644 index 0000000..d906b27 --- /dev/null +++ b/internal/handlers/http/studio/studio.go @@ -0,0 +1,232 @@ +package studio + +import ( + "encoding/json" + "furtuna-be/internal/common/errors" + "furtuna-be/internal/entity" + "furtuna-be/internal/handlers/request" + "furtuna-be/internal/handlers/response" + "furtuna-be/internal/services" + "net/http" + "strconv" + "time" + + "github.com/gin-gonic/gin" + "github.com/go-playground/validator/v10" +) + +type StudioHandler struct { + service services.Studio +} + +func (h *StudioHandler) Route(group *gin.RouterGroup, jwt gin.HandlerFunc) { + route := group.Group("/studio") + + route.POST("/", jwt, h.Create) + route.PUT("/:id", jwt, h.Update) + route.GET("/:id", jwt, h.GetByID) + route.GET("/search", jwt, h.Search) +} + +func NewStudioHandler(service services.Studio) *StudioHandler { + return &StudioHandler{ + service: service, + } +} + +// Create handles the creation of a new studio. +// @Summary Create a new studio +// @Description Create a new studio based on the provided details. +// @Accept json +// @Produce json +// @Param Authorization header string true "JWT token" +// @Param req body request.Studio true "New studio details" +// @Success 200 {object} response.BaseResponse{data=response.Studio} "Studio created successfully" +// @Failure 400 {object} response.BaseResponse{data=errors.Error} "Bad request" +// @Failure 401 {object} response.BaseResponse{data=errors.Error} "Unauthorized" +// @Router /api/v1/studio [post] +// @Tags Studio APIs +func (h *StudioHandler) Create(c *gin.Context) { + ctx := request.GetMyContext(c) + + var req request.Studio + if err := c.ShouldBindJSON(&req); err != nil { + response.ErrorWrapper(c, errors.ErrorBadRequest) + return + } + + validate := validator.New() + if err := validate.Struct(req); err != nil { + response.ErrorWrapper(c, err) + return + } + + res, err := h.service.Create(ctx, req.ToEntity()) + + if err != nil { + response.ErrorWrapper(c, err) + return + } + + c.JSON(http.StatusOK, response.BaseResponse{ + Success: true, + Status: http.StatusOK, + Data: h.toStudioResponse(res), + }) +} + +// Update handles the update of an existing studio. +// @Summary Update an existing studio +// @Description Update the details of an existing studio based on the provided ID. +// @Accept json +// @Produce json +// @Param Authorization header string true "JWT token" +// @Param id path int64 true "Studio ID to update" +// @Param req body request.Studio true "Updated studio details" +// @Success 200 {object} response.BaseResponse{data=response.Studio} "Studio updated successfully" +// @Failure 400 {object} response.BaseResponse{data=errors.Error} "Bad request" +// @Failure 401 {object} response.BaseResponse{data=errors.Error} "Unauthorized" +// @Router /api/v1/studio/{id} [put] +// @Tags Studio APIs +func (h *StudioHandler) Update(c *gin.Context) { + ctx := request.GetMyContext(c) + + id := c.Param("id") + + studioID, err := strconv.ParseInt(id, 10, 64) + if err != nil { + response.ErrorWrapper(c, errors.ErrorBadRequest) + return + } + + var req request.Studio + if err := c.ShouldBindJSON(&req); err != nil { + response.ErrorWrapper(c, errors.ErrorBadRequest) + return + } + + validate := validator.New() + if err := validate.Struct(req); err != nil { + response.ErrorWrapper(c, err) + return + } + + updatedStudio, err := h.service.Update(ctx, studioID, req.ToEntity()) + if err != nil { + response.ErrorWrapper(c, err) + return + } + + c.JSON(http.StatusOK, response.BaseResponse{ + Success: true, + Status: http.StatusOK, + Data: h.toStudioResponse(updatedStudio), + }) +} + +// Search retrieves a list of studios based on search criteria. +// @Summary Search for studios +// @Description Search for studios based on query parameters. +// @Accept json +// @Produce json +// @Param Authorization header string true "JWT token" +// @Param Name query string false "Studio name for search" +// @Param Status query string false "Studio status for search" +// @Param Limit query int false "Number of items to retrieve (default 10)" +// @Param Offset query int false "Offset for pagination (default 0)" +// @Success 200 {object} response.BaseResponse{data=response.StudioList} "List of studios" +// @Failure 400 {object} response.BaseResponse{data=errors.Error} "Bad request" +// @Failure 401 {object} response.BaseResponse{data=errors.Error} "Unauthorized" +// @Router /api/v1/studio/search [get] +// @Tags Studio APIs +func (h *StudioHandler) Search(c *gin.Context) { + var req request.StudioParam + if err := c.ShouldBindQuery(&req); err != nil { + response.ErrorWrapper(c, errors.ErrorBadRequest) + return + } + + studios, total, err := h.service.Search(c.Request.Context(), req.ToEntity()) + if err != nil { + response.ErrorWrapper(c, err) + return + } + + c.JSON(http.StatusOK, response.BaseResponse{ + Success: true, + Status: http.StatusOK, + Data: h.toStudioResponseList(studios, int64(total), req), + }) +} + +// GetByID retrieves details of a specific studio by ID. +// @Summary Get details of a studio by ID +// @Description Get details of a studio based on the provided ID. +// @Accept json +// @Produce json +// @Param Authorization header string true "JWT token" +// @Param id path int64 true "Studio ID to retrieve" +// @Success 200 {object} response.BaseResponse{data=response.Studio} "Studio details" +// @Failure 400 {object} response.BaseResponse{data=errors.Error} "Bad request" +// @Failure 401 {object} response.BaseResponse{data=errors.Error} "Unauthorized" +// @Router /api/v1/studio/{id} [get] +// @Tags Studio APIs +func (h *StudioHandler) GetByID(c *gin.Context) { + id := c.Param("id") + + studioID, err := strconv.ParseInt(id, 10, 64) + if err != nil { + response.ErrorWrapper(c, errors.ErrorBadRequest) + return + } + + res, err := h.service.GetByID(c.Request.Context(), studioID) + if err != nil { + c.JSON(http.StatusInternalServerError, response.BaseResponse{ + Success: false, + Status: http.StatusInternalServerError, + Message: err.Error(), + Data: nil, + }) + return + } + + c.JSON(http.StatusOK, response.BaseResponse{ + Success: true, + Status: http.StatusOK, + Data: h.toStudioResponse(res), + }) +} + +func (h *StudioHandler) toStudioResponse(resp *entity.Studio) response.Studio { + metadata := make(map[string]interface{}) + if err := json.Unmarshal(resp.Metadata, &metadata); err != nil { + //TODO taufanvps + // Handle the error if the metadata cannot be unmarshaled. + } + + return response.Studio{ + ID: &resp.ID, + BranchId: &resp.BranchId, + Name: resp.Name, + Status: string(resp.Status), + Price: resp.Price, + Metadata: metadata, + CreatedAt: resp.CreatedAt.Format(time.RFC3339), + UpdatedAt: resp.CreatedAt.Format(time.RFC3339), + } +} + +func (h *StudioHandler) toStudioResponseList(resp []*entity.Studio, total int64, req request.StudioParam) response.StudioList { + var studios []response.Studio + for _, b := range resp { + studios = append(studios, h.toStudioResponse(b)) + } + + return response.StudioList{ + Studios: studios, + Total: total, + Limit: req.Limit, + Offset: req.Offset, + } +} diff --git a/internal/handlers/http/user/user.go b/internal/handlers/http/user/user.go new file mode 100644 index 0000000..52e330e --- /dev/null +++ b/internal/handlers/http/user/user.go @@ -0,0 +1,288 @@ +package user + +import ( + "net/http" + "strconv" + "time" + + "github.com/gin-gonic/gin" + "github.com/go-playground/validator/v10" + + "furtuna-be/internal/common/errors" + "furtuna-be/internal/entity" + "furtuna-be/internal/handlers/request" + "furtuna-be/internal/handlers/response" + "furtuna-be/internal/services" +) + +type Handler struct { + service services.User +} + +func (h *Handler) Route(group *gin.RouterGroup, jwt gin.HandlerFunc) { + route := group.Group("/user") + + route.POST("/", jwt, h.Create) + route.GET("/list", jwt, h.GetAll) + route.GET("/:id", jwt, h.GetByID) + route.PUT("/:id", jwt, h.Update) + route.DELETE("/:id", jwt, h.Delete) +} + +func NewHandler(service services.User) *Handler { + return &Handler{ + service: service, + } +} + +// Create handles the creation of a new user. +// @Summary Create a new user +// @Description Create a new user based on the provided data. +// @Accept json +// @Produce json +// @Param Authorization header string true "JWT token" +// @Param req body request.User true "New user details" +// @Success 200 {object} response.BaseResponse{data=response.User} "User created successfully" +// @Failure 400 {object} response.BaseResponse{data=errors.Error} "Bad request" +// @Failure 401 {object} response.BaseResponse{data=errors.Error} "Unauthorized" +// @Router /api/v1/user [post] +// @Tags User APIs +func (h *Handler) Create(c *gin.Context) { + ctx := request.GetMyContext(c) + + var req request.User + if err := c.ShouldBindJSON(&req); err != nil { + response.ErrorWrapper(c, errors.ErrorBadRequest) + return + } + + if err := req.Validate(); err != nil { + response.ErrorWrapper(c, errors.ErrorInvalidRequest) + return + } + + res, err := h.service.Create(ctx, req.ToEntity()) + + if err != nil { + response.ErrorWrapper(c, err) + return + } + + resp := response.User{ + ID: res.ID, + Name: res.Name, + Email: res.Email, + RoleID: int64(res.RoleID), + PartnerID: res.PartnerID, + Status: string(res.Status), + } + + c.JSON(http.StatusOK, response.BaseResponse{ + Success: true, + Status: http.StatusOK, + Data: resp, + }) +} + +// Update handles the update of an existing user. +// @Summary Update an existing user +// @Description Update the details of an existing user based on the provided ID. +// @Accept json +// @Produce json +// @Param Authorization header string true "JWT token" +// @Param id path int64 true "User ID to update" +// @Param req body request.User true "Updated user details" +// @Success 200 {object} response.BaseResponse{data=response.User} "User updated successfully" +// @Failure 400 {object} response.BaseResponse{data=errors.Error} "Bad request" +// @Failure 401 {object} response.BaseResponse{data=errors.Error} "Unauthorized" +// @Router /api/v1/user/{id} [put] +// @Tags User APIs +func (h *Handler) Update(c *gin.Context) { + ctx := request.GetMyContext(c) + + if !ctx.IsSuperAdmin() { + response.ErrorWrapper(c, errors.ErrorUnauthorized) + return + } + + id := c.Param("id") + + userID, err := strconv.ParseInt(id, 10, 64) + if err != nil { + response.ErrorWrapper(c, errors.ErrorBadRequest) + return + } + + var req request.User + if err := c.ShouldBindJSON(&req); err != nil { + response.ErrorWrapper(c, errors.ErrorBadRequest) + return + } + + validate := validator.New() + if err := validate.Struct(req); err != nil { + response.ErrorWrapper(c, err) + return + } + + updatedUser, err := h.service.Update(ctx, userID, req.ToEntity()) + if err != nil { + response.ErrorWrapper(c, err) + return + } + + c.JSON(http.StatusOK, response.BaseResponse{ + Success: true, + Status: http.StatusOK, + Data: h.toUserResponse(updatedUser), + }) +} + +// GetAll retrieves a list of users. +// @Summary Get a list of users +// @Description Get a paginated list of users based on query parameters. +// @Accept json +// @Produce json +// @Param Authorization header string true "JWT token" +// @Param Limit query int false "Number of items to retrieve (default 10)" +// @Param Offset query int false "Offset for pagination (default 0)" +// @Success 200 {object} response.BaseResponse{data=response.UserList} "List of users" +// @Failure 400 {object} response.BaseResponse{data=errors.Error} "Bad request" +// @Failure 401 {object} response.BaseResponse{data=errors.Error} "Unauthorized" +// @Router /api/v1/user/list [get] +// @Tags User APIs +func (h *Handler) GetAll(c *gin.Context) { + ctx := request.GetMyContext(c) + var req request.UserParam + if err := c.ShouldBindQuery(&req); err != nil { + + } + + if !ctx.IsSuperAdmin() { + response.ErrorWrapper(c, errors.ErrorUnauthorized) + return + } + + users, total, err := h.service.GetAll(ctx, req.ToEntity()) + if err != nil { + response.ErrorWrapper(c, err) + return + } + + c.JSON(http.StatusOK, response.BaseResponse{ + Success: true, + Status: http.StatusOK, + Data: h.toUserResponseList(users, int64(total), req), + }) +} + +// GetByID retrieves details of a specific user by ID. +// @Summary Get details of a user by ID +// @Description Get details of a user based on the provided ID. +// @Accept json +// @Produce json +// @Param Authorization header string true "JWT token" +// @Param id path int64 true "User ID to retrieve" +// @Success 200 {object} response.BaseResponse{data=response.User} "User details" +// @Failure 400 {object} response.BaseResponse{data=errors.Error} "Bad request" +// @Failure 401 {object} response.BaseResponse{data=errors.Error} "Unauthorized" +// @Router /api/v1/user/{id} [get] +// @Tags User APIs +func (h *Handler) GetByID(c *gin.Context) { + ctx := request.GetMyContext(c) + id := c.Param("id") + + // Parse the ID into a uint + userID, err := strconv.ParseInt(id, 10, 64) + if err != nil { + response.ErrorWrapper(c, errors.ErrorBadRequest) + return + } + + res, err := h.service.GetByID(ctx, userID) + if err != nil { + c.JSON(http.StatusInternalServerError, response.BaseResponse{ + Success: false, + Status: http.StatusInternalServerError, + Message: err.Error(), + Data: nil, + }) + return + } + + c.JSON(http.StatusOK, response.BaseResponse{ + Success: true, + Status: http.StatusOK, + Data: h.toUserResponse(res), + }) +} + +// Delete handles the deletion of a user by ID. +// @Summary Delete a user by ID +// @Description Delete a user based on the provided ID. +// @Accept json +// @Produce json +// @Param Authorization header string true "JWT token" +// @Param id path int64 true "User ID to delete" +// @Success 200 {object} response.BaseResponse "User deleted successfully" +// @Failure 400 {object} response.BaseResponse{data=errors.Error} "Bad request" +// @Failure 401 {object} response.BaseResponse{data=errors.Error} "Unauthorized" +// @Router /api/v1/user/{id} [delete] +// @Tags User APIs +func (h *Handler) Delete(c *gin.Context) { + ctx := request.GetMyContext(c) + id := c.Param("id") + + // Parse the ID into a uint + userID, err := strconv.ParseInt(id, 10, 64) + if err != nil { + response.ErrorWrapper(c, errors.ErrorBadRequest) + return + } + + err = h.service.Delete(ctx, userID) + if err != nil { + c.JSON(http.StatusInternalServerError, response.BaseResponse{ + Success: false, + Status: http.StatusInternalServerError, + Message: err.Error(), + Data: nil, + }) + return + } + + c.JSON(http.StatusOK, response.BaseResponse{ + Success: true, + Status: http.StatusOK, + Data: nil, + }) +} + +func (h *Handler) toUserResponse(resp *entity.User) response.User { + return response.User{ + ID: resp.ID, + Name: resp.Name, + Email: resp.Email, + Status: string(resp.Status), + RoleID: int64(resp.RoleID), + RoleName: resp.RoleName, + PartnerID: resp.PartnerID, + BranchName: resp.BranchName, + CreatedAt: resp.CreatedAt.Format(time.RFC3339), + UpdatedAt: resp.CreatedAt.Format(time.RFC3339), + } +} + +func (h *Handler) toUserResponseList(resp []*entity.User, total int64, req request.UserParam) response.UserList { + var users []response.User + for _, b := range resp { + users = append(users, h.toUserResponse(b)) + } + + return response.UserList{ + Users: users, + Total: total, + Limit: req.Limit, + Offset: req.Offset, + } +} diff --git a/internal/handlers/request/auth.go b/internal/handlers/request/auth.go new file mode 100644 index 0000000..2999abe --- /dev/null +++ b/internal/handlers/request/auth.go @@ -0,0 +1,15 @@ +package request + +type LoginRequest struct { + Email string `json:"email"` + Password string `json:"password"` +} + +type ResetPasswordRequest struct { + Email string `json:"email" validate:"required,email"` +} + +type ResetPasswordChangeRequest struct { + Token string `json:"token" validate:"required"` + Password string `json:"password" validate:"required"` +} diff --git a/internal/handlers/request/branch.go b/internal/handlers/request/branch.go new file mode 100644 index 0000000..43fb688 --- /dev/null +++ b/internal/handlers/request/branch.go @@ -0,0 +1,36 @@ +package request + +import ( + "furtuna-be/internal/constants/branch" + "furtuna-be/internal/entity" +) + +type BranchParam struct { + Search string `form:"search" json:"search" example:"Ketua Umum"` + Name string `form:"name" json:"name" example:"Ketua Umum"` + Limit int `form:"limit" json:"limit" example:"10"` + Offset int `form:"offset" json:"offset" example:"0"` +} + +func (p *BranchParam) ToEntity() entity.BranchSearch { + return entity.BranchSearch{ + Search: p.Search, + Name: p.Name, + Limit: p.Limit, + Offset: p.Offset, + } +} + +type Branch struct { + Name string `json:"name" validate:"required"` + Location string `json:"location" validate:"required"` + Status branch.BranchStatus `json:"status"` +} + +func (e *Branch) ToEntity() *entity.Branch { + return &entity.Branch{ + Name: e.Name, + Location: e.Location, + Status: e.Status, + } +} diff --git a/internal/handlers/request/context.go b/internal/handlers/request/context.go new file mode 100644 index 0000000..ebcf161 --- /dev/null +++ b/internal/handlers/request/context.go @@ -0,0 +1,21 @@ +package request + +import ( + "furtuna-be/internal/common/mycontext" + "github.com/gin-gonic/gin" +) + +func GetMyContext(c *gin.Context) mycontext.Context { + rawCtx, exists := c.Get("myCtx") + if !exists { + // handle missing context + return mycontext.NewContext(c) + } + + myCtx, ok := rawCtx.(mycontext.Context) + if !ok { + return mycontext.NewContext(c) + } + + return myCtx +} diff --git a/internal/handlers/request/event.go b/internal/handlers/request/event.go new file mode 100644 index 0000000..11517d2 --- /dev/null +++ b/internal/handlers/request/event.go @@ -0,0 +1,86 @@ +package request + +import ( + "fmt" + "time" + + "github.com/go-playground/validator/v10" + + "furtuna-be/internal/entity" +) + +type EventParam struct { + Name string `form:"name" json:"name" example:"Ketua Umum"` + Limit int `form:"limit" json:"limit" example:"10"` + Offset int `form:"offset" json:"offset" example:"0"` +} + +func (p *EventParam) ToEntity() entity.EventSearch { + return entity.EventSearch{ + Name: p.Name, + Limit: p.Limit, + Offset: p.Offset, + } +} + +type Event struct { + Name string `json:"name" validate:"required"` + Description string `json:"description"` + StartDate string `json:"start_date" validate:"required"` + StartTime string `json:"start_time"` + EndDate string `json:"end_date" validate:"required"` + EndTime string `json:"end_time"` + Location string `json:"location" validate:"required"` + Level string `json:"level" validate:"required"` + Included []string `json:"included"` + Price float64 `json:"price"` + Paid bool `json:"paid"` + Status entity.Status `json:"status"` + LocationID int64 `json:"location_id"` + startDateTime time.Time + endDateTime time.Time +} + +func (e *Event) ToEntity() *entity.Event { + return &entity.Event{ + Name: e.Name, + Description: e.Description, + StartDate: e.startDateTime, + EndDate: e.endDateTime, + Location: e.Location, + Level: e.Level, + Included: e.Included, + Price: e.Price, + Paid: e.Paid, + LocationID: &e.LocationID, + Status: e.Status, + } +} + +func (e *Event) Validate() error { + validate := validator.New() + if err := validate.Struct(e); err != nil { + return err + } + + startDateTimeStr := e.StartDate + "T" + e.StartTime + "Z" + endDateTimeStr := e.EndDate + "T" + e.EndTime + "Z" + + startDateTime, err := time.Parse(time.RFC3339, startDateTimeStr) + if err != nil { + fmt.Println("Error parsing start date-time:", err) + return err + } + + e.startDateTime = startDateTime + + endDateTime, err := time.Parse(time.RFC3339, endDateTimeStr) + if err != nil { + fmt.Println("Error parsing end date-time:", err) + return err + } + + e.endDateTime = endDateTime + + return nil +} diff --git a/internal/handlers/request/order.go b/internal/handlers/request/order.go new file mode 100644 index 0000000..b525e58 --- /dev/null +++ b/internal/handlers/request/order.go @@ -0,0 +1,116 @@ +package request + +import ( + "furtuna-be/internal/constants/order" + "furtuna-be/internal/constants/transaction" + "furtuna-be/internal/entity" + "time" +) + +type Order struct { + BranchID int64 `json:"branch_id" validate:"required"` + Amount float64 `json:"amount" validate:"required"` + CustomerName string `json:"customer_name" validate:"required"` + CustomerPhone string `json:"customer_phone" validate:"required"` + Pax int `json:"pax" validate:"required"` + PaymentMethod transaction.PaymentMethod `json:"payment_method" validate:"required"` + OrderItem []OrderItem `json:"order_items" validate:"required"` +} + +type OrderItem struct { + ItemID int64 `json:"item_id" validate:"required"` + ItemType order.ItemType `json:"item_type" validate:"required"` + Price float64 `json:"price" validate:"required"` + Qty int64 `json:"qty" validate:"required"` +} + +type OrderParam struct { + Search string `form:"search" json:"search" example:"name,branch_name,item_name"` + StatusActive order.OrderSearchStatus `form:"status_active" json:"status_active" example:"active,inactive"` + Status order.OrderStatus `form:"status" json:"status" example:"NEW,PAID,CANCEL"` + BranchID int64 `form:"branch_id" json:"branch_id" example:"1"` + Limit int `form:"limit" json:"limit" example:"10"` + Offset int `form:"offset" json:"offset" example:"0"` +} + +func (p *OrderParam) ToEntity() entity.OrderSearch { + return entity.OrderSearch{ + Search: p.Search, + StatusActive: p.StatusActive, + Status: p.Status, + BranchID: p.BranchID, + Limit: p.Limit, + Offset: p.Offset, + } +} + +func (o *Order) ToEntity() *entity.Order { + var ordItems []entity.OrderItem + if len(o.OrderItem) > 0 { + for _, i := range o.OrderItem { + ordItems = append(ordItems, entity.OrderItem{ + ItemID: i.ItemID, + ItemType: i.ItemType, + Price: i.Price, + Qty: i.Qty, + }) + } + } + + transaction := entity.Transaction{ + BranchID: o.BranchID, + CustomerName: o.CustomerName, + CustomerPhone: o.CustomerPhone, + PaymentMethod: o.PaymentMethod, + } + + return &entity.Order{ + BranchID: o.BranchID, + Amount: o.Amount, + CustomerName: o.CustomerName, + CustomerPhone: o.CustomerPhone, + Pax: o.Pax, + Transaction: transaction, + OrderItem: ordItems, + } +} + +type OrderTotalRevenueParam struct { + Year int `form:"year" json:"year" example:"1,2,3"` + Month int `form:"month" json:"month" example:"1,2,3"` + BranchID int64 `form:"branch_id" json:"branch_id" example:"1"` + DateStart *time.Time `form:"date_start" json:"date_start" example:"2024-01-01" time_format:"2006-1-2"` + DateEnd *time.Time `form:"date_end" json:"date_end" example:"2024-01-01" time_format:"2006-1-2"` +} + +func (p *OrderTotalRevenueParam) ToEntity() entity.OrderTotalRevenueSearch { + return entity.OrderTotalRevenueSearch{ + Year: p.Year, + Month: p.Month, + BranchID: p.BranchID, + DateStart: p.DateStart, + DateEnd: p.DateEnd, + } +} + +type OrderBranchRevenueParam struct { + DateStart *time.Time `form:"date_start" json:"date_start" example:"2024-01-01" time_format:"2006-1-2"` + DateEnd *time.Time `form:"date_end" json:"date_end" example:"2024-01-01" time_format:"2006-1-2"` +} + +func (p *OrderBranchRevenueParam) ToEntity() entity.OrderBranchRevenueSearch { + return entity.OrderBranchRevenueSearch{ + DateStart: p.DateStart, + DateEnd: p.DateEnd, + } +} + +type UpdateStatus struct { + Status order.OrderStatus `form:"status" json:"status" example:"NEW,PAID,CANCEL"` +} + +func (o *UpdateStatus) ToEntity() *entity.Order { + return &entity.Order{ + Status: o.Status, + } +} diff --git a/internal/handlers/request/partner.go b/internal/handlers/request/partner.go new file mode 100644 index 0000000..fff9c4d --- /dev/null +++ b/internal/handlers/request/partner.go @@ -0,0 +1,35 @@ +package request + +import ( + "furtuna-be/internal/entity" +) + +type PartnerParam struct { + Search string `form:"search" json:"search" example:"Ketua Umum"` + Name string `form:"name" json:"name" example:"Ketua Umum"` + Limit int `form:"limit" json:"limit" example:"10"` + Offset int `form:"offset" json:"offset" example:"0"` +} + +func (p *PartnerParam) ToEntity() entity.PartnerSearch { + return entity.PartnerSearch{ + Search: p.Search, + Name: p.Name, + Limit: p.Limit, + Offset: p.Offset, + } +} + +type Partner struct { + Name string `json:"name" validate:"required"` + Address string `json:"address" validate:"required"` + Status string `json:"status"` +} + +func (e *Partner) ToEntity() *entity.Partner { + return &entity.Partner{ + Name: e.Name, + Address: e.Address, + Status: e.Status, + } +} diff --git a/internal/handlers/request/product.go b/internal/handlers/request/product.go new file mode 100644 index 0000000..413322c --- /dev/null +++ b/internal/handlers/request/product.go @@ -0,0 +1,52 @@ +package request + +import ( + "furtuna-be/internal/constants/product" + "furtuna-be/internal/entity" +) + +type ProductParam struct { + Search string `form:"search" json:"search" example:"Nasi Goreng"` + Name string `form:"name" json:"name" example:"Nasi Goreng"` + Type product.ProductType `form:"type" json:"type" example:"FOOD/BEVERAGE"` + BranchID int64 `form:"branch_id" json:"branch_id" example:"1"` + Available product.ProductStock `form:"available" json:"available" example:"1" example:"AVAILABLE/UNAVAILABLE"` + Limit int `form:"limit" json:"limit" example:"10"` + Offset int `form:"offset" json:"offset" example:"0"` +} + +func (p *ProductParam) ToEntity() entity.ProductSearch { + return entity.ProductSearch{ + Search: p.Search, + Name: p.Name, + Type: p.Type, + BranchID: p.BranchID, + Available: p.Available, + Limit: p.Limit, + Offset: p.Offset, + } +} + +type Product struct { + Name string `json:"name" validate:"required"` + Type product.ProductType `json:"type" validate:"required"` + Price float64 `json:"price" validate:"required"` + Status product.ProductStatus `json:"status" validate:"required"` + Description string `json:"description" ` + Image string `json:"image" ` + BranchID int64 `json:"branch_id" validate:"required"` + StockQty int64 `json:"stock_qty" ` +} + +func (e *Product) ToEntity() *entity.Product { + return &entity.Product{ + Name: e.Name, + Type: e.Type, + Price: e.Price, + Status: e.Status, + Description: e.Description, + Image: e.Image, + BranchID: e.BranchID, + StockQty: e.StockQty, + } +} diff --git a/internal/handlers/request/studio.go b/internal/handlers/request/studio.go new file mode 100644 index 0000000..6ec5475 --- /dev/null +++ b/internal/handlers/request/studio.go @@ -0,0 +1,53 @@ +package request + +import ( + "encoding/json" + "furtuna-be/internal/constants/studio" + "furtuna-be/internal/entity" +) + +type StudioParam struct { + Id string `form:"id" json:"id" example:"1"` + Name string `form:"name" json:"name" example:"Studio A"` + Status studio.StudioStatus `form:"status" json:"status" example:"Active"` + BranchId int64 `form:"branch_id" json:"branch_id" example:"1"` + Limit int `form:"limit" json:"limit" example:"10"` + Offset int `form:"offset" json:"offset" example:"0"` +} + +func (p *StudioParam) ToEntity() entity.StudioSearch { + return entity.StudioSearch{ + Name: p.Name, + Status: p.Status, + BranchId: p.BranchId, + Limit: p.Limit, + Offset: p.Offset, + } +} + +type Studio struct { + Name string `json:"name" validate:"required"` + BranchId int64 `json:"branch_id" validate:"required"` + Status studio.StudioStatus `json:"status"` + Price float64 `json:"price" validate:"required"` + Metadata map[string]interface{} `json:"metadata"` +} + +func (e *Studio) ToEntity() *entity.Studio { + studioEntity := &entity.Studio{ + BranchId: e.BranchId, + Name: e.Name, + Status: e.Status, + Price: e.Price, + } + + if e.Metadata != nil { + jsonData, err := json.Marshal(e.Metadata) + if err != nil { + //TODO @taufanvps + } + studioEntity.Metadata = jsonData + } + + return studioEntity +} diff --git a/internal/handlers/request/transaction.go b/internal/handlers/request/transaction.go new file mode 100644 index 0000000..40a35af --- /dev/null +++ b/internal/handlers/request/transaction.go @@ -0,0 +1,9 @@ +package request + +import ( + "furtuna-be/internal/constants/transaction" +) + +type Transaction struct { + PaymentMethod transaction.PaymentMethod +} diff --git a/internal/handlers/request/user.go b/internal/handlers/request/user.go new file mode 100644 index 0000000..47de17c --- /dev/null +++ b/internal/handlers/request/user.go @@ -0,0 +1,58 @@ +package request + +import ( + "furtuna-be/internal/constants/role" + "furtuna-be/internal/entity" + + "github.com/go-playground/validator/v10" +) + +type User struct { + Name string `json:"name" validate:"required"` + Email string `json:"email" validate:"required"` + Password string `json:"password" validate:"required"` + PartnerID *int64 `json:"partner_id"` + RoleID int64 `json:"role_id" validate:"required"` + NIK string `json:"nik"` + UserType string `json:"user_type"` + PhoneNumber string `json:"phone_number"` +} + +func (e *User) Validate() error { + validate := validator.New() + if err := validate.Struct(e); err != nil { + return err + } + + return nil +} + +func (u *User) ToEntity() *entity.User { + return &entity.User{ + Name: u.Name, + Email: u.Email, + Password: u.Password, + RoleID: role.Role(u.RoleID), + PartnerID: u.PartnerID, + } +} + +type UserParam struct { + Search string `form:"search" json:"search" example:"admin,branch1"` + Name string `form:"name" json:"name" example:"Admin 1"` + RoleID int64 `form:"role_id" json:"role_id" example:"1"` + PartnerID int64 `form:"partner_id" json:"partner_id" example:"1"` + Limit int `form:"limit,default=10" json:"limit" example:"10"` + Offset int `form:"offset,default=0" json:"offset" example:"0"` +} + +func (p *UserParam) ToEntity() entity.UserSearch { + return entity.UserSearch{ + Search: p.Search, + Name: p.Name, + RoleID: p.RoleID, + PartnerID: p.PartnerID, + Limit: p.Limit, + Offset: p.Offset, + } +} diff --git a/internal/handlers/response/auth.go b/internal/handlers/response/auth.go new file mode 100644 index 0000000..1822ab2 --- /dev/null +++ b/internal/handlers/response/auth.go @@ -0,0 +1,13 @@ +package response + +type LoginResponse struct { + Token string `json:"token"` + Name string `json:"name"` + Role Role `json:"role"` + Branch *Branch `json:"branch"` +} + +type Role struct { + ID int64 `json:"id"` + Role string `json:"role_name"` +} diff --git a/internal/handlers/response/base_response.go b/internal/handlers/response/base_response.go new file mode 100644 index 0000000..6bfb27b --- /dev/null +++ b/internal/handlers/response/base_response.go @@ -0,0 +1,12 @@ +package response + +type BaseResponse struct { + Success bool `json:"success"` + Code string `json:"response_code,omitempty"` + Status int `json:"-"` + Message string `json:"message,omitempty"` + ErrorMessage string `json:"error_message,omitempty"` + ErrorDetail interface{} `json:"error_detail,omitempty"` + Data interface{} `json:"data,omitempty"` + PagingMeta *PagingMeta `json:"meta,omitempty"` +} diff --git a/internal/handlers/response/branch.go b/internal/handlers/response/branch.go new file mode 100644 index 0000000..d90fbfd --- /dev/null +++ b/internal/handlers/response/branch.go @@ -0,0 +1,17 @@ +package response + +type Branch struct { + ID *int64 `json:"id"` + Name string `json:"name"` + Status string `json:"status"` + Location string `json:"location"` + CreatedAt string `json:"created_at"` + UpdatedAt string `json:"updated_at"` +} + +type BranchList struct { + Branches []Branch `json:"branches"` + Total int64 `json:"total"` + Limit int `json:"limit"` + Offset int `json:"offset"` +} diff --git a/internal/handlers/response/event.go b/internal/handlers/response/event.go new file mode 100644 index 0000000..df85465 --- /dev/null +++ b/internal/handlers/response/event.go @@ -0,0 +1,27 @@ +package response + +type Event struct { + ID int64 `json:"id"` + Name string `json:"name"` + Status string `json:"status"` + Description string `json:"description"` + StartDate string `json:"start_date"` + EndDate string `json:"end_date"` + StartTime string `json:"start_time"` + EndTime string `json:"end_time"` + Location string `json:"location"` + Level string `json:"level"` + Included []string `json:"included"` + Price float64 `json:"price"` + Paid bool `json:"paid"` + LocationID *int64 `json:"location_id"` + CreatedAt string `json:"created_at"` + UpdatedAt string `json:"updated_at"` +} + +type EventList struct { + Events []Event `json:"events"` + Total int64 `json:"total"` + Limit int `json:"limit"` + Offset int `json:"offset"` +} diff --git a/internal/handlers/response/handler.go b/internal/handlers/response/handler.go new file mode 100644 index 0000000..6572cb2 --- /dev/null +++ b/internal/handlers/response/handler.go @@ -0,0 +1,38 @@ +package response + +import ( + "github.com/gin-gonic/gin" + + "furtuna-be/internal/common/errors" +) + +type response struct { + Status int `json:"status"` + Meta interface{} `json:"meta,omitempty"` + Message string `json:"message"` + Data interface{} `json:"data"` + Success bool `json:"success,omitempty"` +} + +func ErrorWrapper(c *gin.Context, err error) { + var customError errors.Error + customError = errors.ErrorInternalServer + + status := customError.MapErrorsToHTTPCode() + code := customError.MapErrorsToCode() + message := err.Error() + + if validErr, ok := err.(errors.Error); ok { + status = validErr.MapErrorsToHTTPCode() + code = validErr.MapErrorsToCode() + message = code.GetMessage() + } + + resp := BaseResponse{ + ErrorMessage: err.Error(), + Code: code.GetCode(), + Message: message, + } + + c.JSON(status, resp) +} diff --git a/internal/handlers/response/order.go b/internal/handlers/response/order.go new file mode 100644 index 0000000..ccd2ef8 --- /dev/null +++ b/internal/handlers/response/order.go @@ -0,0 +1,52 @@ +package response + +import ( + "furtuna-be/internal/constants/order" + "furtuna-be/internal/constants/transaction" +) + +type Order struct { + ID int64 `json:"id" ` + BranchID int64 `json:"branch_id" ` + BranchName string `json:"branch_name" ` + Amount float64 `json:"amount" ` + Status order.OrderStatus `json:"status" ` + CustomerName string `json:"customer_name" ` + CustomerPhone string `json:"customer_phone" ` + Pax int `json:"pax" ` + PaymentMethod transaction.PaymentMethod `json:"payment_method" ` + OrderItem []OrderItem `json:"order_items" ` + CreatedAt string `json:"created_at"` + UpdatedAt string `json:"updated_at"` +} + +type OrderItem struct { + OrderItemID int64 `json:"order_item_id" ` + ItemID int64 `json:"item_id" ` + ItemType order.ItemType `json:"item_type" ` + ItemName string `json:"item_name" ` + Price float64 `json:"price" ` + Qty int64 `json:"qty" ` + CreatedAt string `json:"created_at"` + UpdatedAt string `json:"updated_at"` +} + +type OrderList struct { + Orders []Order `json:"orders"` + Total int64 `json:"total"` + Limit int `json:"limit"` + Offset int `json:"offset"` +} + +type OrderMonthlyRevenue struct { + TotalRevenue float64 `json:"total_revenue"` + TotalTransaction int64 `json:"total_transaction"` +} + +type OrderBranchRevenue struct { + BranchID string `json:"branch_id"` + BranchName string `json:"name"` + BranchLocation string `json:"location"` + TotalTransaction int `json:"total_trans"` + TotalAmount float64 `json:"total_amount"` +} diff --git a/internal/handlers/response/paging.gp.go b/internal/handlers/response/paging.gp.go new file mode 100644 index 0000000..2ecbff0 --- /dev/null +++ b/internal/handlers/response/paging.gp.go @@ -0,0 +1,7 @@ +package response + +type PagingMeta struct { + Page int `json:"page"` + Limit int `json:"limit"` + Total int64 `json:"total_data"` +} diff --git a/internal/handlers/response/partner.go b/internal/handlers/response/partner.go new file mode 100644 index 0000000..8578dfc --- /dev/null +++ b/internal/handlers/response/partner.go @@ -0,0 +1,17 @@ +package response + +type Partner struct { + ID *int64 `json:"id"` + Name string `json:"name"` + Status string `json:"status"` + Address string `json:"address"` + CreatedAt string `json:"created_at"` + UpdatedAt string `json:"updated_at"` +} + +type PartnerList struct { + Partners []Partner `json:"partners"` + Total int64 `json:"total"` + Limit int `json:"limit"` + Offset int `json:"offset"` +} diff --git a/internal/handlers/response/product.go b/internal/handlers/response/product.go new file mode 100644 index 0000000..a514131 --- /dev/null +++ b/internal/handlers/response/product.go @@ -0,0 +1,24 @@ +package response + +import "furtuna-be/internal/constants/product" + +type Product struct { + ID int64 `json:"id"` + Name string `json:"name"` + Type product.ProductType `json:"type"` + Price float64 `json:"price"` + Status product.ProductStatus `json:"status"` + Description string `json:"description" ` + Image string `json:"image" ` + BranchID int64 `json:"branch_id"` + StockQty int64 `json:"stock_qty"` + CreatedAt string `json:"created_at"` + UpdatedAt string `json:"updated_at"` +} + +type ProductList struct { + Products []Product `json:"products"` + Total int64 `json:"total"` + Limit int `json:"limit"` + Offset int `json:"offset"` +} diff --git a/internal/handlers/response/studio.go b/internal/handlers/response/studio.go new file mode 100644 index 0000000..d552930 --- /dev/null +++ b/internal/handlers/response/studio.go @@ -0,0 +1,19 @@ +package response + +type Studio struct { + ID *int64 `json:"id"` + BranchId *int64 `json:"branch_id"` + Name string `json:"name"` + Status string `json:"status"` + Price float64 `json:"price"` + CreatedAt string `json:"created_at"` + Metadata map[string]interface{} `json:"metadata"` + UpdatedAt string `json:"updated_at"` +} + +type StudioList struct { + Studios []Studio `json:"studios"` + Total int64 `json:"total"` + Limit int `json:"limit"` + Offset int `json:"offset"` +} diff --git a/internal/handlers/response/user.go b/internal/handlers/response/user.go new file mode 100644 index 0000000..01ad00c --- /dev/null +++ b/internal/handlers/response/user.go @@ -0,0 +1,21 @@ +package response + +type User struct { + ID int64 `json:"id"` + Name string `json:"name"` + Email string `json:"email"` + Status string `json:"status"` + RoleID int64 `json:"role_id"` + RoleName string `json:"role_name"` + PartnerID *int64 `json:"partner_id"` + BranchName string `json:"partner_name"` + CreatedAt string `json:"created_at,omitempty"` + UpdatedAt string `json:"updated_at,omitempty"` +} + +type UserList struct { + Users []User `json:"users"` + Total int64 `json:"total"` + Limit int `json:"limit"` + Offset int `json:"offset"` +} diff --git a/internal/middlewares/auth.go b/internal/middlewares/auth.go new file mode 100644 index 0000000..40a3b65 --- /dev/null +++ b/internal/middlewares/auth.go @@ -0,0 +1,63 @@ +package middlewares + +import ( + "furtuna-be/internal/common/mycontext" + "net/http" + "strings" + + "github.com/gin-gonic/gin" + + "furtuna-be/internal/repository" +) + +func AuthorizationMiddleware(cryp repository.Crypto) gin.HandlerFunc { + return func(c *gin.Context) { + // Get the JWT token from the header + tokenString := c.GetHeader("Authorization") + if tokenString == "" { + c.JSON(http.StatusUnauthorized, gin.H{"error": "Authorization header is required"}) + c.Abort() + return + } + + tokenString = strings.TrimPrefix(tokenString, "Bearer ") + + claims, err := cryp.ParseAndValidateJWT(tokenString) + if err != nil { + c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid JWT token"}) + c.Abort() + return + } + + customCtx, err := mycontext.NewMyContext(c.Request.Context(), claims) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "error initialize context"}) + c.Abort() + return + } + + c.Set("myCtx", customCtx) + + c.Next() + } +} + +func SuperAdminMiddleware() gin.HandlerFunc { + return func(c *gin.Context) { + ctx, exists := c.Get("myCtx") + if !exists { + c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"}) + c.Abort() + return + } + + myCtx, ok := ctx.(*mycontext.MyContextImpl) + if !ok || !myCtx.IsSuperAdmin() { + c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"}) + c.Abort() + return + } + + c.Next() + } +} diff --git a/internal/middlewares/cors.go b/internal/middlewares/cors.go new file mode 100644 index 0000000..0d1f783 --- /dev/null +++ b/internal/middlewares/cors.go @@ -0,0 +1,38 @@ +package middlewares + +import ( + "fmt" + "net/http" + + "github.com/gin-gonic/gin" + + "furtuna-be/internal/common/logger" +) + +func Cors() gin.HandlerFunc { + return func(c *gin.Context) { + c.Header("Access-Control-Allow-Origin", "*") + c.Header("Access-Control-Allow-Credentials", "true") + c.Header("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, Accept, origin, Referer, Cache-Control, X-Requested-With") + c.Header("Access-Control-Allow-Methods", "POST,HEAD,PATCH, OPTIONS, GET, PUT, DELETE") + c.Header("Vary", "Origin") + + if c.Request.Method == "OPTIONS" { + c.AbortWithStatus(204) + return + } + + c.Next() + } +} + +func LogCorsError() gin.HandlerFunc { + return func(c *gin.Context) { + c.Next() + + // check if the request was blocked due to CORS + if c.Writer.Status() == http.StatusForbidden && c.Writer.Header().Get("Access-Control-Allow-Origin") == "" { + logger.GetLogger().Error(fmt.Sprintf("CORS error: %s", c.Writer.Header().Get("Access-Control-Allow-Origin"))) + } + } +} diff --git a/internal/middlewares/logger.go b/internal/middlewares/logger.go new file mode 100644 index 0000000..2d7f4b4 --- /dev/null +++ b/internal/middlewares/logger.go @@ -0,0 +1,31 @@ +package middlewares + +import ( + "fmt" + "time" + + "github.com/gin-gonic/gin" + + "furtuna-be/internal/common/request" +) + +func Logger() gin.HandlerFunc { + return gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string { + var parsedReqInfo request.RequestInfo + + reqInfo, exists := param.Keys[request.ReqInfoKey] + if exists { + parsedReqInfo = reqInfo.(request.RequestInfo) + } + + return fmt.Sprintf("%s - [HTTP] TraceId: %s; UserId: %d; Method: %s; Path: %s; Status: %d, Latency: %s;\n\n", + param.TimeStamp.Format(time.RFC1123), + parsedReqInfo.TraceId, + parsedReqInfo.UserId, + param.Method, + param.Path, + param.StatusCode, + param.Latency, + ) + }) +} diff --git a/internal/middlewares/request.go b/internal/middlewares/request.go new file mode 100644 index 0000000..497531f --- /dev/null +++ b/internal/middlewares/request.go @@ -0,0 +1,130 @@ +package middlewares + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "net/http" + "strconv" + "strings" + "time" + + "github.com/gin-gonic/gin" + "go.uber.org/zap" + + "furtuna-be/internal/common/logger" +) + +func RequestMiddleware() (handler gin.HandlerFunc) { + return func(ctx *gin.Context) { + start := time.Now() + body, _ := readRequestBody(ctx.Request) + reqData := getRequestParam(ctx.Request, body) + + // Check if the request contains a file + isFileUpload := false + contentType := ctx.Request.Header.Get("Content-Type") + if strings.HasPrefix(contentType, "multipart/form-data") { + isFileUpload = true + } + + // Log the request if it's not a file upload + if !isFileUpload { + logger.ContextLogger(ctx).With(reqData...).Info("Request") + } + + rbw := &ResponseBodyWriter{body: bytes.NewBufferString(""), ResponseWriter: ctx.Writer} + ctx.Writer = rbw + + stop := time.Now() + latency := stop.Sub(start).Milliseconds() + + resData := reqData + resData = append(resData, getResponseParam(rbw, latency)...) + + if !isFileUpload { + logger.ContextLogger(ctx).With(resData...).Info("Response") + } + } +} + +func readRequestBody(req *http.Request) ([]byte, error) { + body, err := io.ReadAll(req.Body) + if err != nil { + logger.ContextLogger(req.Context()).Error(fmt.Sprintf("Error reading body: %v", err)) + return nil, err + } + + req.Body = io.NopCloser(bytes.NewBuffer(body)) + + return body, nil +} + +type ResponseBodyWriter struct { + gin.ResponseWriter + body *bytes.Buffer +} + +func excludeSensitiveFields(data []interface{}) []interface{} { + var result []interface{} + for _, item := range data { + if param, ok := item.(gin.Param); ok { + // Exclude Authorization and Password fields + if param.Key != "Authorization" && param.Key != "Password" { + result = append(result, item) + } + } else { + result = append(result, item) + } + } + return result +} + +func getRequestParam(req *http.Request, body []byte) []zap.Field { + var reqData []zap.Field + reqData = append(reqData, zap.Any("host", req.Host), + zap.Any("uri", req.RequestURI), + zap.Any("method", req.Method), + zap.Any("path", func() interface{} { + p := req.URL.Path + if p == "" { + p = "/" + } + + return p + }()), + zap.Any("protocol", req.Proto), + zap.Any("referer", req.Referer()), + zap.Any("user_agent", req.UserAgent()), + zap.Any("headers", req.Header), + zap.Any("remote_ip", req.RemoteAddr), + zap.Any("body", excludeSensitiveFieldsFromBody(body)), + ) + + return reqData +} + +func getResponseParam(rbw *ResponseBodyWriter, latency int64) []zap.Field { + var resData []zap.Field + resData = append(resData, + zap.Any("httpStatus", rbw.Status()), + zap.Any("body", rbw.body.String()), + zap.Any("latency_human", strconv.FormatInt(latency, 10)), + zap.Any("headers", rbw.Header()), + ) + + return resData +} + +func excludeSensitiveFieldsFromBody(body []byte) string { + var data map[string]interface{} + if err := json.Unmarshal(body, &data); err != nil { + return string(body) + } + + delete(data, "password") + + result, _ := json.Marshal(data) + return string(result) +} diff --git a/internal/middlewares/trace.go b/internal/middlewares/trace.go new file mode 100644 index 0000000..c921e3c --- /dev/null +++ b/internal/middlewares/trace.go @@ -0,0 +1,20 @@ +package middlewares + +import ( + "furtuna-be/internal/common/request" + "furtuna-be/internal/constants" + "furtuna-be/internal/utils/generator" + "github.com/gin-gonic/gin" +) + +func Trace() gin.HandlerFunc { + return func(c *gin.Context) { + traceId := c.Request.Header.Get("Trace-Id") + if traceId == "" { + traceId = generator.GenerateUUID() + } + + request.SetTraceId(c, traceId) + c.Set(constants.ContextRequestID, traceId) + } +} diff --git a/internal/repository/auth/exec.go b/internal/repository/auth/exec.go new file mode 100644 index 0000000..8832b06 --- /dev/null +++ b/internal/repository/auth/exec.go @@ -0,0 +1 @@ +package auth diff --git a/internal/repository/auth/init.go b/internal/repository/auth/init.go new file mode 100644 index 0000000..57a57d8 --- /dev/null +++ b/internal/repository/auth/init.go @@ -0,0 +1,47 @@ +package auth + +import ( + "context" + "errors" + "fmt" + + "go.uber.org/zap" + "gorm.io/gorm" + + "furtuna-be/internal/common/logger" + "furtuna-be/internal/entity" +) + +type AuthRepository struct { + db *gorm.DB +} + +func NewAuthRepository(db *gorm.DB) *AuthRepository { + return &AuthRepository{ + db: db, + } +} + +func (r *AuthRepository) CheckExistsUserAccount(ctx context.Context, email string) (*entity.UserDB, error) { + var user entity.UserDB + + err := r.db. + Table("users"). + Select("users.*, user_roles.role_id, user_roles.partner_id, roles.role_name, partners.name as partner_name"). + Where("users.email = ?", email). + Joins("left join user_roles on users.id = user_roles.user_id"). + Joins("left join roles on user_roles.role_id = roles.role_id"). + Joins("left join partners on user_roles.partner_id = partners.id"). + First(&user).Error + + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, fmt.Errorf("user with email %s does not exist", email) // or use a custom error type + } + + logger.ContextLogger(ctx).Error(fmt.Sprintf("Failed to get user with email: %s", email), zap.Error(err)) + return nil, err + } + + return &user, nil +} diff --git a/internal/repository/branches/branch.go b/internal/repository/branches/branch.go new file mode 100644 index 0000000..d3064ab --- /dev/null +++ b/internal/repository/branches/branch.go @@ -0,0 +1,91 @@ +package branches + +import ( + "context" + "furtuna-be/internal/common/logger" + "furtuna-be/internal/entity" + + "go.uber.org/zap" + "gorm.io/gorm" +) + +type BranchRepository struct { + db *gorm.DB +} + +func NewBranchRepository(db *gorm.DB) *BranchRepository { + return &BranchRepository{ + db: db, + } +} + +func (b *BranchRepository) CreateBranch(ctx context.Context, branch *entity.BranchDB) (*entity.BranchDB, error) { + err := b.db.Create(branch).Error + if err != nil { + logger.ContextLogger(ctx).Error("error when create branch", zap.Error(err)) + return nil, err + } + return branch, nil +} + +func (b *BranchRepository) UpdateBranch(ctx context.Context, branch *entity.BranchDB) (*entity.BranchDB, error) { + if err := b.db.Save(branch).Error; err != nil { + logger.ContextLogger(ctx).Error("error when update branch", zap.Error(err)) + return nil, err + } + return branch, nil +} + +func (b *BranchRepository) GetBranchByID(ctx context.Context, id int64) (*entity.BranchDB, error) { + branch := new(entity.BranchDB) + if err := b.db.First(branch, id).Error; err != nil { + logger.ContextLogger(ctx).Error("error when get by id branch", zap.Error(err)) + return nil, err + } + return branch, nil +} + +func (b *BranchRepository) GetAllBranches(ctx context.Context, req entity.BranchSearch) (entity.BranchList, int, error) { + var branches []*entity.BranchDB + var total int64 + + query := b.db + query = query.Where("deleted_at is null") + + if req.Search != "" { + query = query.Where("name ILIKE ?", "%"+req.Search+"%") + } + + if req.Name != "" { + query = query.Where("name ILIKE ?", "%"+req.Name+"%") + } + + if req.Limit > 0 { + query = query.Limit(req.Limit) + } + + if req.Offset > 0 { + query = query.Offset(req.Offset) + } + + if err := query.Find(&branches).Error; err != nil { + logger.ContextLogger(ctx).Error("error when get all branches", zap.Error(err)) + return nil, 0, err + } + + if err := b.db.Model(&entity.BranchDB{}).Where(query).Count(&total).Error; err != nil { + logger.ContextLogger(ctx).Error("error when count branches", zap.Error(err)) + return nil, 0, err + } + + return branches, int(total), nil +} + +func (b *BranchRepository) DeleteBranch(ctx context.Context, id int64) error { + branch := new(entity.BranchDB) + branch.ID = id + if err := b.db.Delete(branch).Error; err != nil { + return err + } + return nil +} diff --git a/internal/repository/crypto/crypto.go b/internal/repository/crypto/crypto.go new file mode 100644 index 0000000..4853e7a --- /dev/null +++ b/internal/repository/crypto/crypto.go @@ -0,0 +1,4 @@ +//go:generate mockery --name Crypto --filename crypto.go --output ./mock --with-expecter + +package crypto + diff --git a/internal/repository/crypto/init.go b/internal/repository/crypto/init.go new file mode 100644 index 0000000..0619d75 --- /dev/null +++ b/internal/repository/crypto/init.go @@ -0,0 +1,90 @@ +package crypto + +import ( + "fmt" + "strconv" + "time" + + "github.com/golang-jwt/jwt" + "golang.org/x/crypto/bcrypt" + + "furtuna-be/internal/common/errors" + "furtuna-be/internal/entity" +) + +func NewCrypto(config CryptoConfig) *CryptoImpl { + return &CryptoImpl{ + Config: config, + } +} + +type CryptoConfig interface { + AccessTokenSecret() string + AccessTokenExpiresDate() time.Time +} + +type CryptoImpl struct { + Config CryptoConfig +} + +func (c *CryptoImpl) CompareHashAndPassword(hash string, password string) bool { + err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) + return err == nil +} + +func (c *CryptoImpl) ValidateWT(tokenString string) (*jwt.Token, error) { + token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { + if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { + return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"]) + } + return []byte(c.Config.AccessTokenSecret()), nil + }) + + return token, err +} + +func (c *CryptoImpl) GenerateJWT(user *entity.User) (string, error) { + branchID := int64(0) + if user.PartnerID != nil { + branchID = *user.PartnerID + } + + claims := &entity.JWTAuthClaims{ + StandardClaims: jwt.StandardClaims{ + Subject: strconv.FormatInt(user.ID, 10), + ExpiresAt: c.Config.AccessTokenExpiresDate().Unix(), + IssuedAt: time.Now().Unix(), + NotBefore: time.Now().Unix(), + }, + UserID: user.ID, + Name: user.Name, + Email: user.Email, + Role: int(user.RoleID), + BranchID: branchID, + } + + token, err := jwt. + NewWithClaims(jwt.SigningMethodHS256, claims). + SignedString([]byte(c.Config.AccessTokenSecret())) + + if err != nil { + return "", err + } + + return token, nil +} + +func (c *CryptoImpl) ParseAndValidateJWT(tokenString string) (*entity.JWTAuthClaims, error) { + token, err := jwt.ParseWithClaims(tokenString, &entity.JWTAuthClaims{}, func(token *jwt.Token) (interface{}, error) { + return []byte(c.Config.AccessTokenSecret()), nil + }) + if err != nil { + return nil, err + } + + if claims, ok := token.Claims.(*entity.JWTAuthClaims); ok && token.Valid { + return claims, nil + } else { + return nil, errors.ErrorUnauthorized + } +} diff --git a/internal/repository/events/event.go b/internal/repository/events/event.go new file mode 100644 index 0000000..1e36d9b --- /dev/null +++ b/internal/repository/events/event.go @@ -0,0 +1,86 @@ +package event + +import ( + "context" + + "go.uber.org/zap" + "gorm.io/gorm" + + "furtuna-be/internal/common/logger" + "furtuna-be/internal/entity" +) + +type EventRepoImpl struct { + DB *gorm.DB +} + +func NewEventRepo(db *gorm.DB) *EventRepoImpl { + return &EventRepoImpl{DB: db} +} + +func (e *EventRepoImpl) CreateEvent(ctx context.Context, event *entity.EventDB) (*entity.EventDB, error) { + err := e.DB.Create(event).Error + if err != nil { + logger.ContextLogger(ctx).Error("error when create event", zap.Error(err)) + return nil, err + } + return event, nil +} + +func (e *EventRepoImpl) UpdateEvent(ctx context.Context, event *entity.EventDB) (*entity.EventDB, error) { + if err := e.DB.Save(event).Error; err != nil { + logger.ContextLogger(ctx).Error("error when update event", zap.Error(err)) + return nil, err + } + return event, nil +} + +func (e *EventRepoImpl) GetEventByID(ctx context.Context, id int64) (*entity.EventDB, error) { + event := new(entity.EventDB) + if err := e.DB.First(event, id).Error; err != nil { + logger.ContextLogger(ctx).Error("error when get event by id", zap.Error(err)) + return nil, err + } + return event, nil +} + +func (e *EventRepoImpl) GetAllEvents(ctx context.Context, nameFilter string, limit, offset int) (entity.EventList, int, error) { + var events []*entity.EventDB + var total int64 + + query := e.DB + query = query.Where("deleted_at is null") + + if nameFilter != "" { + query = query.Where("name LIKE ?", "%"+nameFilter+"%") + } + + if limit > 0 { + query = query.Limit(limit) + } + if offset > 0 { + query = query.Offset(offset) + } + + if err := query.Find(&events).Error; err != nil { + logger.ContextLogger(ctx).Error("error when get all events", zap.Error(err)) + return nil, 0, err + } + + if err := e.DB.Model(&entity.EventDB{}).Where(query).Count(&total).Error; err != nil { + logger.ContextLogger(ctx).Error("error when count event", zap.Error(err)) + return nil, 0, err + } + + return events, int(total), nil +} + +func (e *EventRepoImpl) DeleteEvent(ctx context.Context, id int64) error { + event := new(entity.EventDB) + event.ID = id + if err := e.DB.Delete(event).Error; err != nil { + logger.ContextLogger(ctx).Error("error when get all events", zap.Error(err)) + return err + } + return nil +} diff --git a/internal/repository/orders/order.go b/internal/repository/orders/order.go new file mode 100644 index 0000000..d514eb5 --- /dev/null +++ b/internal/repository/orders/order.go @@ -0,0 +1,303 @@ +package orders + +import ( + "context" + "fmt" + "furtuna-be/internal/common/logger" + "furtuna-be/internal/entity" + + "go.uber.org/zap" + "gorm.io/gorm" +) + +type OrderRepository struct { + db *gorm.DB +} + +func NewOrderRepository(db *gorm.DB) *OrderRepository { + return &OrderRepository{ + db: db, + } +} + +func (o *OrderRepository) CreateOrder(ctx context.Context, order *entity.OrderDB) (*entity.OrderDB, error) { + tx := o.db.Begin() + + if err := tx.Select("branch_id", "status", "customer_name", "customer_phone", "pax", "amount", "created_by").Create(order).Error; err != nil { + tx.Rollback() + logError(ctx, "creating order", err) + return nil, err + } + + for i, orditem := range order.OrderItem { + orderItem := orditem.ToOrderItemDB() + orderItem.OrderID = order.ID + + err := tx.Select("order_id", "item_id", "item_type", "price", "qty", "created_by").Create(orderItem).Error + if err != nil { + logger.ContextLogger(ctx).Error("error when create order item", zap.Error(err)) + return nil, err + } + + order.OrderItem[i] = *orderItem.ToOrderItem() + } + + //insert transaction + transaction := order.Transaction.ToTransactionDB() + transaction.OrderID = order.ID + + if err := tx.Select("branch_id", "status", "amount", "order_id", "payment_method", "customer_name", "customer_phone", "created_by").Create(transaction).Error; err != nil { + tx.Rollback() + logError(ctx, "creating transaction", err) + return nil, err + } + + if err := tx.Commit().Error; err != nil { + tx.Rollback() + logError(ctx, "committing transaction", err) + return nil, err + } + + return order, nil +} + +func (b *OrderRepository) UpdateOrder(ctx context.Context, order *entity.OrderDB) (*entity.OrderDB, error) { + + if err := b.db.Select("status", "updated_at", "updated_by").Save(order).Error; err != nil { + logError(ctx, "update order", err) + return nil, err + } + + return order, nil +} + +func (b *OrderRepository) GetAllOrders(ctx context.Context, req entity.OrderSearch) (entity.OrderList, int, error) { + var orders []*entity.OrderDB + var total int64 + + query := b.db.Table("orders"). + Select("orders.id, orders.branch_id, b.name as branch_name, orders.status, orders.amount, orders.created_at, orders.updated_at, oi.order_item_id, oi.order_id, oi.item_id, oi.item_type, COALESCE(p.name, s.name, '') as item_name, oi.price, oi.qty, oi.created_at, oi.updated_at, COALESCE(t.payment_method, ''), COALESCE(orders.customer_name, ''), COALESCE(orders.customer_phone, ''), COALESCE(orders.pax, 0)"). + Joins("LEFT JOIN order_items oi ON orders.id = oi.order_id"). + Joins("LEFT JOIN transactions t ON orders.id = t.order_id"). + Joins("LEFT JOIN products p ON oi.item_id = p.id AND oi.item_type ='PRODUCT' "). + Joins("LEFT JOIN studios s ON oi.item_id = s.id AND oi.item_type ='STUDIO' "). + Joins("LEFT JOIN branches b ON orders.branch_id = b.id") + + if req.Search != "" { + query = query.Where("b.name ILIKE ? or orders.status ILIKE ? or oi.item_type ILIKE ? or p.name ILIKE ? or orders.customer_name ILIKE ? ", "%"+req.Search+"%", "%"+req.Search+"%", "%"+req.Search+"%", "%"+req.Search+"%", "%"+req.Search+"%") + } + + if req.Status != "" { + query = query.Where("orders.status = ?", req.Status) + } + + if req.BranchID > 0 { + query = query.Where("orders.branch_id = ?", req.BranchID) + } + + if req.StatusActive.IsActive() { + query = query.Joins("INNER JOIN (SELECT o.id, oi.qty, o.created_at FROM orders o INNER JOIN order_items oi ON o.id = oi.order_id AND oi.item_type = 'STUDIO' where o.status != 'CANCEL' and CURRENT_TIMESTAMP > o.created_at AND CURRENT_TIMESTAMP < (o.created_at + (oi.qty || ' hours')::interval)) order_active on order_active.id=orders.id") + } + + if req.Limit > 0 { + query = query.Limit(req.Limit) + } + + if req.Offset > 0 { + query = query.Offset(req.Offset) + } + + query.Order("orders.created_at DESC") + + rows, err := query.Rows() + if err != nil { + return nil, 0, err + } + + defer rows.Close() + + ordersMap := make(map[int64]*entity.OrderDB) // Map to store orders by ID + for rows.Next() { + var ordr entity.OrderDB + var oi entity.OrderItem + + err := rows.Scan(&ordr.ID, &ordr.BranchID, &ordr.BranchName, &ordr.Status, &ordr.Amount, &ordr.CreatedAt, &ordr.UpdatedAt, + &oi.OrderItemID, &oi.OrderID, &oi.ItemID, &oi.ItemType, &oi.ItemName, &oi.Price, &oi.Qty, &oi.CreatedAt, &oi.UpdatedAt, + &ordr.Transaction.PaymentMethod, &ordr.CustomerName, &ordr.CustomerPhone, &ordr.Pax) + if err != nil { + logger.ContextLogger(ctx).Error("error scanning rows", zap.Error(err)) + return nil, 0, err + } + + if order, ok := ordersMap[ordr.ID]; ok { + // Order already exists in map, append OrderItem to existing order + order.OrderItem = append(order.OrderItem, oi) + } else { + // Order doesn't exist in map, create a new OrderDB + newOrder := ordr + newOrder.OrderItem = []entity.OrderItem{oi} + ordersMap[ordr.ID] = &newOrder + + orders = append(orders, &ordr) + } + } + + // assign value order item + for _, v := range orders { + v.OrderItem = ordersMap[v.ID].OrderItem + } + + //reset limit for count total data + query = query.Offset(-1).Limit(-1) + + if err := query.Count(&total).Error; err != nil { + logger.ContextLogger(ctx).Error("error when count orders", zap.Error(err)) + return nil, 0, err + } + + return orders, int(total), nil +} + +func (b *OrderRepository) GetOrderByID(ctx context.Context, id int64) (*entity.OrderDB, error) { + var orders *entity.OrderDB + + query := b.db.Table("orders"). + Select("orders.id, orders.branch_id, b.name as branch_name, orders.status, orders.amount, orders.created_at, orders.updated_at, oi.order_item_id, oi.order_id, oi.item_id, oi.item_type, COALESCE(p.name, s.name, '') as item_name, oi.price, oi.qty, oi.created_at, oi.updated_at, t.payment_method, orders.customer_name, orders.customer_phone, orders.pax"). + Joins("LEFT JOIN order_items oi ON orders.id = oi.order_id"). + Joins("LEFT JOIN transactions t ON orders.id = t.order_id"). + Joins("LEFT JOIN products p ON oi.item_id = p.id AND oi.item_type ='PRODUCT' "). + Joins("LEFT JOIN studios s ON oi.item_id = s.id AND oi.item_type ='STUDIO' "). + Joins("LEFT JOIN branches b ON orders.branch_id = b.id"). + Where("orders.id = ?", id) + + rows, err := query.Rows() + if err != nil { + return nil, err + } + + defer rows.Close() + + var ordr entity.OrderDB // Map to store orders by ID + for rows.Next() { + var oi entity.OrderItem + + err := rows.Scan(&ordr.ID, &ordr.BranchID, &ordr.BranchName, &ordr.Status, &ordr.Amount, &ordr.CreatedAt, &ordr.UpdatedAt, + &oi.OrderItemID, &oi.OrderID, &oi.ItemID, &oi.ItemType, &oi.ItemName, &oi.Price, &oi.Qty, &oi.CreatedAt, &oi.UpdatedAt, + &ordr.Transaction.PaymentMethod, &ordr.CustomerName, &ordr.CustomerPhone, &ordr.Pax) + if err != nil { + logger.ContextLogger(ctx).Error("error scanning rows", zap.Error(err)) + return nil, err + } + + ordr.OrderItem = append(ordr.OrderItem, oi) + } + + orders = &ordr + + if orders == nil { + return nil, fmt.Errorf("order not found") + } + + return orders, nil +} + +func (b *OrderRepository) GetTotalRevenue(ctx context.Context, req entity.OrderTotalRevenueSearch) (float64, int64, error) { + var ( + totalmonthlyRevenue float64 + totalmonthlyTrans int64 + ) + + query := b.db.Table("orders"). + Select("COALESCE(sum(amount),0) as total_amount, COALESCE(count(id),0) as total_transaction"). + Where("status in ('NEW','PAID') ") + + if req.BranchID > 0 { + query = query.Where("branch_id = ?", req.BranchID) + } + + if req.Month > 0 { + query = query.Where("EXTRACT(MONTH FROM created_at) = ? ", req.Month) + } + + if req.Year > 0 { + query = query.Where("EXTRACT(YEAR FROM created_at) = ? ", req.Year) + } + + if req.DateStart != nil { + query = query.Where("created_at >= ? ", req.DateStart) + } + + if req.DateEnd != nil { + query = query.Where("created_at <= ? ", req.DateEnd) + } + + rows, err := query.Rows() + if err != nil { + return totalmonthlyRevenue, totalmonthlyTrans, err + } + defer rows.Close() + + for rows.Next() { + err := rows.Scan(&totalmonthlyRevenue, &totalmonthlyTrans) + if err != nil { + logger.ContextLogger(ctx).Error("error scanning rows", zap.Error(err)) + return totalmonthlyRevenue, totalmonthlyTrans, err + } + } + + return totalmonthlyRevenue, totalmonthlyTrans, nil +} + +func (b *OrderRepository) GetYearlyRevenue(ctx context.Context, year int) (entity.OrderYearlyRevenueList, error) { + var result entity.OrderYearlyRevenueList + + err := b.db.Raw(` SELECT + oi.item_type, + EXTRACT(MONTH FROM o.created_at) AS month_number, + SUM(oi.price ) AS total_amount + FROM + orders o + JOIN + order_items oi ON o.id = oi.order_id + WHERE + EXTRACT(YEAR FROM o.created_at) = ? + AND o.status IN ('NEW', 'PAID') + GROUP BY + EXTRACT(MONTH FROM o.created_at), + oi.item_type + ORDER BY + month_number, + oi.item_type`, year).Scan(&result).Error + + return result, err +} + +func (b *OrderRepository) GetBranchRevenue(ctx context.Context, req entity.OrderBranchRevenueSearch) (entity.OrderBranchRevenueList, error) { + var result entity.OrderBranchRevenueList + + query := b.db.Table("orders o"). + Joins("JOIN branches ON branches.id = o.branch_id"). + Select("o.branch_id, branches.name, branches.location, SUM(o.amount) as total_amount, COUNT(o.id) as total_trans"). + Where("o.status IN ('NEW', 'PAID')"). + Group("o.branch_id, branches.name, branches.location"). + Order("total_amount DESC, total_trans DESC") + + if req.DateStart != nil { + query = query.Where("o.created_at >= ? ", req.DateStart) + } + + if req.DateEnd != nil { + query = query.Where("o.created_at <= ? ", req.DateEnd) + } + + if err := query.Find(&result).Error; err != nil { + logger.ContextLogger(ctx).Error("error when GetBranchRevenue", zap.Error(err)) + return nil, err + } + + return result, nil +} + +func logError(ctx context.Context, s string, err error) { + panic("unimplemented") +} diff --git a/internal/repository/oss/oss.go b/internal/repository/oss/oss.go new file mode 100644 index 0000000..c57c7c1 --- /dev/null +++ b/internal/repository/oss/oss.go @@ -0,0 +1,67 @@ +package oss + +import ( + "bytes" + "context" + "fmt" + + "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 OSSConfig interface { + GetAccessKeyID() string + GetAccessKeySecret() string + GetEndpoint() string + GetBucketName() string + GetHostURL() string +} + +const _awsRegion = "us-east-1" +const _s3ACL = "public-read" + +type OssRepositoryImpl struct { + s3 *s3.S3 + cfg OSSConfig +} + +func NewOssRepositoryImpl(ossCfg OSSConfig) *OssRepositoryImpl { + sess, err := session.NewSession(&aws.Config{ + S3ForcePathStyle: aws.Bool(true), + Endpoint: aws.String(ossCfg.GetEndpoint()), + Region: aws.String(_awsRegion), + Credentials: credentials.NewStaticCredentials(ossCfg.GetAccessKeyID(), ossCfg.GetAccessKeySecret(), ""), + }) + + if err != nil { + fmt.Println("Failed to create AWS session:", err) + return nil + } + + return &OssRepositoryImpl{ + s3: s3.New(sess), + cfg: ossCfg, + } +} + +func (r *OssRepositoryImpl) UploadFile(ctx context.Context, fileName string, fileContent []byte) (fileUrl string, err error) { + reader := bytes.NewReader(fileContent) + + _, err = r.s3.PutObject(&s3.PutObjectInput{ + Bucket: aws.String(r.cfg.GetBucketName()), + Key: aws.String(fileName), + Body: reader, + ACL: aws.String(_s3ACL), + }) + + return r.GetPublicURL(fileName), err +} + +func (r *OssRepositoryImpl) GetPublicURL(fileName string) string { + if fileName == "" { + return "" + } + return fmt.Sprintf("%s/%s%s", r.cfg.GetHostURL(), r.cfg.GetBucketName(), fileName) +} diff --git a/internal/repository/partners/partners.go b/internal/repository/partners/partners.go new file mode 100644 index 0000000..68b8678 --- /dev/null +++ b/internal/repository/partners/partners.go @@ -0,0 +1,91 @@ +package partners + +import ( + "context" + "furtuna-be/internal/common/logger" + "furtuna-be/internal/entity" + + "go.uber.org/zap" + "gorm.io/gorm" +) + +type PartnerRepository struct { + db *gorm.DB +} + +func NewPartnerRepository(db *gorm.DB) *PartnerRepository { + return &PartnerRepository{ + db: db, + } +} + +func (b *PartnerRepository) Create(ctx context.Context, Partner *entity.PartnerDB) (*entity.PartnerDB, error) { + err := b.db.Create(Partner).Error + if err != nil { + logger.ContextLogger(ctx).Error("error when create Partner", zap.Error(err)) + return nil, err + } + return Partner, nil +} + +func (b *PartnerRepository) Update(ctx context.Context, Partner *entity.PartnerDB) (*entity.PartnerDB, error) { + if err := b.db.Save(Partner).Error; err != nil { + logger.ContextLogger(ctx).Error("error when update Partner", zap.Error(err)) + return nil, err + } + return Partner, nil +} + +func (b *PartnerRepository) GetByID(ctx context.Context, id int64) (*entity.PartnerDB, error) { + Partner := new(entity.PartnerDB) + if err := b.db.First(Partner, id).Error; err != nil { + logger.ContextLogger(ctx).Error("error when get by id Partner", zap.Error(err)) + return nil, err + } + return Partner, nil +} + +func (b *PartnerRepository) GetAll(ctx context.Context, req entity.PartnerSearch) (entity.PartnerList, int, error) { + var Partneres []*entity.PartnerDB + var total int64 + + query := b.db + query = query.Where("deleted_at is null") + + if req.Search != "" { + query = query.Where("name ILIKE ?", "%"+req.Search+"%") + } + + if req.Name != "" { + query = query.Where("name ILIKE ?", "%"+req.Name+"%") + } + + if req.Limit > 0 { + query = query.Limit(req.Limit) + } + + if req.Offset > 0 { + query = query.Offset(req.Offset) + } + + if err := query.Find(&Partneres).Error; err != nil { + logger.ContextLogger(ctx).Error("error when get all Partneres", zap.Error(err)) + return nil, 0, err + } + + if err := b.db.Model(&entity.PartnerDB{}).Where(query).Count(&total).Error; err != nil { + logger.ContextLogger(ctx).Error("error when count Partneres", zap.Error(err)) + return nil, 0, err + } + + return Partneres, int(total), nil +} + +func (b *PartnerRepository) Delete(ctx context.Context, id int64) error { + Partner := new(entity.PartnerDB) + Partner.ID = id + if err := b.db.Delete(Partner).Error; err != nil { + return err + } + return nil +} diff --git a/internal/repository/products/product.go b/internal/repository/products/product.go new file mode 100644 index 0000000..ba3d0c7 --- /dev/null +++ b/internal/repository/products/product.go @@ -0,0 +1,107 @@ +package products + +import ( + "context" + "furtuna-be/internal/common/logger" + "furtuna-be/internal/entity" + + "go.uber.org/zap" + "gorm.io/gorm" +) + +type ProductRepository struct { + db *gorm.DB +} + +func NewProductRepository(db *gorm.DB) *ProductRepository { + return &ProductRepository{ + db: db, + } +} + +func (b *ProductRepository) CreateProduct(ctx context.Context, product *entity.ProductDB) (*entity.ProductDB, error) { + err := b.db.Create(product).Error + if err != nil { + logger.ContextLogger(ctx).Error("error when create product", zap.Error(err)) + return nil, err + } + return product, nil +} + +func (b *ProductRepository) UpdateProduct(ctx context.Context, product *entity.ProductDB) (*entity.ProductDB, error) { + if err := b.db.Save(product).Error; err != nil { + logger.ContextLogger(ctx).Error("error when update product", zap.Error(err)) + return nil, err + } + return product, nil +} + +func (b *ProductRepository) GetProductByID(ctx context.Context, id int64) (*entity.ProductDB, error) { + product := new(entity.ProductDB) + if err := b.db.First(product, id).Error; err != nil { + logger.ContextLogger(ctx).Error("error when get by id product", zap.Error(err)) + return nil, err + } + return product, nil +} + +func (b *ProductRepository) GetAllProducts(ctx context.Context, req entity.ProductSearch) (entity.ProductList, int, error) { + var products []*entity.ProductDB + var total int64 + + query := b.db + query = query.Where("deleted_at is null") + + if req.Search != "" { + query = query.Where("name ILIKE ?", "%"+req.Search+"%") + } + + if req.Name != "" { + query = query.Where("name ILIKE ?", "%"+req.Name+"%") + } + + if req.Type != "" { + query = query.Where("type = ? ", req.Type) + } + + if req.BranchID > 0 { + query = query.Where("branch_id = ? ", req.BranchID) + } + + if req.Available != "" { + if req.Available.IsAvailable() { + query = query.Where("stock_qty > 0 ") + } else if req.Available.IsUnavailable() { + query = query.Where("stock_qty < 1 ") + } + } + + if req.Limit > 0 { + query = query.Limit(req.Limit) + } + + if req.Offset > 0 { + query = query.Offset(req.Offset) + } + + if err := query.Find(&products).Error; err != nil { + logger.ContextLogger(ctx).Error("error when get all products", zap.Error(err)) + return nil, 0, err + } + + if err := b.db.Model(&entity.ProductDB{}).Where(query).Count(&total).Error; err != nil { + logger.ContextLogger(ctx).Error("error when count products", zap.Error(err)) + return nil, 0, err + } + + return products, int(total), nil +} + +func (b *ProductRepository) DeleteProduct(ctx context.Context, id int64) error { + product := new(entity.ProductDB) + product.ID = id + if err := b.db.Delete(product).Error; err != nil { + return err + } + return nil +} diff --git a/internal/repository/repository.go b/internal/repository/repository.go new file mode 100644 index 0000000..925b300 --- /dev/null +++ b/internal/repository/repository.go @@ -0,0 +1,122 @@ +package repository + +import ( + "context" + "furtuna-be/internal/repository/branches" + "furtuna-be/internal/repository/orders" + "furtuna-be/internal/repository/oss" + "furtuna-be/internal/repository/partners" + "furtuna-be/internal/repository/products" + "furtuna-be/internal/repository/studios" + "furtuna-be/internal/repository/users" + + "github.com/golang-jwt/jwt" + "gorm.io/gorm" + + "furtuna-be/config" + "furtuna-be/internal/entity" + "furtuna-be/internal/repository/auth" + "furtuna-be/internal/repository/crypto" + event "furtuna-be/internal/repository/events" +) + +type RepoManagerImpl struct { + Crypto Crypto + Auth Auth + Event Event + User User + Branch Branch + Studio Studio + Product Product + Order Order + OSS OSSRepository + Partner PartnerRepository +} + +func NewRepoManagerImpl(db *gorm.DB, cfg *config.Config) *RepoManagerImpl { + return &RepoManagerImpl{ + Crypto: crypto.NewCrypto(cfg.Auth()), + Auth: auth.NewAuthRepository(db), + Event: event.NewEventRepo(db), + User: users.NewUserRepository(db), + Branch: branches.NewBranchRepository(db), + Studio: studios.NewStudioRepository(db), + Product: products.NewProductRepository(db), + Order: orders.NewOrderRepository(db), + OSS: oss.NewOssRepositoryImpl(cfg.OSSConfig), + Partner: partners.NewPartnerRepository(db), + } +} + +type Auth interface { + CheckExistsUserAccount(ctx context.Context, email string) (*entity.UserDB, error) +} + +type Event interface { + CreateEvent(ctx context.Context, event *entity.EventDB) (*entity.EventDB, error) + UpdateEvent(ctx context.Context, event *entity.EventDB) (*entity.EventDB, error) + GetEventByID(ctx context.Context, id int64) (*entity.EventDB, error) + GetAllEvents(ctx context.Context, nameFilter string, limit, offset int) (entity.EventList, int, error) + DeleteEvent(ctx context.Context, id int64) error +} + +type Crypto interface { + CompareHashAndPassword(hash string, password string) bool + ValidateWT(tokenString string) (*jwt.Token, error) + GenerateJWT(user *entity.User) (string, error) + ParseAndValidateJWT(token string) (*entity.JWTAuthClaims, error) +} + +type User interface { + Create(ctx context.Context, user *entity.UserDB) (*entity.UserDB, error) + GetAllUsers(ctx context.Context, req entity.UserSearch) (entity.UserList, int, error) + GetUserByID(ctx context.Context, id int64) (*entity.UserDB, error) + GetUserByEmail(ctx context.Context, email string) (*entity.UserDB, error) + UpdateUser(ctx context.Context, user *entity.UserDB) (*entity.UserDB, error) +} + +type Branch interface { + CreateBranch(ctx context.Context, branch *entity.BranchDB) (*entity.BranchDB, error) + UpdateBranch(ctx context.Context, branch *entity.BranchDB) (*entity.BranchDB, error) + GetBranchByID(ctx context.Context, id int64) (*entity.BranchDB, error) + GetAllBranches(ctx context.Context, req entity.BranchSearch) (entity.BranchList, int, error) + DeleteBranch(ctx context.Context, id int64) error +} + +type Studio interface { + CreateStudio(ctx context.Context, studio *entity.StudioDB) (*entity.StudioDB, error) + UpdateStudio(ctx context.Context, studio *entity.StudioDB) (*entity.StudioDB, error) + GetStudioByID(ctx context.Context, id int64) (*entity.StudioDB, error) + SearchStudios(ctx context.Context, req entity.StudioSearch) (entity.StudioList, int, error) +} + +type Product interface { + CreateProduct(ctx context.Context, product *entity.ProductDB) (*entity.ProductDB, error) + UpdateProduct(ctx context.Context, product *entity.ProductDB) (*entity.ProductDB, error) + GetProductByID(ctx context.Context, id int64) (*entity.ProductDB, error) + GetAllProducts(ctx context.Context, req entity.ProductSearch) (entity.ProductList, int, error) + DeleteProduct(ctx context.Context, id int64) error +} + +type Order interface { + CreateOrder(ctx context.Context, order *entity.OrderDB) (*entity.OrderDB, error) + UpdateOrder(ctx context.Context, order *entity.OrderDB) (*entity.OrderDB, error) + GetOrderByID(ctx context.Context, id int64) (*entity.OrderDB, error) + GetAllOrders(ctx context.Context, req entity.OrderSearch) (entity.OrderList, int, error) + GetTotalRevenue(ctx context.Context, req entity.OrderTotalRevenueSearch) (float64, int64, error) + GetYearlyRevenue(ctx context.Context, year int) (entity.OrderYearlyRevenueList, error) + GetBranchRevenue(ctx context.Context, req entity.OrderBranchRevenueSearch) (entity.OrderBranchRevenueList, error) +} + +type OSSRepository interface { + UploadFile(ctx context.Context, fileName string, fileContent []byte) (fileUrl string, err error) + GetPublicURL(fileName string) string +} + +type PartnerRepository interface { + Create(ctx context.Context, branch *entity.PartnerDB) (*entity.PartnerDB, error) + Update(ctx context.Context, branch *entity.PartnerDB) (*entity.PartnerDB, error) + GetByID(ctx context.Context, id int64) (*entity.PartnerDB, error) + GetAll(ctx context.Context, req entity.PartnerSearch) (entity.PartnerList, int, error) + Delete(ctx context.Context, id int64) error +} diff --git a/internal/repository/studios/studio.go b/internal/repository/studios/studio.go new file mode 100644 index 0000000..7166d00 --- /dev/null +++ b/internal/repository/studios/studio.go @@ -0,0 +1,89 @@ +package studios + +import ( + "context" + "furtuna-be/internal/common/logger" + "furtuna-be/internal/entity" + + "go.uber.org/zap" + "gorm.io/gorm" +) + +type StudioRepository struct { + db *gorm.DB +} + +func NewStudioRepository(db *gorm.DB) *StudioRepository { + return &StudioRepository{ + db: db, + } +} + +func (s *StudioRepository) CreateStudio(ctx context.Context, studio *entity.StudioDB) (*entity.StudioDB, error) { + err := s.db.Omit("ID").Create(studio).Error + if err != nil { + logger.ContextLogger(ctx).Error("error when creating studio", zap.Error(err)) + return nil, err + } + return studio, nil +} + +func (s *StudioRepository) UpdateStudio(ctx context.Context, studio *entity.StudioDB) (*entity.StudioDB, error) { + if err := s.db.Save(studio).Error; err != nil { + logger.ContextLogger(ctx).Error("error when updating studio", zap.Error(err)) + return nil, err + } + return studio, nil +} + +func (s *StudioRepository) GetStudioByID(ctx context.Context, id int64) (*entity.StudioDB, error) { + studio := new(entity.StudioDB) + if err := s.db.First(studio, id).Error; err != nil { + logger.ContextLogger(ctx).Error("error when getting studio by ID", zap.Error(err)) + return nil, err + } + return studio, nil +} + +func (s *StudioRepository) SearchStudios(ctx context.Context, req entity.StudioSearch) (entity.StudioList, int, error) { + var studios []*entity.StudioDB + var total int64 + + query := s.db + + if req.Id > 0 { + query = query.Where("id = ?", req.Id) + } + + if req.Name != "" { + query = query.Where("name ILIKE ?", "%"+req.Name+"%") + } + + if req.Status != "" { + query = query.Where("status = ?", req.Status) + } + + if req.BranchId > 0 { + query = query.Where("branch_id = ?", req.BranchId) + } + + if req.Limit > 0 { + query = query.Limit(req.Limit) + } + + if req.Offset > 0 { + query = query.Offset(req.Offset) + } + + if err := query.Find(&studios).Error; err != nil { + logger.ContextLogger(ctx).Error("error when getting all studios", zap.Error(err)) + return nil, 0, err + } + + if err := s.db.Model(&entity.StudioDB{}).Where(query).Count(&total).Error; err != nil { + logger.ContextLogger(ctx).Error("error when counting studios", zap.Error(err)) + return nil, 0, err + } + + return studios, int(total), nil +} diff --git a/internal/repository/users/user.go b/internal/repository/users/user.go new file mode 100644 index 0000000..8ef2869 --- /dev/null +++ b/internal/repository/users/user.go @@ -0,0 +1,166 @@ +package users + +import ( + "context" + "furtuna-be/internal/common/logger" + "furtuna-be/internal/entity" + + "go.uber.org/zap" + "gorm.io/gorm" +) + +type UserRepository struct { + db *gorm.DB +} + +func NewUserRepository(db *gorm.DB) *UserRepository { + return &UserRepository{ + db: db, + } +} + +func (r *UserRepository) Create(ctx context.Context, user *entity.UserDB) (*entity.UserDB, error) { + tx := r.db.Begin() + + user.ID = 0 + if err := tx.Select("name", "email", "password", "status", "created_by", "nik", "user_type", "phone_number").Create(user).Error; err != nil { + tx.Rollback() + logError(ctx, "creating user", err) + return nil, err + } + + if err := tx.First(user, user.ID).Error; err != nil { + tx.Rollback() + logError(ctx, "retrieving user", err) + return nil, err + } + + userRole := user.ToUserRoleDB() + if err := tx.Create(userRole).Error; err != nil { + tx.Rollback() + logError(ctx, "creating user role", err) + return nil, err + } + + if err := tx.Commit().Error; err != nil { + tx.Rollback() + logError(ctx, "committing transaction", err) + return nil, err + } + + return user, nil +} + +func (b *UserRepository) GetAllUsers(ctx context.Context, req entity.UserSearch) (entity.UserList, int, error) { + var users []*entity.UserDB + var total int64 + + query := b.db.Table("users"). + Select("users.id, users.email, users.name, users.status, users.created_at, users.updated_at, ur.role_id, r.role_name, ur.partner_id, b.name as partner_name"). + Joins("LEFT JOIN user_roles ur ON users.id = ur.user_id"). + Joins("LEFT JOIN roles r ON ur.role_id = r.role_id"). + Joins("LEFT JOIN partners b ON ur.partner_id = b.id"). + Where("users.deleted_at is null") + + if req.Search != "" { + query = query.Where("users.name ILIKE ? or users.email ILIKE ? or r.role_name ILIKE ? or b.name ILIKE ? ", "%"+req.Search+"%", "%"+req.Search+"%", "%"+req.Search+"%", "%"+req.Search+"%") + } + + if req.Name != "" { + query = query.Where("users.name ILIKE ?", "%"+req.Name+"%") + } + + if req.RoleID > 0 { + query = query.Where("ur.role_id = ? ", req.RoleID) + } + + if req.PartnerID > 0 { + query = query.Where("ur.partner_id = ? ", req.PartnerID) + } + + // Get the total count without applying the limit and offset. + if err := query.Model(&entity.UserDB{}).Count(&total).Error; err != nil { + logger.ContextLogger(ctx).Error("error when count users", zap.Error(err)) + return nil, 0, err + } + + // Apply pagination. + query = query.Offset(req.Offset).Limit(req.Limit) + + if err := query.Scan(&users).Error; err != nil { + logger.ContextLogger(ctx).Error("error when get all users", zap.Error(err)) + return nil, 0, err + } + + return users, int(total), nil +} + +func (b *UserRepository) GetUserByID(ctx context.Context, id int64) (*entity.UserDB, error) { + var user *entity.UserDB + + query := b.db.Table("users"). + Select("users.id, users.email, users.name, users.status, users.created_at, users.updated_at, ur.role_id, r.role_name, ur.partner_id, b.name as partner_name"). + Joins("LEFT JOIN user_roles ur ON users.id = ur.user_id"). + Joins("LEFT JOIN roles r ON ur.role_id = r.role_id"). + Joins("LEFT JOIN partners b ON ur.partner_id = b.id"). + Where("users.id = ?", id) + + if err := query.Scan(&user).Error; err != nil { + logger.ContextLogger(ctx).Error("error when get user", zap.Error(err)) + return nil, err + } + + return user, nil +} + +func (b *UserRepository) GetUserByEmail(ctx context.Context, email string) (*entity.UserDB, error) { + var user *entity.UserDB + + query := b.db.Table("users"). + Select("users.id, users.email, users.name, users.status, users.created_at, users.updated_at, ur.role_id, r.role_name, ur.partner_id, b.name as partner_name"). + Joins("LEFT JOIN user_roles ur ON users.id = ur.user_id"). + Joins("LEFT JOIN roles r ON ur.role_id = r.role_id"). + Joins("LEFT JOIN partners b ON ur.partner_id = b.id"). + Where("users.email = ?", email) + + if err := query.Scan(&user).Error; err != nil { + logger.ContextLogger(ctx).Error("error when get user", zap.Error(err)) + return nil, err + } + + return user, nil +} + +func (r *UserRepository) UpdateUser(ctx context.Context, user *entity.UserDB) (*entity.UserDB, error) { + tx := r.db.Begin() + defer func() { + if r := recover(); r != nil { + tx.Rollback() + } + }() + + if err := tx.Select("name", "email", "password", "status", "deleted_at", "updated_by").Save(user).Error; err != nil { + tx.Rollback() + logError(ctx, "update user", err) + return nil, err + } + + userRole := user.ToUserRoleDB() + if err := tx.Model(userRole).Where("user_id = ?", user.ID).Updates(userRole).Error; err != nil { + tx.Rollback() + logError(ctx, "update user role", err) + return nil, err + } + + if err := tx.Commit().Error; err != nil { + tx.Rollback() + logError(ctx, "committing transaction", err) + return nil, err + } + + return user, nil +} + +func logError(ctx context.Context, action string, err error) { + logger.ContextLogger(ctx).Error("error when "+action, zap.Error(err)) +} diff --git a/internal/routes/routes.go b/internal/routes/routes.go new file mode 100644 index 0000000..158992d --- /dev/null +++ b/internal/routes/routes.go @@ -0,0 +1,60 @@ +package routes + +import ( + "furtuna-be/internal/handlers/http/branch" + "furtuna-be/internal/handlers/http/order" + "furtuna-be/internal/handlers/http/oss" + "furtuna-be/internal/handlers/http/partner" + "furtuna-be/internal/handlers/http/product" + "furtuna-be/internal/handlers/http/studio" + "furtuna-be/internal/handlers/http/user" + swaggerFiles "github.com/swaggo/files" + ginSwagger "github.com/swaggo/gin-swagger" + "net/http" + + "furtuna-be/internal/handlers/http/event" + "furtuna-be/internal/middlewares" + + "github.com/gin-gonic/gin" + + "furtuna-be/internal/app" + "furtuna-be/internal/handlers/http/auth" + "furtuna-be/internal/repository" + "furtuna-be/internal/services" +) + +func RegisterPublicRoutes(app *app.Server, serviceManager *services.ServiceManagerImpl, + repoManager *repository.RepoManagerImpl) { + route := app.Group("/") + route.GET("/", func(c *gin.Context) { + c.JSON(http.StatusOK, "HEALTHY") + }) + route.GET("/docs/*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) +} + +type HTTPHandlerRoutes interface { + Route(group *gin.RouterGroup, jwt gin.HandlerFunc) +} + +func RegisterPrivateRoutes(app *app.Server, serviceManager *services.ServiceManagerImpl, + repoManager *repository.RepoManagerImpl) { + approute := app.Group("/api/v1") + + authMiddleware := middlewares.AuthorizationMiddleware(repoManager.Crypto) + + serverRoutes := []HTTPHandlerRoutes{ + auth.NewAuthHandler(serviceManager.AuthSvc), + event.NewHandler(serviceManager.EventSvc), + user.NewHandler(serviceManager.UserSvc), + branch.NewHandler(serviceManager.BranchSvc), + studio.NewStudioHandler(serviceManager.StudioSvc), + product.NewHandler(serviceManager.ProductSvc), + order.NewHandler(serviceManager.OrderSvc), + oss.NewOssHandler(serviceManager.OSSSvc), + partner.NewHandler(serviceManager.PartnerSvc), + } + + for _, handler := range serverRoutes { + handler.Route(approute, authMiddleware) + } +} diff --git a/internal/services/auth/init.go b/internal/services/auth/init.go new file mode 100644 index 0000000..be1f033 --- /dev/null +++ b/internal/services/auth/init.go @@ -0,0 +1,48 @@ +package auth + +import ( + "context" + "furtuna-be/internal/entity" + + "go.uber.org/zap" + + "furtuna-be/internal/common/errors" + "furtuna-be/internal/common/logger" + "furtuna-be/internal/repository" +) + +type AuthServiceImpl struct { + authRepo repository.Auth + crypto repository.Crypto +} + +func New(authRepo repository.Auth, crypto repository.Crypto) *AuthServiceImpl { + return &AuthServiceImpl{ + authRepo: authRepo, + crypto: crypto, + } +} + +func (u *AuthServiceImpl) AuthenticateUser(ctx context.Context, email, password string) (*entity.AuthenticateUser, error) { + user, err := u.authRepo.CheckExistsUserAccount(ctx, email) + if err != nil { + logger.ContextLogger(ctx).Error("error when get user", zap.Error(err)) + return nil, errors.ErrorInternalServer + } + + if user == nil { + return nil, errors.ErrorUserIsNotFound + } + + if ok := u.crypto.CompareHashAndPassword(user.Password, password); !ok { + return nil, errors.ErrorUserInvalidLogin + } + + signedToken, err := u.crypto.GenerateJWT(user.ToUser()) + + if err != nil { + return nil, err + } + + return user.ToUserAuthenticate(signedToken), nil +} diff --git a/internal/services/branch/branch.go b/internal/services/branch/branch.go new file mode 100644 index 0000000..9f7183b --- /dev/null +++ b/internal/services/branch/branch.go @@ -0,0 +1,89 @@ +package branch + +import ( + "context" + "furtuna-be/internal/common/logger" + "furtuna-be/internal/common/mycontext" + "furtuna-be/internal/entity" + "furtuna-be/internal/repository" + + "go.uber.org/zap" +) + +type BranchService struct { + repo repository.Branch +} + +func NewBranchService(repo repository.Branch) *BranchService { + return &BranchService{ + repo: repo, + } +} + +func (s *BranchService) Create(ctx mycontext.Context, branchReq *entity.Branch) (*entity.Branch, error) { + branchDB := branchReq.ToBranchDB() + branchDB.CreatedBy = ctx.RequestedBy() + + branchDB, err := s.repo.CreateBranch(ctx, branchDB) + if err != nil { + logger.ContextLogger(ctx).Error("error when create branch", zap.Error(err)) + return nil, err + } + + return branchDB.ToBranch(), nil +} + +func (s *BranchService) Update(ctx mycontext.Context, id int64, branchReq *entity.Branch) (*entity.Branch, error) { + existingBranch, err := s.repo.GetBranchByID(ctx, id) + if err != nil { + return nil, err + } + + existingBranch.ToUpdatedBranch(ctx.RequestedBy(), *branchReq) + + updatedBranchDB, err := s.repo.UpdateBranch(ctx, existingBranch.ToBranchDB()) + if err != nil { + logger.ContextLogger(ctx).Error("error when update branch", zap.Error(err)) + return nil, err + } + + return updatedBranchDB.ToBranch(), nil +} + +func (s *BranchService) GetByID(ctx context.Context, id int64) (*entity.Branch, error) { + branchDB, err := s.repo.GetBranchByID(ctx, id) + if err != nil { + logger.ContextLogger(ctx).Error("error when get branch by id", zap.Error(err)) + return nil, err + } + + return branchDB.ToBranch(), nil +} + +func (s *BranchService) GetAll(ctx context.Context, search entity.BranchSearch) ([]*entity.Branch, int, error) { + branches, total, err := s.repo.GetAllBranches(ctx, search) + if err != nil { + logger.ContextLogger(ctx).Error("error when get all branches", zap.Error(err)) + return nil, 0, err + } + + return branches.ToBranchList(), total, nil +} + +func (s *BranchService) Delete(ctx mycontext.Context, id int64) error { + branchDB, err := s.repo.GetBranchByID(ctx, id) + if err != nil { + logger.ContextLogger(ctx).Error("error when get branch by id", zap.Error(err)) + return err + } + + branchDB.SetDeleted(ctx.RequestedBy()) + + _, err = s.repo.UpdateBranch(ctx, branchDB) + if err != nil { + logger.ContextLogger(ctx).Error("error when update branch", zap.Error(err)) + return err + } + + return nil +} diff --git a/internal/services/event/event.go b/internal/services/event/event.go new file mode 100644 index 0000000..0dd2e1e --- /dev/null +++ b/internal/services/event/event.go @@ -0,0 +1,88 @@ +package event + +import ( + "context" + + "go.uber.org/zap" + + "furtuna-be/internal/common/logger" + "furtuna-be/internal/entity" + "furtuna-be/internal/repository" +) + +type EventService struct { + repo repository.Event +} + +func NewEventService(repo repository.Event) *EventService { + return &EventService{ + repo: repo, + } +} + +func (s *EventService) Create(ctx context.Context, eventReq *entity.Event) (*entity.Event, error) { + eventDB := eventReq.ToEventDB() + + eventDB, err := s.repo.CreateEvent(ctx, eventDB) + if err != nil { + logger.ContextLogger(ctx).Error("error when create event", zap.Error(err)) + return nil, err + } + + return eventDB.ToEvent(), nil +} + +func (s *EventService) Update(ctx context.Context, id int64, eventReq *entity.Event) (*entity.Event, error) { + existingEvent, err := s.repo.GetEventByID(ctx, id) + if err != nil { + return nil, err + } + + existingEvent.ToUpdatedEvent(*eventReq) + + updatedEventDB, err := s.repo.UpdateEvent(ctx, existingEvent.ToEventDB()) + if err != nil { + logger.ContextLogger(ctx).Error("error when update event", zap.Error(err)) + return nil, err + } + + return updatedEventDB.ToEvent(), nil +} + +func (s *EventService) GetByID(ctx context.Context, id int64) (*entity.Event, error) { + eventDB, err := s.repo.GetEventByID(ctx, id) + if err != nil { + logger.ContextLogger(ctx).Error("error when get event by id", zap.Error(err)) + return nil, err + } + + return eventDB.ToEvent(), nil +} + +func (s *EventService) GetAll(ctx context.Context, search entity.EventSearch) ([]*entity.Event, int, error) { + events, total, err := s.repo.GetAllEvents(ctx, search.Name, search.Limit, search.Offset) + if err != nil { + logger.ContextLogger(ctx).Error("error when get all events", zap.Error(err)) + return nil, 0, err + } + + return events.ToEventList(), total, nil +} + +func (s *EventService) Delete(ctx context.Context, id int64) error { + eventDB, err := s.repo.GetEventByID(ctx, id) + if err != nil { + logger.ContextLogger(ctx).Error("error when get event by id", zap.Error(err)) + return err + } + + eventDB.SetDeleted() + + _, err = s.repo.UpdateEvent(ctx, eventDB) + if err != nil { + logger.ContextLogger(ctx).Error("error when update event", zap.Error(err)) + return err + } + + return nil +} diff --git a/internal/services/order/order.go b/internal/services/order/order.go new file mode 100644 index 0000000..2113a19 --- /dev/null +++ b/internal/services/order/order.go @@ -0,0 +1,147 @@ +package order + +import ( + "context" + "fmt" + "furtuna-be/internal/common/logger" + "furtuna-be/internal/common/mycontext" + "furtuna-be/internal/constants/order" + "furtuna-be/internal/constants/transaction" + "furtuna-be/internal/entity" + "furtuna-be/internal/repository" + + "go.uber.org/zap" +) + +type OrderService struct { + repo repository.Order + prodRepo repository.Product + studioRepo repository.Studio +} + +func NewOrderService(repo repository.Order, prodRepo repository.Product, studioRepo repository.Studio) *OrderService { + return &OrderService{ + repo: repo, + prodRepo: prodRepo, + studioRepo: studioRepo, + } +} + +func (o *OrderService) Create(ctx mycontext.Context, req *entity.Order) error { + + for i, oi := range req.OrderItem { + if oi.ItemType.IsProduct() { + item, err := o.prodRepo.GetProductByID(ctx, oi.ItemID) + if err != nil { + logger.ContextLogger(ctx).Error("error when get product item", zap.Error(err)) + return err + } + + req.OrderItem[i].Price = item.Price + req.OrderItem[i].CreatedBy = ctx.RequestedBy() + } else if oi.ItemType.IsStudio() { + //get data studio + item, err := o.studioRepo.GetStudioByID(ctx, oi.ItemID) + if err != nil { + logger.ContextLogger(ctx).Error("error when get studio item", zap.Error(err)) + return err + } + + req.OrderItem[i].Price = item.Price + req.OrderItem[i].CreatedBy = ctx.RequestedBy() + } + + } + + orderDB := req.ToOrderDB() + orderDB.CreatedBy = ctx.RequestedBy() + orderDB.Transaction.CreatedBy = ctx.RequestedBy() + + //set status + orderDB.Status = order.Paid + orderDB.Transaction.Status = transaction.Paid + + orderDB, err := o.repo.CreateOrder(ctx, orderDB) + if err != nil { + logger.ContextLogger(ctx).Error("error when create order", zap.Error(err)) + return err + } + + return nil +} + +func (s *OrderService) GetByID(ctx context.Context, id int64) (*entity.Order, error) { + orderDB, err := s.repo.GetOrderByID(ctx, id) + if err != nil { + logger.ContextLogger(ctx).Error("error when get order by id", zap.Error(err)) + return nil, err + } + + return orderDB.ToOrder(), nil +} + +func (s *OrderService) GetAll(ctx context.Context, search entity.OrderSearch) ([]*entity.Order, int, error) { + orders, total, err := s.repo.GetAllOrders(ctx, search) + if err != nil { + logger.ContextLogger(ctx).Error("error when get all orders", zap.Error(err)) + return nil, 0, err + } + + return orders.ToOrderList(), total, nil +} + +func (s *OrderService) GetTotalRevenue(ctx context.Context, search entity.OrderTotalRevenueSearch) (float64, int64, error) { + rev, trans, err := s.repo.GetTotalRevenue(ctx, search) + if err != nil { + logger.ContextLogger(ctx).Error("error when get total revenue orders", zap.Error(err)) + return 0, 0, err + } + + return rev, trans, nil +} + +func (s *OrderService) GetYearlyRevenue(ctx context.Context, year int) (entity.OrderYearlyRevenueList, error) { + result, err := s.repo.GetYearlyRevenue(ctx, year) + if err != nil { + logger.ContextLogger(ctx).Error("error when get yearly revenue orders", zap.Error(err)) + return nil, err + } + + return result, nil +} + +func (s *OrderService) GetBranchRevenue(ctx context.Context, req entity.OrderBranchRevenueSearch) (entity.OrderBranchRevenueList, error) { + result, err := s.repo.GetBranchRevenue(ctx, req) + if err != nil { + logger.ContextLogger(ctx).Error("error when get branch revenue orders", zap.Error(err)) + return nil, err + } + + return result, nil +} + +func (s *OrderService) UpdateStatus(ctx mycontext.Context, id int64, orderReq *entity.Order) (*entity.Order, error) { + existingOrder, err := s.repo.GetOrderByID(ctx, id) + if err != nil { + return nil, err + } + + //validate state + if !existingOrder.Status.IsNew() { + return nil, fmt.Errorf("Invalid order status") + } + + if existingOrder.Status == orderReq.Status { + return nil, fmt.Errorf("Order status already %s", existingOrder.Status) + } + + existingOrder.ToUpdatedOrder(ctx.RequestedBy(), *orderReq) + + _, err = s.repo.UpdateOrder(ctx, existingOrder) + if err != nil { + logger.ContextLogger(ctx).Error("error when update user", zap.Error(err)) + return nil, err + } + + return existingOrder.ToOrder(), nil +} diff --git a/internal/services/oss/impl.go b/internal/services/oss/impl.go new file mode 100644 index 0000000..d7ac7cf --- /dev/null +++ b/internal/services/oss/impl.go @@ -0,0 +1,45 @@ +package oss + +import ( + "context" + "fmt" + "furtuna-be/internal/entity" + "furtuna-be/internal/utils/generator" + "path" + + "github.com/go-playground/validator/v10" +) + +func (s *OssService) UploadFile(ctx context.Context, req *entity.UploadFileRequest) (*entity.UploadFileResponse, error) { + file := req.FileHeader + req.FileSize = file.Size + req.Ext = path.Ext(file.Filename) + validate := validator.New() + if err := validate.Struct(req); err != nil { + return nil, err + } + + // Open the file and read its content + srcFile, err := file.Open() + if err != nil { + return nil, err + } + defer srcFile.Close() + + fileContent := make([]byte, file.Size) + _, err = srcFile.Read(fileContent) + if err != nil { + return nil, err + } + + filePath := fmt.Sprintf("%v/%v%v", req.FolderName, generator.GenerateFileName(), req.Ext) + fileUrl, err := s.ossRepo.UploadFile(ctx, filePath, fileContent) + if err != nil { + return nil, err + } + + return &entity.UploadFileResponse{ + FilePath: filePath, + FileUrl: fileUrl, + }, nil +} diff --git a/internal/services/oss/init.go b/internal/services/oss/init.go new file mode 100644 index 0000000..9c764f4 --- /dev/null +++ b/internal/services/oss/init.go @@ -0,0 +1,13 @@ +package oss + +import "furtuna-be/internal/repository" + +type OssService struct { + ossRepo repository.OSSRepository +} + +func NewOSSService(ossRepo repository.OSSRepository) *OssService { + return &OssService{ + ossRepo: ossRepo, + } +} diff --git a/internal/services/partner/partner.go b/internal/services/partner/partner.go new file mode 100644 index 0000000..99db53e --- /dev/null +++ b/internal/services/partner/partner.go @@ -0,0 +1,89 @@ +package partner + +import ( + "context" + "furtuna-be/internal/common/logger" + "furtuna-be/internal/common/mycontext" + "furtuna-be/internal/entity" + "furtuna-be/internal/repository" + + "go.uber.org/zap" +) + +type PartnerService struct { + repo repository.PartnerRepository +} + +func NewPartnerService(repo repository.PartnerRepository) *PartnerService { + return &PartnerService{ + repo: repo, + } +} + +func (s *PartnerService) Create(ctx mycontext.Context, PartnerReq *entity.Partner) (*entity.Partner, error) { + PartnerDB := PartnerReq.ToPartnerDB() + PartnerDB.CreatedBy = ctx.RequestedBy() + + PartnerDB, err := s.repo.Create(ctx, PartnerDB) + if err != nil { + logger.ContextLogger(ctx).Error("error when create Partner", zap.Error(err)) + return nil, err + } + + return PartnerDB.ToPartner(), nil +} + +func (s *PartnerService) Update(ctx mycontext.Context, id int64, PartnerReq *entity.Partner) (*entity.Partner, error) { + existingPartner, err := s.repo.GetByID(ctx, id) + if err != nil { + return nil, err + } + + existingPartner.ToUpdatedPartner(ctx.RequestedBy(), *PartnerReq) + + updatedPartnerDB, err := s.repo.Update(ctx, existingPartner.ToPartnerDB()) + if err != nil { + logger.ContextLogger(ctx).Error("error when update Partner", zap.Error(err)) + return nil, err + } + + return updatedPartnerDB.ToPartner(), nil +} + +func (s *PartnerService) GetByID(ctx context.Context, id int64) (*entity.Partner, error) { + PartnerDB, err := s.repo.GetByID(ctx, id) + if err != nil { + logger.ContextLogger(ctx).Error("error when get Partner by id", zap.Error(err)) + return nil, err + } + + return PartnerDB.ToPartner(), nil +} + +func (s *PartnerService) GetAll(ctx context.Context, search entity.PartnerSearch) ([]*entity.Partner, int, error) { + Partneres, total, err := s.repo.GetAll(ctx, search) + if err != nil { + logger.ContextLogger(ctx).Error("error when get all Partneres", zap.Error(err)) + return nil, 0, err + } + + return Partneres.ToPartnerList(), total, nil +} + +func (s *PartnerService) Delete(ctx mycontext.Context, id int64) error { + PartnerDB, err := s.repo.GetByID(ctx, id) + if err != nil { + logger.ContextLogger(ctx).Error("error when get Partner by id", zap.Error(err)) + return err + } + + PartnerDB.SetDeleted(ctx.RequestedBy()) + + _, err = s.repo.Update(ctx, PartnerDB) + if err != nil { + logger.ContextLogger(ctx).Error("error when update Partner", zap.Error(err)) + return err + } + + return nil +} diff --git a/internal/services/product/product.go b/internal/services/product/product.go new file mode 100644 index 0000000..4e1dd6e --- /dev/null +++ b/internal/services/product/product.go @@ -0,0 +1,89 @@ +package product + +import ( + "context" + "furtuna-be/internal/common/logger" + "furtuna-be/internal/common/mycontext" + "furtuna-be/internal/entity" + "furtuna-be/internal/repository" + + "go.uber.org/zap" +) + +type ProductService struct { + repo repository.Product +} + +func NewProductService(repo repository.Product) *ProductService { + return &ProductService{ + repo: repo, + } +} + +func (s *ProductService) Create(ctx mycontext.Context, productReq *entity.Product) (*entity.Product, error) { + productDB := productReq.ToProductDB() + productDB.CreatedBy = ctx.RequestedBy() + + productDB, err := s.repo.CreateProduct(ctx, productDB) + if err != nil { + logger.ContextLogger(ctx).Error("error when create product", zap.Error(err)) + return nil, err + } + + return productDB.ToProduct(), nil +} + +func (s *ProductService) Update(ctx mycontext.Context, id int64, productReq *entity.Product) (*entity.Product, error) { + existingProduct, err := s.repo.GetProductByID(ctx, id) + if err != nil { + return nil, err + } + + existingProduct.ToUpdatedProduct(ctx.RequestedBy(), *productReq) + + updatedProductDB, err := s.repo.UpdateProduct(ctx, existingProduct.ToProductDB()) + if err != nil { + logger.ContextLogger(ctx).Error("error when update product", zap.Error(err)) + return nil, err + } + + return updatedProductDB.ToProduct(), nil +} + +func (s *ProductService) GetByID(ctx context.Context, id int64) (*entity.Product, error) { + productDB, err := s.repo.GetProductByID(ctx, id) + if err != nil { + logger.ContextLogger(ctx).Error("error when get product by id", zap.Error(err)) + return nil, err + } + + return productDB.ToProduct(), nil +} + +func (s *ProductService) GetAll(ctx context.Context, search entity.ProductSearch) ([]*entity.Product, int, error) { + products, total, err := s.repo.GetAllProducts(ctx, search) + if err != nil { + logger.ContextLogger(ctx).Error("error when get all products", zap.Error(err)) + return nil, 0, err + } + + return products.ToProductList(), total, nil +} + +func (s *ProductService) Delete(ctx mycontext.Context, id int64) error { + productDB, err := s.repo.GetProductByID(ctx, id) + if err != nil { + logger.ContextLogger(ctx).Error("error when get product by id", zap.Error(err)) + return err + } + + productDB.SetDeleted(ctx.RequestedBy()) + + _, err = s.repo.UpdateProduct(ctx, productDB) + if err != nil { + logger.ContextLogger(ctx).Error("error when update product", zap.Error(err)) + return err + } + + return nil +} diff --git a/internal/services/service.go b/internal/services/service.go new file mode 100644 index 0000000..ddcab25 --- /dev/null +++ b/internal/services/service.go @@ -0,0 +1,110 @@ +package services + +import ( + "context" + "furtuna-be/internal/common/mycontext" + "furtuna-be/internal/services/branch" + "furtuna-be/internal/services/order" + "furtuna-be/internal/services/oss" + "furtuna-be/internal/services/partner" + "furtuna-be/internal/services/product" + "furtuna-be/internal/services/studio" + "furtuna-be/internal/services/users" + + "furtuna-be/config" + "furtuna-be/internal/entity" + "furtuna-be/internal/repository" + "furtuna-be/internal/services/auth" + "furtuna-be/internal/services/event" +) + +type ServiceManagerImpl struct { + AuthSvc Auth + EventSvc Event + UserSvc User + BranchSvc Branch + StudioSvc Studio + ProductSvc Product + OrderSvc Order + OSSSvc OSSService + PartnerSvc Partner +} + +func NewServiceManagerImpl(cfg *config.Config, repo *repository.RepoManagerImpl) *ServiceManagerImpl { + return &ServiceManagerImpl{ + AuthSvc: auth.New(repo.Auth, repo.Crypto), + EventSvc: event.NewEventService(repo.Event), + UserSvc: users.NewUserService(repo.User, repo.Branch), + BranchSvc: branch.NewBranchService(repo.Branch), + StudioSvc: studio.NewStudioService(repo.Studio), + ProductSvc: product.NewProductService(repo.Product), + OrderSvc: order.NewOrderService(repo.Order, repo.Product, repo.Studio), + OSSSvc: oss.NewOSSService(repo.OSS), + PartnerSvc: partner.NewPartnerService(repo.Partner), + } +} + +type Auth interface { + AuthenticateUser(ctx context.Context, email, password string) (*entity.AuthenticateUser, error) +} + +type Event interface { + Create(ctx context.Context, eventReq *entity.Event) (*entity.Event, error) + Update(ctx context.Context, id int64, eventReq *entity.Event) (*entity.Event, error) + GetByID(ctx context.Context, id int64) (*entity.Event, error) + GetAll(ctx context.Context, search entity.EventSearch) ([]*entity.Event, int, error) + Delete(ctx context.Context, id int64) error +} + +type User interface { + Create(ctx mycontext.Context, userReq *entity.User) (*entity.User, error) + GetAll(ctx mycontext.Context, search entity.UserSearch) ([]*entity.User, int, error) + Update(ctx mycontext.Context, id int64, userReq *entity.User) (*entity.User, error) + GetByID(ctx mycontext.Context, id int64) (*entity.User, error) + Delete(ctx mycontext.Context, id int64) error +} + +type Branch interface { + Create(ctx mycontext.Context, branchReq *entity.Branch) (*entity.Branch, error) + Update(ctx mycontext.Context, id int64, branchReq *entity.Branch) (*entity.Branch, error) + GetByID(ctx context.Context, id int64) (*entity.Branch, error) + GetAll(ctx context.Context, search entity.BranchSearch) ([]*entity.Branch, int, error) + Delete(ctx mycontext.Context, id int64) error +} + +type Studio interface { + Create(ctx mycontext.Context, studioReq *entity.Studio) (*entity.Studio, error) + Update(ctx mycontext.Context, id int64, studioReq *entity.Studio) (*entity.Studio, error) + GetByID(ctx context.Context, id int64) (*entity.Studio, error) + Search(ctx context.Context, search entity.StudioSearch) ([]*entity.Studio, int, error) +} + +type Product interface { + Create(ctx mycontext.Context, productReq *entity.Product) (*entity.Product, error) + Update(ctx mycontext.Context, id int64, productReq *entity.Product) (*entity.Product, error) + GetByID(ctx context.Context, id int64) (*entity.Product, error) + GetAll(ctx context.Context, search entity.ProductSearch) ([]*entity.Product, int, error) + Delete(ctx mycontext.Context, id int64) error +} + +type Order interface { + Create(ctx mycontext.Context, req *entity.Order) error + UpdateStatus(ctx mycontext.Context, id int64, orderReq *entity.Order) (*entity.Order, error) + GetByID(ctx context.Context, id int64) (*entity.Order, error) + GetAll(ctx context.Context, search entity.OrderSearch) ([]*entity.Order, int, error) + GetTotalRevenue(ctx context.Context, search entity.OrderTotalRevenueSearch) (float64, int64, error) + GetYearlyRevenue(ctx context.Context, year int) (entity.OrderYearlyRevenueList, error) + GetBranchRevenue(ctx context.Context, req entity.OrderBranchRevenueSearch) (entity.OrderBranchRevenueList, error) +} + +type OSSService interface { + UploadFile(ctx context.Context, req *entity.UploadFileRequest) (*entity.UploadFileResponse, error) +} + +type Partner interface { + Create(ctx mycontext.Context, branchReq *entity.Partner) (*entity.Partner, error) + Update(ctx mycontext.Context, id int64, branchReq *entity.Partner) (*entity.Partner, error) + GetByID(ctx context.Context, id int64) (*entity.Partner, error) + GetAll(ctx context.Context, search entity.PartnerSearch) ([]*entity.Partner, int, error) + Delete(ctx mycontext.Context, id int64) error +} diff --git a/internal/services/studio/studio.go b/internal/services/studio/studio.go new file mode 100644 index 0000000..adb145c --- /dev/null +++ b/internal/services/studio/studio.go @@ -0,0 +1,70 @@ +package studio + +import ( + "context" + "furtuna-be/internal/common/logger" + "furtuna-be/internal/common/mycontext" + "furtuna-be/internal/entity" + "furtuna-be/internal/repository" + "go.uber.org/zap" +) + +type StudioService struct { + repo repository.Studio +} + +func NewStudioService(repo repository.Studio) *StudioService { + return &StudioService{ + repo: repo, + } +} + +func (s *StudioService) Create(ctx mycontext.Context, studioReq *entity.Studio) (*entity.Studio, error) { + newStudioDB := studioReq.NewStudiosDB() + newStudioDB.CreatedBy = ctx.RequestedBy() + + newStudioDB, err := s.repo.CreateStudio(ctx, newStudioDB) + if err != nil { + logger.ContextLogger(ctx).Error("error when creating studio", zap.Error(err)) + return nil, err + } + + return newStudioDB.ToStudio(), nil +} + +func (s *StudioService) Update(ctx mycontext.Context, id int64, studioReq *entity.Studio) (*entity.Studio, error) { + existingStudio, err := s.repo.GetStudioByID(ctx, id) + if err != nil { + return nil, err + } + + existingStudio.ToUpdatedStudio(ctx.RequestedBy(), *studioReq) + + updatedStudioDB, err := s.repo.UpdateStudio(ctx, existingStudio.ToStudioDB()) + if err != nil { + logger.ContextLogger(ctx).Error("error when updating studio", zap.Error(err)) + return nil, err + } + + return updatedStudioDB.ToStudio(), nil +} + +func (s *StudioService) GetByID(ctx context.Context, id int64) (*entity.Studio, error) { + studioDB, err := s.repo.GetStudioByID(ctx, id) + if err != nil { + logger.ContextLogger(ctx).Error("error when getting studio by id", zap.Error(err)) + return nil, err + } + + return studioDB.ToStudio(), nil +} + +func (s *StudioService) Search(ctx context.Context, search entity.StudioSearch) ([]*entity.Studio, int, error) { + studios, total, err := s.repo.SearchStudios(ctx, search) + if err != nil { + logger.ContextLogger(ctx).Error("error when getting all studios", zap.Error(err)) + return nil, 0, err + } + + return studios.ToStudioList(), total, nil +} diff --git a/internal/services/users/users.go b/internal/services/users/users.go new file mode 100644 index 0000000..e9cb3c1 --- /dev/null +++ b/internal/services/users/users.go @@ -0,0 +1,118 @@ +package users + +import ( + "fmt" + "furtuna-be/internal/common/mycontext" + + "go.uber.org/zap" + + "furtuna-be/internal/common/logger" + "furtuna-be/internal/entity" + "furtuna-be/internal/repository" +) + +type UserService struct { + repo repository.User + branchRepo repository.Branch +} + +func NewUserService(repo repository.User, branchRepo repository.Branch) *UserService { + return &UserService{ + repo: repo, + branchRepo: branchRepo, + } +} + +func (s *UserService) Create(ctx mycontext.Context, userReq *entity.User) (*entity.User, error) { + //check + userExist, err := s.repo.GetUserByEmail(ctx, userReq.Email) + if err != nil { + return nil, err + } + + if userExist != nil { + return nil, fmt.Errorf("Email already exist") + } + + userDB, err := userReq.ToUserDB(ctx.RequestedBy()) + if err != nil { + return nil, err + } + + userDB, err = s.repo.Create(ctx, userDB) + if err != nil { + logger.ContextLogger(ctx).Error("error when create user", zap.Error(err)) + return nil, err + } + + return userDB.ToUser(), nil +} + +func (s *UserService) GetAll(ctx mycontext.Context, search entity.UserSearch) ([]*entity.User, int, error) { + + users, total, err := s.repo.GetAllUsers(ctx, search) + if err != nil { + logger.ContextLogger(ctx).Error("error when get all users", zap.Error(err)) + return nil, 0, err + } + + return users.ToUserList(), total, nil +} + +func (s *UserService) GetByID(ctx mycontext.Context, id int64) (*entity.User, error) { + userDB, err := s.repo.GetUserByID(ctx, id) + if err != nil { + logger.ContextLogger(ctx).Error("error when get user by id", zap.Error(err)) + return nil, err + } + + return userDB.ToUser(), nil +} + +func (s *UserService) Update(ctx mycontext.Context, id int64, userReq *entity.User) (*entity.User, error) { + existingUser, err := s.repo.GetUserByID(ctx, id) + if err != nil { + return nil, err + } + + //if changed branch + if *userReq.PartnerID > 0 { + branch, err := s.branchRepo.GetBranchByID(ctx, *userReq.PartnerID) + if err != nil { + return nil, err + } + + existingUser.BranchName = branch.Name + } + + err = existingUser.ToUpdatedUser(*userReq) + if err != nil { + return nil, err + } + + updatedUserDB, err := s.repo.UpdateUser(ctx, existingUser) + if err != nil { + logger.ContextLogger(ctx).Error("error when update user", zap.Error(err)) + return nil, err + } + + return updatedUserDB.ToUser(), nil +} + +func (s *UserService) Delete(ctx mycontext.Context, id int64) error { + userDB, err := s.repo.GetUserByID(ctx, id) + if err != nil { + logger.ContextLogger(ctx).Error("error when get user by id", zap.Error(err)) + return err + } + + userDB.SetDeleted(ctx.RequestedBy()) + + _, err = s.repo.UpdateUser(ctx, userDB) + if err != nil { + logger.ContextLogger(ctx).Error("error when update user", zap.Error(err)) + return err + } + + return nil +} diff --git a/internal/utils/arrays.go b/internal/utils/arrays.go new file mode 100644 index 0000000..dfa3db8 --- /dev/null +++ b/internal/utils/arrays.go @@ -0,0 +1,20 @@ +package utils + +import ( + "fmt" + "strings" +) + +func ArrayToString(a []int64, delim string) string { + return strings.Trim(strings.Replace(fmt.Sprint(a), " ", delim, -1), "[]") +} + +func Contains(slice []string, item string) bool { + set := make(map[string]struct{}, len(slice)) + for _, s := range slice { + set[s] = struct{}{} + } + + _, ok := set[item] + return ok +} \ No newline at end of file diff --git a/internal/utils/currency.go b/internal/utils/currency.go new file mode 100644 index 0000000..c37c034 --- /dev/null +++ b/internal/utils/currency.go @@ -0,0 +1,43 @@ +package utils + +import ( + "fmt" + "math/rand" + "strconv" + "strings" + "time" +) + +func FormatCurrency(value float64) string { + currencyStr := strconv.FormatFloat(value, 'f', 2, 64) + split := strings.Split(currencyStr, ".") + n := len(split[0]) + if n <= 3 { + return fmt.Sprintf("Rp %s,%s", split[0], split[1]) + } + var result []string + for i := 0; i < n; i += 3 { + end := n - i + start := end - 3 + if start < 0 { + start = 0 + } + result = append([]string{split[0][start:end]}, result...) + } + currencyStr = strings.Join(result, ".") + return fmt.Sprintf("Rp %s,%s", currencyStr, split[1]) +} + +const charset = "abcdefghijklmnopqrstuvwxyz" + + "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + +var seededRand *rand.Rand = rand.New( + rand.NewSource(time.Now().UnixNano())) + +func GenerateRandomString(length int) string { + b := make([]byte, length) + for i := range b { + b[i] = charset[seededRand.Intn(len(charset))] + } + return string(b) +} diff --git a/internal/utils/currency_test.go b/internal/utils/currency_test.go new file mode 100644 index 0000000..26bbff6 --- /dev/null +++ b/internal/utils/currency_test.go @@ -0,0 +1,36 @@ +package utils + +import "testing" + +func TestFormatCurrency(t *testing.T) { + type args struct { + value float64 + } + tests := []struct { + name string + args args + want string + }{ + { + name: "hundred", + args: args{ + value: 626000, + }, + want: "Rp 626.000,00", + }, + { + name: "million", + args: args{ + value: 62603300, + }, + want: "Rp 62.603.300,00", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := FormatCurrency(tt.args.value); got != tt.want { + t.Errorf("FormatCurrency() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/internal/utils/generator/string-generator.go b/internal/utils/generator/string-generator.go new file mode 100644 index 0000000..b295bc0 --- /dev/null +++ b/internal/utils/generator/string-generator.go @@ -0,0 +1,36 @@ +package generator + +import ( + "fmt" + "math/rand" + "strconv" + "time" + + "github.com/google/uuid" +) + +func MedicalRecordNumberRand() string { + return RandStringRunes(10) +} + +func RandStringRunes(n int) string { + rand.Seed(time.Now().UnixNano()) // seed the random number generator + + const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + b := make([]byte, n) + for i := range b { + b[i] = letterBytes[rand.Intn(len(letterBytes))] + } + + return string(b) +} + +// format -> -