init project

This commit is contained in:
aditya.siregar 2023-10-08 15:59:42 +07:00
commit 67f1dbc850
141 changed files with 16879 additions and 0 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

17
.dockerignore Normal file
View File

@ -0,0 +1,17 @@
# Files
.dockerignore
.editorconfig
.gitignore
.env.*
Dockerfile
Makefile
LICENSE
**/*.md
**/*_test.go
*.out
bin/
# Folders
.git/
.github/
build/

8
.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
.idea/*
bin
config/env/*
!.env
vendor

35
.gitlab-ci.yml Normal file
View File

@ -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/<VERSION>/$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

24
Dockerfile Normal file
View File

@ -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"]

24
Dockerfile copy Normal file
View File

@ -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"]

21
LICENSE Normal file
View File

@ -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.

115
Makefile Normal file
View File

@ -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

64
README.md Normal file
View File

@ -0,0 +1,64 @@
<h1 align="center">
<img height="80" width="160" src="./assets/gopher-icon.gif" alt="Go"><br>Backend Template
</h1>
> 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).

68
config/configs.go Normal file
View File

@ -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,
}
}

17
config/crypto.go Normal file
View File

@ -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)
}

28
config/db.go Normal file
View File

@ -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
}

17
config/http.go Normal file
View File

@ -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
}

10
config/jwt.go Normal file
View File

@ -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"`
}

39
config/oss.go Normal file
View File

@ -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
}

7
config/server.go Normal file
View File

@ -0,0 +1,7 @@
package config
type Server struct {
Port string `mapstructure:"port"`
BaseUrl string `mapstructure:"common-url"`
LocalUrl string `mapstructure:"local-url"`
}

1
config/tuya.go Normal file
View File

@ -0,0 +1 @@
package config

8
docker-compose.yaml Normal file
View File

@ -0,0 +1,8 @@
version: "3.3"
services:
app:
build: .
ports:
- "3300:3300"
volumes:
- ./:/app/

3101
docs/docs.go Normal file

File diff suppressed because it is too large Load Diff

3072
docs/swagger.json Normal file

File diff suppressed because it is too large Load Diff

1833
docs/swagger.yaml Normal file

File diff suppressed because it is too large Load Diff

94
go.mod Normal file
View File

@ -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
)

797
go.sum Normal file
View File

@ -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=

View File

@ -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

48
internal/app/server.go Normal file
View File

@ -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")
}

View File

@ -0,0 +1,5 @@
package database
type Config interface {
ConnString() string
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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
}
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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,
}
}

View File

@ -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{})
}

View File

@ -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{}
}

View File

@ -0,0 +1,12 @@
package branch
type BranchStatus string
const (
Active BranchStatus = "Active"
Inactive BranchStatus = "Inactive"
)
func (b BranchStatus) toString() string {
return string(b)
}

View File

@ -0,0 +1,11 @@
package constants
const (
ContextRequestID string = "requestId"
)
type UserType string
func (u UserType) toString() string {
return string(u)
}

View File

@ -0,0 +1,12 @@
package device
type DeviceStatus string
const (
On DeviceStatus = "On"
Off DeviceStatus = "Off"
)
func (b DeviceStatus) toString() string {
return string(b)
}

View File

@ -0,0 +1,12 @@
package device
type DeviceConnectionStatus string
const (
Connected DeviceConnectionStatus = "Connected"
Disconnected DeviceConnectionStatus = "Disconnected"
)
func (b DeviceConnectionStatus) toString() string {
return string(b)
}

View File

@ -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
}

View File

@ -0,0 +1,9 @@
package constants
const (
OssLogLevelLogOff = "LogOff"
OssLogLevelDebug = "Debug"
OssLogLevelError = "Error"
OssLogLevelWarn = "Warn"
OssLogLevelInfo = "Info"
)

View File

@ -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
}

View File

@ -0,0 +1,9 @@
package role
type Role int64
const (
SuperAdmin Role = 1
BranchAdmin Role = 2
CasheerAdmin Role = 3
)

View File

@ -0,0 +1,12 @@
package studio
type StudioStatus string
const (
Active StudioStatus = "Active"
Inactive StudioStatus = "Inactive"
)
func (b StudioStatus) toString() string {
return string(b)
}

View File

@ -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)
}

View File

@ -0,0 +1,12 @@
package userstatus
type UserStatus string
const (
Active UserStatus = "Active"
Inactive UserStatus = "Inactive"
)
func (u UserStatus) toString() string {
return string(u)
}

143
internal/entity/auth.go Normal file
View File

@ -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 = &currentTime
o.UpdatedBy = updatedby
o.Status = userstatus.Inactive
}

83
internal/entity/branch.go Normal file
View File

@ -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 = &currentTime
o.UpdatedBy = updatedby
}

158
internal/entity/event.go Normal file
View File

@ -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 = &currentTime
}

12
internal/entity/jwt.go Normal file
View File

@ -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
}

166
internal/entity/order.go Normal file
View File

@ -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,
}
}

24
internal/entity/oss.go Normal file
View File

@ -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"`
}

View File

@ -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 = &currentTime
o.UpdatedBy = updatedBy
}

114
internal/entity/product.go Normal file
View File

@ -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 = &currentTime
o.UpdatedBy = updatedby
}

95
internal/entity/studio.go Normal file
View File

@ -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
}

View File

@ -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"
}

78
internal/entity/user.go Normal file
View File

@ -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
}

View File

@ -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,
})
}

View File

@ -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,
}
}

View File

@ -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,
}
}

View File

@ -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
}

View File

@ -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"
}

View File

@ -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,
}
}

View File

@ -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,
}
}

View File

@ -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,
}
}

View File

@ -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,
}
}

View File

@ -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"`
}

View File

@ -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,
}
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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,
}
}

View File

@ -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,
}
}

View File

@ -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,
}
}

View File

@ -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
}

View File

@ -0,0 +1,9 @@
package request
import (
"furtuna-be/internal/constants/transaction"
)
type Transaction struct {
PaymentMethod transaction.PaymentMethod
}

View File

@ -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,
}
}

View File

@ -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"`
}

View File

@ -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"`
}

View File

@ -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"`
}

View File

@ -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"`
}

View File

@ -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)
}

View File

@ -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"`
}

View File

@ -0,0 +1,7 @@
package response
type PagingMeta struct {
Page int `json:"page"`
Limit int `json:"limit"`
Total int64 `json:"total_data"`
}

View File

@ -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"`
}

View File

@ -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"`
}

View File

@ -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"`
}

View File

@ -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"`
}

View File

@ -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()
}
}

View File

@ -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")))
}
}
}

View File

@ -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,
)
})
}

View File

@ -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)
}

View File

@ -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)
}
}

View File

@ -0,0 +1 @@
package auth

View File

@ -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
}

View File

@ -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
}

View File

@ -0,0 +1,4 @@
//go:generate mockery --name Crypto --filename crypto.go --output ./mock --with-expecter
package crypto

View File

@ -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
}
}

View File

@ -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
}

View File

@ -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")
}

View File

@ -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)
}

View File

@ -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
}

Some files were not shown because too many files have changed in this diff Show More