├── .gitignore
├── README.md
├── config
├── config.go
└── config.yaml.example
├── db
└── db.go
├── go-base-backend.postman_collection.json
├── go.mod
├── go.sum
├── internal
├── dtos
│ ├── article_dto.go
│ ├── auth_dto.go
│ └── user_dto.go
├── errors
│ ├── error.go
│ └── pgerrcode.go
├── handlers
│ ├── article_handler.go
│ ├── article_handler_test.go
│ ├── auth_handler.go
│ ├── auth_handler_test.go
│ ├── init.go
│ ├── init_test.go
│ ├── user_handler.go
│ └── user_handler_test.go
├── helpers
│ ├── hash.go
│ ├── jwt.go
│ └── response.go
├── middlewares
│ └── authorize_jwt.go
├── mocks
│ ├── repositories
│ │ ├── mock_article_repository.go
│ │ └── mock_user_repository.go
│ └── services
│ │ ├── mock_article_service.go
│ │ ├── mock_auth_service.go
│ │ └── mock_user_service.go
├── models
│ ├── article.go
│ └── user.go
├── repositories
│ ├── article_repository.go
│ ├── article_repository_test.go
│ ├── user_repository.go
│ └── user_repository_test.go
├── routes
│ └── routes.go
└── services
│ ├── article_service.go
│ ├── article_service_test.go
│ ├── auth_service.go
│ ├── auth_service_test.go
│ ├── user_service.go
│ └── user_service_test.go
├── main.go
└── makefile
/.gitignore:
--------------------------------------------------------------------------------
1 | config/*.yaml
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Focused on PostgreSQL database (master branch)
2 |
3 | # go-base-backend
4 |
5 | ## Release List
6 | 1. Database using PostgreSQL: master branch
7 | 2. Database using MySQL: checkout branch [mysql](https://github.com/geshtng/go-base-backend/tree/mysql)
8 | 3. Support for gRPC (database using PostgreSQL): checkout branch [grpc-postgresql](https://github.com/geshtng/go-base-backend/tree/grpc-postgresql)
9 | 4. Support for gRPC (database using MySQL): checkout branch [grpc-mysql](https://github.com/geshtng/go-base-backend/tree/grpc-mysql)
10 |
11 | ## Description
12 | Example implementation go backend (clean) architecture. It is very easy to configure.
13 |
14 | This project has 4 domain layers:
15 |
16 | - Model
17 |
18 | This layer will save models that were used in the other domains. Can be accessed from any other layer and other domains.
19 | - Handler
20 |
21 | This layer will do the job as the presenter of the application.
22 | - Service
23 |
24 | This layer will do the job as a controller and handle the business logic.
25 | - Repository
26 |
27 | This layer is the one that stores the database handler. Any operation on database like querying, inserting, updating, and deleting, will be done on this layer.
28 |
29 | ## Setup
30 | 1. Clone project.
31 | ```bash
32 | $ git clone https://github.com/geshtng/go-base-backend
33 | ```
34 | 2. Init Database.
35 | - Create a new database. Example database name: `go_base_backend`.
36 | - After you run the server, it will automatically create tables and relations in the database `go_base_backend`.
37 | 3. Change config.
38 |
39 | Make a new file named `config.yaml` inside the folder `/config`.
40 | Use `config.yaml.example` to see the example or see the config sample below.
41 | ```yaml
42 | app:
43 | name: go-base-backend
44 |
45 | server:
46 | host: localhost
47 | port: 8080
48 |
49 | database:
50 | db_host: localhost
51 | db_port: 5432
52 | db_name: go_base_backend
53 | db_username: postgres
54 | db_password: postgres
55 | db_postgres_ssl_mode: disable
56 |
57 | jwt:
58 | expired: 60
59 | issuer: go-base-backend
60 | secret: sKk6E5gpVD
61 | ```
62 |
63 | ## Run the Project
64 | ```bash
65 | $ make run
66 | ```
67 |
68 | ## API List
69 | You can find API list on file `routes/routes.go`
70 |
71 | ## Example API with Authentication
72 | I have set up an example of an API that uses authentication:
73 | ```http
74 | GET localhost:8080/profiles
75 | ```
76 |
77 | ## Postman Collection
78 | Import files `go-base-backend.postman_collection.json` to your postman
79 |
80 | ## Framework and Library
81 | - [Gin](https://github.com/gin-gonic/gin)
82 | - [Gorm](https://github.com/go-gorm/gorm)
83 | - [Copier](https://github.com/jinzhu/copier)
84 | - [Golang-jwt](https://github.com/golang-jwt/jwt)
85 | - [Viper](https://github.com/spf13/viper)
86 | - [Testify](https://github.com/stretchr/testify)
87 | - [Go-Sqlmock](https://github.com/DATA-DOG/go-sqlmock)
88 | - Other libraries listed in `go.mod`
89 |
--------------------------------------------------------------------------------
/config/config.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "fmt"
5 | "os"
6 |
7 | "github.com/spf13/viper"
8 | )
9 |
10 | type Server struct {
11 | Host string `mapstructure:"host"`
12 | Port int `mapstructure:"port"`
13 | }
14 |
15 | type Database struct {
16 | DBHost string `mapstructure:"db_host"`
17 | DBPort string `mapstructure:"db_port"`
18 | DBName string `mapstructure:"db_name"`
19 | DBUsername string `mapstructure:"db_username"`
20 | DBPassword string `mapstructure:"db_password"`
21 | DBPostgresSslMode string `mapstructure:"db_postgres_ssl_mode"`
22 | }
23 |
24 | type JwtConfig struct {
25 | Expired string
26 | Issuer string
27 | Secret string
28 | }
29 |
30 | type Jwt struct {
31 | Expired string `mapstructure:"expired"`
32 | Issuer string `mapstructure:"issuer"`
33 | Secret string `mapstructure:"secret"`
34 | }
35 |
36 | type Configuration struct {
37 | Server `mapstructure:"server"`
38 | Database `mapstructure:"database"`
39 | Jwt `mapstructure:"jwt"`
40 | }
41 |
42 | func initConfig() {
43 | viper.AddConfigPath("./config")
44 | viper.SetConfigName("config")
45 | viper.SetConfigType("yaml")
46 |
47 | if err := viper.ReadInConfig(); err != nil {
48 | fmt.Println("Error reading config file: ", err)
49 | os.Exit(1)
50 | }
51 | }
52 |
53 | func InitConfigDsn() string {
54 | initConfig()
55 |
56 | var config Configuration
57 |
58 | err := viper.Unmarshal(&config)
59 | if err != nil {
60 | fmt.Println("[Config][InitConfigDsn] Unable to decode into struct:", err)
61 | }
62 |
63 | dsn := `postgres://` + config.DBUsername + `:` + config.DBPassword + `@` + config.DBHost + `:` + config.DBPort + `/` + config.DBName + `?sslmode=` + config.DBPostgresSslMode
64 |
65 | return dsn
66 | }
67 |
68 | func InitConfigPort() string {
69 | initConfig()
70 |
71 | var config Configuration
72 |
73 | err := viper.Unmarshal(&config)
74 | if err != nil {
75 | fmt.Println("[Config][InitConfigPort] Uncable to decode into struct:", err)
76 | }
77 |
78 | port := `:` + fmt.Sprint(config.Port)
79 |
80 | return port
81 | }
82 |
83 | func InitJwt() JwtConfig {
84 | initConfig()
85 |
86 | var config Configuration
87 |
88 | err := viper.Unmarshal(&config)
89 | if err != nil {
90 | fmt.Println("[Config][InitJwt] Uncable to decode into struct:", err)
91 | }
92 |
93 | jwtConfig := JwtConfig{
94 | Expired: config.Expired,
95 | Issuer: config.Issuer,
96 | Secret: config.Secret,
97 | }
98 |
99 | return jwtConfig
100 | }
101 |
--------------------------------------------------------------------------------
/config/config.yaml.example:
--------------------------------------------------------------------------------
1 | app:
2 | name: go-base-backend
3 |
4 | server:
5 | host:
6 | port:
7 |
8 | database:
9 | db_host:
10 | db_port:
11 | db_name:
12 | db_username:
13 | db_password:
14 | db_postgres_ssl_mode:
15 |
16 | jwt:
17 | expired:
18 | issuer:
19 | secret:
--------------------------------------------------------------------------------
/db/db.go:
--------------------------------------------------------------------------------
1 | package db
2 |
3 | import (
4 | "gorm.io/driver/postgres"
5 | "gorm.io/gorm"
6 |
7 | "github.com/geshtng/go-base-backend/config"
8 | "github.com/geshtng/go-base-backend/internal/models"
9 | )
10 |
11 | var db *gorm.DB
12 |
13 | func Connect() (err error) {
14 | conf := config.InitConfigDsn()
15 |
16 | db, err = gorm.Open(postgres.Open(conf), &gorm.Config{})
17 | if err != nil {
18 | panic(err)
19 | }
20 |
21 | err = db.AutoMigrate(
22 | &models.Article{},
23 | &models.User{},
24 | )
25 |
26 | if err != nil {
27 | return err
28 | }
29 |
30 | return nil
31 | }
32 |
33 | func Get() *gorm.DB {
34 | return db
35 | }
36 |
--------------------------------------------------------------------------------
/go-base-backend.postman_collection.json:
--------------------------------------------------------------------------------
1 | {
2 | "info": {
3 | "_postman_id": "5d0fca69-2f09-4b0c-ba64-f1bbeeff9754",
4 | "name": "go-base-backend",
5 | "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
6 | "_exporter_id": "23028979"
7 | },
8 | "item": [
9 | {
10 | "name": "Articles",
11 | "item": [
12 | {
13 | "name": "Get All Articles",
14 | "request": {
15 | "method": "GET",
16 | "header": [],
17 | "url": {
18 | "raw": "{{host}}/articles",
19 | "host": [
20 | "{{host}}"
21 | ],
22 | "path": [
23 | "articles"
24 | ]
25 | }
26 | },
27 | "response": []
28 | },
29 | {
30 | "name": "Get Article By ID",
31 | "request": {
32 | "method": "GET",
33 | "header": [],
34 | "url": {
35 | "raw": "{{host}}/articles/:id",
36 | "host": [
37 | "{{host}}"
38 | ],
39 | "path": [
40 | "articles",
41 | ":id"
42 | ],
43 | "variable": [
44 | {
45 | "key": "id",
46 | "value": ""
47 | }
48 | ]
49 | }
50 | },
51 | "response": []
52 | },
53 | {
54 | "name": "Create Article",
55 | "request": {
56 | "method": "POST",
57 | "header": [],
58 | "body": {
59 | "mode": "raw",
60 | "raw": "{\n \"title\": \"Sample Title\",\n \"description\": \"Sample Description\"\n}",
61 | "options": {
62 | "raw": {
63 | "language": "json"
64 | }
65 | }
66 | },
67 | "url": {
68 | "raw": "{{host}}/articles",
69 | "host": [
70 | "{{host}}"
71 | ],
72 | "path": [
73 | "articles"
74 | ]
75 | }
76 | },
77 | "response": []
78 | },
79 | {
80 | "name": "Update Article",
81 | "request": {
82 | "method": "PUT",
83 | "header": [],
84 | "body": {
85 | "mode": "raw",
86 | "raw": "{\n \"title\": \"Sample Title Update\",\n \"description\": \"Sample Description Update\"\n}",
87 | "options": {
88 | "raw": {
89 | "language": "json"
90 | }
91 | }
92 | },
93 | "url": {
94 | "raw": "{{host}}/articles/:id",
95 | "host": [
96 | "{{host}}"
97 | ],
98 | "path": [
99 | "articles",
100 | ":id"
101 | ],
102 | "variable": [
103 | {
104 | "key": "id",
105 | "value": "1"
106 | }
107 | ]
108 | }
109 | },
110 | "response": []
111 | },
112 | {
113 | "name": "Delete Article",
114 | "request": {
115 | "method": "DELETE",
116 | "header": [],
117 | "url": {
118 | "raw": "{{host}}/articles/:id",
119 | "host": [
120 | "{{host}}"
121 | ],
122 | "path": [
123 | "articles",
124 | ":id"
125 | ],
126 | "variable": [
127 | {
128 | "key": "id",
129 | "value": "1"
130 | }
131 | ]
132 | }
133 | },
134 | "response": []
135 | }
136 | ]
137 | },
138 | {
139 | "name": "Auth",
140 | "item": [
141 | {
142 | "name": "Login",
143 | "request": {
144 | "method": "POST",
145 | "header": [],
146 | "body": {
147 | "mode": "raw",
148 | "raw": "{\n \"username\": \"username\",\n \"password\": \"password\"\n}",
149 | "options": {
150 | "raw": {
151 | "language": "json"
152 | }
153 | }
154 | },
155 | "url": {
156 | "raw": "{{host}}/login",
157 | "host": [
158 | "{{host}}"
159 | ],
160 | "path": [
161 | "login"
162 | ]
163 | }
164 | },
165 | "response": []
166 | },
167 | {
168 | "name": "Register",
169 | "request": {
170 | "method": "POST",
171 | "header": [],
172 | "body": {
173 | "mode": "raw",
174 | "raw": "{\n \"username\": \"username\",\n \"password\": \"password\"\n}",
175 | "options": {
176 | "raw": {
177 | "language": "json"
178 | }
179 | }
180 | },
181 | "url": {
182 | "raw": "{{host}}/register",
183 | "host": [
184 | "{{host}}"
185 | ],
186 | "path": [
187 | "register"
188 | ]
189 | }
190 | },
191 | "response": []
192 | }
193 | ]
194 | },
195 | {
196 | "name": "User",
197 | "item": [
198 | {
199 | "name": "Get By ID",
200 | "request": {
201 | "auth": {
202 | "type": "bearer",
203 | "bearer": [
204 | {
205 | "key": "token",
206 | "value": "",
207 | "type": "string"
208 | }
209 | ]
210 | },
211 | "method": "GET",
212 | "header": [],
213 | "url": {
214 | "raw": "{{host}}/profiles",
215 | "host": [
216 | "{{host}}"
217 | ],
218 | "path": [
219 | "users"
220 | ]
221 | }
222 | },
223 | "response": []
224 | }
225 | ],
226 | "auth": {
227 | "type": "bearer",
228 | "bearer": [
229 | {
230 | "key": "token",
231 | "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnby1iYXNlLWJhY2tlbmQiLCJleHAiOjE2NjU2NjYyNDYsImlhdCI6MTY2NTY2MjY0NiwiZGF0YSI6eyJJRCI6MSwiVXNlcm5hbWUiOiJ1c2VybmFtZVgiLCJQYXNzd29yZCI6IiJ9fQ.UR3ZB-J9FtB6DZwSC128ya8xPzX1JJwWF2tt4IYBzeE",
232 | "type": "string"
233 | }
234 | ]
235 | },
236 | "event": [
237 | {
238 | "listen": "prerequest",
239 | "script": {
240 | "type": "text/javascript",
241 | "exec": [
242 | ""
243 | ]
244 | }
245 | },
246 | {
247 | "listen": "test",
248 | "script": {
249 | "type": "text/javascript",
250 | "exec": [
251 | ""
252 | ]
253 | }
254 | }
255 | ]
256 | }
257 | ],
258 | "event": [
259 | {
260 | "listen": "prerequest",
261 | "script": {
262 | "type": "text/javascript",
263 | "exec": [
264 | ""
265 | ]
266 | }
267 | },
268 | {
269 | "listen": "test",
270 | "script": {
271 | "type": "text/javascript",
272 | "exec": [
273 | ""
274 | ]
275 | }
276 | }
277 | ],
278 | "variable": [
279 | {
280 | "key": "host",
281 | "value": "localhost:8080",
282 | "type": "string"
283 | }
284 | ]
285 | }
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/geshtng/go-base-backend
2 |
3 | go 1.23
4 |
5 | require (
6 | github.com/DATA-DOG/go-sqlmock v1.5.2
7 | github.com/gin-gonic/gin v1.8.1
8 | github.com/golang-jwt/jwt/v4 v4.4.2
9 | github.com/jackc/pgconn v1.13.0
10 | github.com/jinzhu/copier v0.3.5
11 | github.com/spf13/viper v1.13.0
12 | github.com/stretchr/testify v1.9.0
13 | golang.org/x/crypto v0.0.0-20221012134737-56aed061732a
14 | gorm.io/driver/postgres v1.4.4
15 | gorm.io/gorm v1.24.0
16 | )
17 |
18 | require (
19 | github.com/davecgh/go-spew v1.1.1 // indirect
20 | github.com/fsnotify/fsnotify v1.5.4 // indirect
21 | github.com/gin-contrib/sse v0.1.0 // indirect
22 | github.com/go-playground/locales v0.14.0 // indirect
23 | github.com/go-playground/universal-translator v0.18.0 // indirect
24 | github.com/go-playground/validator/v10 v10.10.0 // indirect
25 | github.com/goccy/go-json v0.9.7 // indirect
26 | github.com/hashicorp/hcl v1.0.0 // indirect
27 | github.com/jackc/chunkreader/v2 v2.0.1 // indirect
28 | github.com/jackc/pgio v1.0.0 // indirect
29 | github.com/jackc/pgpassfile v1.0.0 // indirect
30 | github.com/jackc/pgproto3/v2 v2.3.1 // indirect
31 | github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect
32 | github.com/jackc/pgtype v1.12.0 // indirect
33 | github.com/jackc/pgx/v4 v4.17.2 // indirect
34 | github.com/jinzhu/inflection v1.0.0 // indirect
35 | github.com/jinzhu/now v1.1.5 // indirect
36 | github.com/json-iterator/go v1.1.12 // indirect
37 | github.com/leodido/go-urn v1.2.1 // indirect
38 | github.com/magiconair/properties v1.8.6 // indirect
39 | github.com/mattn/go-isatty v0.0.14 // indirect
40 | github.com/mitchellh/mapstructure v1.5.0 // indirect
41 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
42 | github.com/modern-go/reflect2 v1.0.2 // indirect
43 | github.com/pelletier/go-toml v1.9.5 // indirect
44 | github.com/pelletier/go-toml/v2 v2.0.5 // indirect
45 | github.com/pmezard/go-difflib v1.0.0 // indirect
46 | github.com/spf13/afero v1.8.2 // indirect
47 | github.com/spf13/cast v1.5.0 // indirect
48 | github.com/spf13/jwalterweatherman v1.1.0 // indirect
49 | github.com/spf13/pflag v1.0.5 // indirect
50 | github.com/stretchr/objx v0.5.2 // indirect
51 | github.com/subosito/gotenv v1.4.1 // indirect
52 | github.com/ugorji/go/codec v1.2.7 // indirect
53 | golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2 // indirect
54 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect
55 | golang.org/x/text v0.3.8 // indirect
56 | google.golang.org/protobuf v1.28.0 // indirect
57 | gopkg.in/ini.v1 v1.67.0 // indirect
58 | gopkg.in/yaml.v2 v2.4.0 // indirect
59 | gopkg.in/yaml.v3 v3.0.1 // indirect
60 | )
61 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
2 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
3 | cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
4 | cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
5 | cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
6 | cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
7 | cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
8 | cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
9 | cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
10 | cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
11 | cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
12 | cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
13 | cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
14 | cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
15 | cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
16 | cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
17 | cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
18 | cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
19 | cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY=
20 | cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
21 | cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
22 | cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
23 | cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
24 | cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
25 | cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
26 | cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
27 | cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
28 | cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
29 | cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
30 | cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
31 | cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
32 | cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
33 | cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
34 | cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
35 | cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
36 | cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
37 | cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
38 | dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
39 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
40 | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
41 | github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU=
42 | github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU=
43 | github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=
44 | github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
45 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
46 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
47 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
48 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
49 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
50 | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
51 | github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
52 | github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
53 | github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
54 | github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
55 | github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
56 | github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
57 | github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
58 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
59 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
60 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
61 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
62 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
63 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
64 | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
65 | github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
66 | github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
67 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
68 | github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
69 | github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps=
70 | github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
71 | github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
72 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
73 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
74 | github.com/gin-gonic/gin v1.8.1 h1:4+fr/el88TOO3ewCmQr8cx/CtZ/umlIRIs5M4NTNjf8=
75 | github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk=
76 | github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
77 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
78 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
79 | github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
80 | github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
81 | github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
82 | github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
83 | github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU=
84 | github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
85 | github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho=
86 | github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
87 | github.com/go-playground/validator/v10 v10.10.0 h1:I7mrTYv78z8k8VXa/qJlOlEXn/nBh+BF8dHX5nt/dr0=
88 | github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
89 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
90 | github.com/goccy/go-json v0.9.7 h1:IcB+Aqpx/iMHu5Yooh7jEzJk1JZ7Pjtmys2ukPr7EeM=
91 | github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
92 | github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
93 | github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
94 | github.com/golang-jwt/jwt/v4 v4.4.2 h1:rcc4lwaZgFMCZ5jxF9ABolDcIHdBytAFgqFPbSJQAYs=
95 | github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
96 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
97 | github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
98 | github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
99 | github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
100 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
101 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
102 | github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
103 | github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
104 | github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
105 | github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
106 | github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
107 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
108 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
109 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
110 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
111 | github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
112 | github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
113 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
114 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
115 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
116 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
117 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
118 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
119 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
120 | github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
121 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
122 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
123 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
124 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
125 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
126 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
127 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
128 | github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
129 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
130 | github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
131 | github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
132 | github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
133 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
134 | github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
135 | github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
136 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
137 | github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
138 | github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
139 | github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
140 | github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
141 | github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
142 | github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
143 | github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
144 | github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
145 | github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
146 | github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
147 | github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
148 | github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
149 | github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
150 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
151 | github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
152 | github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
153 | github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
154 | github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
155 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
156 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
157 | github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
158 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
159 | github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
160 | github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
161 | github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
162 | github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
163 | github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8=
164 | github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
165 | github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA=
166 | github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE=
167 | github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s=
168 | github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o=
169 | github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY=
170 | github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
171 | github.com/jackc/pgconn v1.13.0 h1:3L1XMNV2Zvca/8BYhzcRFS70Lr0WlDg16Di6SFGAbys=
172 | github.com/jackc/pgconn v1.13.0/go.mod h1:AnowpAqO4CMIIJNZl2VJp+KrkAZciAkhEl0W0JIobpI=
173 | github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE=
174 | github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
175 | github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=
176 | github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c=
177 | github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc=
178 | github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak=
179 | github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
180 | github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
181 | github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78=
182 | github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA=
183 | github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg=
184 | github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
185 | github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
186 | github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
187 | github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
188 | github.com/jackc/pgproto3/v2 v2.3.1 h1:nwj7qwf0S+Q7ISFfBndqeLwSwxs+4DPsbRFjECT1Y4Y=
189 | github.com/jackc/pgproto3/v2 v2.3.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
190 | github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg=
191 | github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
192 | github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=
193 | github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
194 | github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
195 | github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM=
196 | github.com/jackc/pgtype v1.12.0 h1:Dlq8Qvcch7kiehm8wPGIW0W3KsCCHJnRacKW0UM8n5w=
197 | github.com/jackc/pgtype v1.12.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
198 | github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
199 | github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
200 | github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
201 | github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs=
202 | github.com/jackc/pgx/v4 v4.17.2 h1:0Ut0rpeKwvIVbMQ1KbMBU4h6wxehBI535LK6Flheh8E=
203 | github.com/jackc/pgx/v4 v4.17.2/go.mod h1:lcxIZN44yMIrWI78a5CpucdD14hX0SBDbNRvjDBItsw=
204 | github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
205 | github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
206 | github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
207 | github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
208 | github.com/jinzhu/copier v0.3.5 h1:GlvfUwHk62RokgqVNvYsku0TATCF7bAHVwEXoBh3iJg=
209 | github.com/jinzhu/copier v0.3.5/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
210 | github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
211 | github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
212 | github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
213 | github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
214 | github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
215 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
216 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
217 | github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
218 | github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
219 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
220 | github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE=
221 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
222 | github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
223 | github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
224 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
225 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
226 | github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
227 | github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
228 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
229 | github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
230 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
231 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
232 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
233 | github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
234 | github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
235 | github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
236 | github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
237 | github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
238 | github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8=
239 | github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
240 | github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo=
241 | github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
242 | github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
243 | github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
244 | github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
245 | github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
246 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
247 | github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
248 | github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
249 | github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
250 | github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
251 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
252 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
253 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
254 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
255 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
256 | github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
257 | github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
258 | github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwbMiyQg=
259 | github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas=
260 | github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
261 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
262 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
263 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
264 | github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
265 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
266 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
267 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
268 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
269 | github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
270 | github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
271 | github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
272 | github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
273 | github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
274 | github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
275 | github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
276 | github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
277 | github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
278 | github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
279 | github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
280 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
281 | github.com/spf13/afero v1.8.2 h1:xehSyVa0YnHWsJ49JFljMpg1HX19V6NDZ1fkm1Xznbo=
282 | github.com/spf13/afero v1.8.2/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo=
283 | github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
284 | github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU=
285 | github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
286 | github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
287 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
288 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
289 | github.com/spf13/viper v1.13.0 h1:BWSJ/M+f+3nmdz9bxB+bWX28kkALN2ok11D0rSo8EJU=
290 | github.com/spf13/viper v1.13.0/go.mod h1:Icm2xNL3/8uyh/wFuB1jI7TiTNKp8632Nwegu+zgdYw=
291 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
292 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
293 | github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
294 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
295 | github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
296 | github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
297 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
298 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
299 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
300 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
301 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
302 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
303 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
304 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
305 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
306 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
307 | github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs=
308 | github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
309 | github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
310 | github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0=
311 | github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
312 | github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
313 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
314 | github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
315 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
316 | github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
317 | go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
318 | go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
319 | go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
320 | go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
321 | go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
322 | go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
323 | go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
324 | go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
325 | go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
326 | go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
327 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
328 | go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
329 | go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
330 | go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
331 | go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
332 | go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
333 | go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
334 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
335 | golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
336 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
337 | golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
338 | golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
339 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
340 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
341 | golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
342 | golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
343 | golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
344 | golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
345 | golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
346 | golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
347 | golang.org/x/crypto v0.0.0-20221012134737-56aed061732a h1:NmSIgad6KjE6VvHciPZuNRTKxGhlPfD6OA87W/PLkqg=
348 | golang.org/x/crypto v0.0.0-20221012134737-56aed061732a/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
349 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
350 | golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
351 | golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
352 | golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
353 | golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
354 | golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
355 | golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
356 | golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
357 | golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
358 | golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
359 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
360 | golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
361 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
362 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
363 | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
364 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
365 | golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
366 | golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
367 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
368 | golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
369 | golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
370 | golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
371 | golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
372 | golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
373 | golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
374 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
375 | golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
376 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
377 | golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
378 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
379 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
380 | golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
381 | golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
382 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
383 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
384 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
385 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
386 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
387 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
388 | golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
389 | golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
390 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
391 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
392 | golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
393 | golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
394 | golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
395 | golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
396 | golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
397 | golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
398 | golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
399 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
400 | golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
401 | golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
402 | golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
403 | golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
404 | golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
405 | golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
406 | golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
407 | golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
408 | golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
409 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
410 | golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
411 | golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
412 | golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
413 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
414 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
415 | golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2 h1:NWy5+hlRbC7HK+PmcXVUmW1IMyFce7to56IUvhUFm7Y=
416 | golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
417 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
418 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
419 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
420 | golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
421 | golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
422 | golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
423 | golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
424 | golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
425 | golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
426 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
427 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
428 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
429 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
430 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
431 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
432 | golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
433 | golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
434 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
435 | golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
436 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
437 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
438 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
439 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
440 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
441 | golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
442 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
443 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
444 | golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
445 | golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
446 | golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
447 | golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
448 | golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
449 | golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
450 | golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
451 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
452 | golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
453 | golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
454 | golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
455 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
456 | golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
457 | golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
458 | golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
459 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
460 | golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
461 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
462 | golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
463 | golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
464 | golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
465 | golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
466 | golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
467 | golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
468 | golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
469 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
470 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
471 | golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
472 | golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
473 | golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
474 | golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
475 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
476 | golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
477 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
478 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
479 | golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
480 | golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
481 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s=
482 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
483 | golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
484 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
485 | golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
486 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
487 | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
488 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
489 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
490 | golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
491 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
492 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
493 | golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY=
494 | golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
495 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
496 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
497 | golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
498 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
499 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
500 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
501 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
502 | golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
503 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
504 | golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
505 | golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
506 | golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
507 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
508 | golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
509 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
510 | golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
511 | golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
512 | golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
513 | golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
514 | golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
515 | golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
516 | golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
517 | golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
518 | golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
519 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
520 | golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
521 | golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
522 | golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
523 | golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
524 | golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
525 | golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
526 | golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
527 | golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
528 | golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
529 | golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
530 | golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
531 | golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
532 | golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
533 | golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
534 | golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
535 | golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
536 | golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
537 | golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
538 | golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
539 | golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
540 | golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
541 | golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
542 | golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
543 | golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
544 | golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
545 | golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
546 | golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
547 | golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
548 | golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
549 | golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
550 | golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
551 | golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
552 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
553 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
554 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
555 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
556 | google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
557 | google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
558 | google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
559 | google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
560 | google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
561 | google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
562 | google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
563 | google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
564 | google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
565 | google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
566 | google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
567 | google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
568 | google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
569 | google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
570 | google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
571 | google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
572 | google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
573 | google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
574 | google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
575 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
576 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
577 | google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
578 | google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
579 | google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
580 | google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
581 | google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
582 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
583 | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
584 | google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
585 | google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
586 | google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
587 | google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
588 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
589 | google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
590 | google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
591 | google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
592 | google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
593 | google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
594 | google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
595 | google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
596 | google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
597 | google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
598 | google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
599 | google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
600 | google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
601 | google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
602 | google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
603 | google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
604 | google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
605 | google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
606 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
607 | google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
608 | google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
609 | google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
610 | google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
611 | google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
612 | google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
613 | google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
614 | google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
615 | google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
616 | google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
617 | google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
618 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
619 | google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
620 | google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
621 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
622 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
623 | google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
624 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
625 | google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
626 | google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
627 | google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
628 | google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
629 | google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
630 | google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
631 | google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
632 | google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
633 | google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
634 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
635 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
636 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
637 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
638 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
639 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
640 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
641 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
642 | google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
643 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
644 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
645 | google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
646 | google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
647 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
648 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
649 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
650 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
651 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
652 | gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
653 | gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
654 | gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
655 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
656 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
657 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
658 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
659 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
660 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
661 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
662 | gorm.io/driver/postgres v1.4.4 h1:zt1fxJ+C+ajparn0SteEnkoPg0BQ6wOWXEQ99bteAmw=
663 | gorm.io/driver/postgres v1.4.4/go.mod h1:whNfh5WhhHs96honoLjBAMwJGYEuA3m1hvgUbNXhPCw=
664 | gorm.io/gorm v1.23.7/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
665 | gorm.io/gorm v1.24.0 h1:j/CoiSm6xpRpmzbFJsQHYj+I8bGYWLXVHeYEyyKlF74=
666 | gorm.io/gorm v1.24.0/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=
667 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
668 | honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
669 | honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
670 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
671 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
672 | honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
673 | honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
674 | rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
675 | rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
676 | rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
677 |
--------------------------------------------------------------------------------
/internal/dtos/article_dto.go:
--------------------------------------------------------------------------------
1 | package dtos
2 |
3 | type CreateArticleRequestDTO struct {
4 | Title string `json:"title" binding:"required"`
5 | Description string `json:"description" binding:"required"`
6 | }
7 |
8 | type CreateArticleResponseDTO struct {
9 | ID int64 `json:"id"`
10 | Title string `json:"title"`
11 | Description string `json:"description"`
12 | }
13 |
14 | type UpdateArticleRequestDTO struct {
15 | Title string `json:"title"`
16 | Description string `json:"description"`
17 | }
18 |
19 | type UpdateArticleResponseDTO struct {
20 | ID int64 `json:"id"`
21 | Title string `json:"title"`
22 | Description string `json:"description"`
23 | }
24 |
--------------------------------------------------------------------------------
/internal/dtos/auth_dto.go:
--------------------------------------------------------------------------------
1 | package dtos
2 |
3 | type RegisterRequestDTO struct {
4 | Username string `json:"username" binding:"required"`
5 | Password string `json:"password" binding:"required"`
6 | }
7 |
8 | type RegisterResponseDTO struct {
9 | ID int64 `json:"id"`
10 | Username string `json:"username"`
11 | Token string `json:"token"`
12 | }
13 |
14 | type LoginRequestDTO struct {
15 | Username string `json:"username" binding:"required"`
16 | Password string `json:"password" binding:"required"`
17 | }
18 |
19 | type LoginResponseDTO struct {
20 | ID int64 `json:"id"`
21 | Username string `json:"username"`
22 | Token string `json:"token"`
23 | }
24 |
25 | type JwtData struct {
26 | ID int64 `json:"id"`
27 | Username string `json:"username"`
28 | Token string `json:"token"`
29 | }
30 |
--------------------------------------------------------------------------------
/internal/dtos/user_dto.go:
--------------------------------------------------------------------------------
1 | package dtos
2 |
3 | type ShowProfileResponseDTO struct {
4 | ID int64 `json:"id"`
5 | Username string `json:"username"`
6 | }
7 |
--------------------------------------------------------------------------------
/internal/errors/error.go:
--------------------------------------------------------------------------------
1 | package errors
2 |
3 | import "errors"
4 |
5 | // Create your custom error
6 | var (
7 | // ErrSampleCustomError example custom error
8 | ErrSampleCustomError = errors.New("example custom error")
9 |
10 | // ErrWrongUsernameOrPassword when user try to login with wrong username or password
11 | ErrWrongUsernameOrPassword = errors.New("wrong username or password")
12 |
13 | // ErrUsernameAlreadyExist when user register with registered username
14 | ErrUsernameAlreadyExist = errors.New("username is already exist")
15 | )
16 |
--------------------------------------------------------------------------------
/internal/errors/pgerrcode.go:
--------------------------------------------------------------------------------
1 | // pgerrcode contains constants for PostgreSQL error codes.
2 | // Source https://github.com/jackc/pgerrcode/blob/master/errcode.go
3 | package errors
4 |
5 | // Source: https://www.postgresql.org/docs/14/errcodes-appendix.html
6 | // See gen.rb for script that can convert the error code table to Go code.
7 |
8 | const (
9 |
10 | // Class 00 — Successful Completion
11 | SuccessfulCompletion = "00000"
12 |
13 | // Class 01 — Warning
14 | Warning = "01000"
15 | DynamicResultSetsReturned = "0100C"
16 | ImplicitZeroBitPadding = "01008"
17 | NullValueEliminatedInSetFunction = "01003"
18 | PrivilegeNotGranted = "01007"
19 | PrivilegeNotRevoked = "01006"
20 | StringDataRightTruncationWarning = "01004"
21 | DeprecatedFeature = "01P01"
22 |
23 | // Class 02 — No Data (this is also a warning class per the SQL standard)
24 | NoData = "02000"
25 | NoAdditionalDynamicResultSetsReturned = "02001"
26 |
27 | // Class 03 — SQL Statement Not Yet Complete
28 | SQLStatementNotYetComplete = "03000"
29 |
30 | // Class 08 — Connection Exception
31 | ConnectionException = "08000"
32 | ConnectionDoesNotExist = "08003"
33 | ConnectionFailure = "08006"
34 | SQLClientUnableToEstablishSQLConnection = "08001"
35 | SQLServerRejectedEstablishmentOfSQLConnection = "08004"
36 | TransactionResolutionUnknown = "08007"
37 | ProtocolViolation = "08P01"
38 |
39 | // Class 09 — Triggered Action Exception
40 | TriggeredActionException = "09000"
41 |
42 | // Class 0A — Feature Not Supported
43 | FeatureNotSupported = "0A000"
44 |
45 | // Class 0B — Invalid Transaction Initiation
46 | InvalidTransactionInitiation = "0B000"
47 |
48 | // Class 0F — Locator Exception
49 | LocatorException = "0F000"
50 | InvalidLocatorSpecification = "0F001"
51 |
52 | // Class 0L — Invalid Grantor
53 | InvalidGrantor = "0L000"
54 | InvalidGrantOperation = "0LP01"
55 |
56 | // Class 0P — Invalid Role Specification
57 | InvalidRoleSpecification = "0P000"
58 |
59 | // Class 0Z — Diagnostics Exception
60 | DiagnosticsException = "0Z000"
61 | StackedDiagnosticsAccessedWithoutActiveHandler = "0Z002"
62 |
63 | // Class 20 — Case Not Found
64 | CaseNotFound = "20000"
65 |
66 | // Class 21 — Cardinality Violation
67 | CardinalityViolation = "21000"
68 |
69 | // Class 22 — Data Exception
70 | DataException = "22000"
71 | ArraySubscriptError = "2202E"
72 | CharacterNotInRepertoire = "22021"
73 | DatetimeFieldOverflow = "22008"
74 | DivisionByZero = "22012"
75 | ErrorInAssignment = "22005"
76 | EscapeCharacterConflict = "2200B"
77 | IndicatorOverflow = "22022"
78 | IntervalFieldOverflow = "22015"
79 | InvalidArgumentForLogarithm = "2201E"
80 | InvalidArgumentForNtileFunction = "22014"
81 | InvalidArgumentForNthValueFunction = "22016"
82 | InvalidArgumentForPowerFunction = "2201F"
83 | InvalidArgumentForWidthBucketFunction = "2201G"
84 | InvalidCharacterValueForCast = "22018"
85 | InvalidDatetimeFormat = "22007"
86 | InvalidEscapeCharacter = "22019"
87 | InvalidEscapeOctet = "2200D"
88 | InvalidEscapeSequence = "22025"
89 | NonstandardUseOfEscapeCharacter = "22P06"
90 | InvalidIndicatorParameterValue = "22010"
91 | InvalidParameterValue = "22023"
92 | InvalidPrecedingOrFollowingSize = "22013"
93 | InvalidRegularExpression = "2201B"
94 | InvalidRowCountInLimitClause = "2201W"
95 | InvalidRowCountInResultOffsetClause = "2201X"
96 | InvalidTablesampleArgument = "2202H"
97 | InvalidTablesampleRepeat = "2202G"
98 | InvalidTimeZoneDisplacementValue = "22009"
99 | InvalidUseOfEscapeCharacter = "2200C"
100 | MostSpecificTypeMismatch = "2200G"
101 | NullValueNotAllowedDataException = "22004"
102 | NullValueNoIndicatorParameter = "22002"
103 | NumericValueOutOfRange = "22003"
104 | SequenceGeneratorLimitExceeded = "2200H"
105 | StringDataLengthMismatch = "22026"
106 | StringDataRightTruncationDataException = "22001"
107 | SubstringError = "22011"
108 | TrimError = "22027"
109 | UnterminatedCString = "22024"
110 | ZeroLengthCharacterString = "2200F"
111 | FloatingPointException = "22P01"
112 | InvalidTextRepresentation = "22P02"
113 | InvalidBinaryRepresentation = "22P03"
114 | BadCopyFileFormat = "22P04"
115 | UntranslatableCharacter = "22P05"
116 | NotAnXMLDocument = "2200L"
117 | InvalidXMLDocument = "2200M"
118 | InvalidXMLContent = "2200N"
119 | InvalidXMLComment = "2200S"
120 | InvalidXMLProcessingInstruction = "2200T"
121 | DuplicateJSONObjectKeyValue = "22030"
122 | InvalidArgumentForSQLJSONDatetimeFunction = "22031"
123 | InvalidJSONText = "22032"
124 | InvalidSQLJSONSubscript = "22033"
125 | MoreThanOneSQLJSONItem = "22034"
126 | NoSQLJSONItem = "22035"
127 | NonNumericSQLJSONItem = "22036"
128 | NonUniqueKeysInAJSONObject = "22037"
129 | SingletonSQLJSONItemRequired = "22038"
130 | SQLJSONArrayNotFound = "22039"
131 | SQLJSONMemberNotFound = "2203A"
132 | SQLJSONNumberNotFound = "2203B"
133 | SQLJSONObjectNotFound = "2203C"
134 | TooManyJSONArrayElements = "2203D"
135 | TooManyJSONObjectMembers = "2203E"
136 | SQLJSONScalarRequired = "2203F"
137 |
138 | // Class 23 — Integrity Constraint Violation
139 | IntegrityConstraintViolation = "23000"
140 | RestrictViolation = "23001"
141 | NotNullViolation = "23502"
142 | ForeignKeyViolation = "23503"
143 | UniqueViolation = "23505"
144 | CheckViolation = "23514"
145 | ExclusionViolation = "23P01"
146 |
147 | // Class 24 — Invalid Cursor State
148 | InvalidCursorState = "24000"
149 |
150 | // Class 25 — Invalid Transaction State
151 | InvalidTransactionState = "25000"
152 | ActiveSQLTransaction = "25001"
153 | BranchTransactionAlreadyActive = "25002"
154 | HeldCursorRequiresSameIsolationLevel = "25008"
155 | InappropriateAccessModeForBranchTransaction = "25003"
156 | InappropriateIsolationLevelForBranchTransaction = "25004"
157 | NoActiveSQLTransactionForBranchTransaction = "25005"
158 | ReadOnlySQLTransaction = "25006"
159 | SchemaAndDataStatementMixingNotSupported = "25007"
160 | NoActiveSQLTransaction = "25P01"
161 | InFailedSQLTransaction = "25P02"
162 | IdleInTransactionSessionTimeout = "25P03"
163 |
164 | // Class 26 — Invalid SQL Statement Name
165 | InvalidSQLStatementName = "26000"
166 |
167 | // Class 27 — Triggered Data Change Violation
168 | TriggeredDataChangeViolation = "27000"
169 |
170 | // Class 28 — Invalid Authorization Specification
171 | InvalidAuthorizationSpecification = "28000"
172 | InvalidPassword = "28P01"
173 |
174 | // Class 2B — Dependent Privilege Descriptors Still Exist
175 | DependentPrivilegeDescriptorsStillExist = "2B000"
176 | DependentObjectsStillExist = "2BP01"
177 |
178 | // Class 2D — Invalid Transaction Termination
179 | InvalidTransactionTermination = "2D000"
180 |
181 | // Class 2F — SQL Routine Exception
182 | SQLRoutineException = "2F000"
183 | FunctionExecutedNoReturnStatement = "2F005"
184 | ModifyingSQLDataNotPermittedSQLRoutineException = "2F002"
185 | ProhibitedSQLStatementAttemptedSQLRoutineException = "2F003"
186 | ReadingSQLDataNotPermittedSQLRoutineException = "2F004"
187 |
188 | // Class 34 — Invalid Cursor Name
189 | InvalidCursorName = "34000"
190 |
191 | // Class 38 — External Routine Exception
192 | ExternalRoutineException = "38000"
193 | ContainingSQLNotPermitted = "38001"
194 | ModifyingSQLDataNotPermittedExternalRoutineException = "38002"
195 | ProhibitedSQLStatementAttemptedExternalRoutineException = "38003"
196 | ReadingSQLDataNotPermittedExternalRoutineException = "38004"
197 |
198 | // Class 39 — External Routine Invocation Exception
199 | ExternalRoutineInvocationException = "39000"
200 | InvalidSQLstateReturned = "39001"
201 | NullValueNotAllowedExternalRoutineInvocationException = "39004"
202 | TriggerProtocolViolated = "39P01"
203 | SRFProtocolViolated = "39P02"
204 | EventTriggerProtocolViolated = "39P03"
205 |
206 | // Class 3B — Savepoint Exception
207 | SavepointException = "3B000"
208 | InvalidSavepointSpecification = "3B001"
209 |
210 | // Class 3D — Invalid Catalog Name
211 | InvalidCatalogName = "3D000"
212 |
213 | // Class 3F — Invalid Schema Name
214 | InvalidSchemaName = "3F000"
215 |
216 | // Class 40 — Transaction Rollback
217 | TransactionRollback = "40000"
218 | TransactionIntegrityConstraintViolation = "40002"
219 | SerializationFailure = "40001"
220 | StatementCompletionUnknown = "40003"
221 | DeadlockDetected = "40P01"
222 |
223 | // Class 42 — Syntax Error or Access Rule Violation
224 | SyntaxErrorOrAccessRuleViolation = "42000"
225 | SyntaxError = "42601"
226 | InsufficientPrivilege = "42501"
227 | CannotCoerce = "42846"
228 | GroupingError = "42803"
229 | WindowingError = "42P20"
230 | InvalidRecursion = "42P19"
231 | InvalidForeignKey = "42830"
232 | InvalidName = "42602"
233 | NameTooLong = "42622"
234 | ReservedName = "42939"
235 | DatatypeMismatch = "42804"
236 | IndeterminateDatatype = "42P18"
237 | CollationMismatch = "42P21"
238 | IndeterminateCollation = "42P22"
239 | WrongObjectType = "42809"
240 | GeneratedAlways = "428C9"
241 | UndefinedColumn = "42703"
242 | UndefinedFunction = "42883"
243 | UndefinedTable = "42P01"
244 | UndefinedParameter = "42P02"
245 | UndefinedObject = "42704"
246 | DuplicateColumn = "42701"
247 | DuplicateCursor = "42P03"
248 | DuplicateDatabase = "42P04"
249 | DuplicateFunction = "42723"
250 | DuplicatePreparedStatement = "42P05"
251 | DuplicateSchema = "42P06"
252 | DuplicateTable = "42P07"
253 | DuplicateAlias = "42712"
254 | DuplicateObject = "42710"
255 | AmbiguousColumn = "42702"
256 | AmbiguousFunction = "42725"
257 | AmbiguousParameter = "42P08"
258 | AmbiguousAlias = "42P09"
259 | InvalidColumnReference = "42P10"
260 | InvalidColumnDefinition = "42611"
261 | InvalidCursorDefinition = "42P11"
262 | InvalidDatabaseDefinition = "42P12"
263 | InvalidFunctionDefinition = "42P13"
264 | InvalidPreparedStatementDefinition = "42P14"
265 | InvalidSchemaDefinition = "42P15"
266 | InvalidTableDefinition = "42P16"
267 | InvalidObjectDefinition = "42P17"
268 |
269 | // Class 44 — WITH CHECK OPTION Violation
270 | WithCheckOptionViolation = "44000"
271 |
272 | // Class 53 — Insufficient Resources
273 | InsufficientResources = "53000"
274 | DiskFull = "53100"
275 | OutOfMemory = "53200"
276 | TooManyConnections = "53300"
277 | ConfigurationLimitExceeded = "53400"
278 |
279 | // Class 54 — Program Limit Exceeded
280 | ProgramLimitExceeded = "54000"
281 | StatementTooComplex = "54001"
282 | TooManyColumns = "54011"
283 | TooManyArguments = "54023"
284 |
285 | // Class 55 — Object Not In Prerequisite State
286 | ObjectNotInPrerequisiteState = "55000"
287 | ObjectInUse = "55006"
288 | CantChangeRuntimeParam = "55P02"
289 | LockNotAvailable = "55P03"
290 | UnsafeNewEnumValueUsage = "55P04"
291 |
292 | // Class 57 — Operator Intervention
293 | OperatorIntervention = "57000"
294 | QueryCanceled = "57014"
295 | AdminShutdown = "57P01"
296 | CrashShutdown = "57P02"
297 | CannotConnectNow = "57P03"
298 | DatabaseDropped = "57P04"
299 | IdleSessionTimeout = "57P05"
300 |
301 | // Class 58 — System Error (errors external to PostgreSQL itself)
302 | SystemError = "58000"
303 | IOError = "58030"
304 | UndefinedFile = "58P01"
305 | DuplicateFile = "58P02"
306 |
307 | // Class 72 — Snapshot Failure
308 | SnapshotTooOld = "72000"
309 |
310 | // Class F0 — Configuration File Error
311 | ConfigFileError = "F0000"
312 | LockFileExists = "F0001"
313 |
314 | // Class HV — Foreign Data Wrapper Error (SQL/MED)
315 | FDWError = "HV000"
316 | FDWColumnNameNotFound = "HV005"
317 | FDWDynamicParameterValueNeeded = "HV002"
318 | FDWFunctionSequenceError = "HV010"
319 | FDWInconsistentDescriptorInformation = "HV021"
320 | FDWInvalidAttributeValue = "HV024"
321 | FDWInvalidColumnName = "HV007"
322 | FDWInvalidColumnNumber = "HV008"
323 | FDWInvalidDataType = "HV004"
324 | FDWInvalidDataTypeDescriptors = "HV006"
325 | FDWInvalidDescriptorFieldIdentifier = "HV091"
326 | FDWInvalidHandle = "HV00B"
327 | FDWInvalidOptionIndex = "HV00C"
328 | FDWInvalidOptionName = "HV00D"
329 | FDWInvalidStringLengthOrBufferLength = "HV090"
330 | FDWInvalidStringFormat = "HV00A"
331 | FDWInvalidUseOfNullPointer = "HV009"
332 | FDWTooManyHandles = "HV014"
333 | FDWOutOfMemory = "HV001"
334 | FDWNoSchemas = "HV00P"
335 | FDWOptionNameNotFound = "HV00J"
336 | FDWReplyHandle = "HV00K"
337 | FDWSchemaNotFound = "HV00Q"
338 | FDWTableNotFound = "HV00R"
339 | FDWUnableToCreateExecution = "HV00L"
340 | FDWUnableToCreateReply = "HV00M"
341 | FDWUnableToEstablishConnection = "HV00N"
342 |
343 | // Class P0 — PL/pgSQL Error
344 | PLpgSQLError = "P0000"
345 | RaiseException = "P0001"
346 | NoDataFound = "P0002"
347 | TooManyRows = "P0003"
348 | AssertFailure = "P0004"
349 |
350 | // Class XX — Internal Error
351 | InternalError = "XX000"
352 | DataCorrupted = "XX001"
353 | IndexCorrupted = "XX002"
354 | )
355 |
356 | // IsSuccessfulCompletion asserts the error code class is Class 00 — Successful Completion
357 | func IsSuccessfulCompletion(code string) bool {
358 | switch code {
359 | case SuccessfulCompletion:
360 | return true
361 | }
362 | return false
363 | }
364 |
365 | // IsWarning asserts the error code class is Class 01 — Warning
366 | func IsWarning(code string) bool {
367 | switch code {
368 | case Warning, DynamicResultSetsReturned, ImplicitZeroBitPadding, NullValueEliminatedInSetFunction, PrivilegeNotGranted, PrivilegeNotRevoked, StringDataRightTruncationWarning, DeprecatedFeature:
369 | return true
370 | }
371 | return false
372 | }
373 |
374 | // IsNoData asserts the error code class is Class 02 — No Data (this is also a warning class per the SQL standard)
375 | func IsNoData(code string) bool {
376 | switch code {
377 | case NoData, NoAdditionalDynamicResultSetsReturned:
378 | return true
379 | }
380 | return false
381 | }
382 |
383 | // IsSQLStatementNotYetComplete asserts the error code class is Class 03 — SQL Statement Not Yet Complete
384 | func IsSQLStatementNotYetComplete(code string) bool {
385 | switch code {
386 | case SQLStatementNotYetComplete:
387 | return true
388 | }
389 | return false
390 | }
391 |
392 | // IsConnectionException asserts the error code class is Class 08 — Connection Exception
393 | func IsConnectionException(code string) bool {
394 | switch code {
395 | case ConnectionException, ConnectionDoesNotExist, ConnectionFailure, SQLClientUnableToEstablishSQLConnection, SQLServerRejectedEstablishmentOfSQLConnection, TransactionResolutionUnknown, ProtocolViolation:
396 | return true
397 | }
398 | return false
399 | }
400 |
401 | // IsTriggeredActionException asserts the error code class is Class 09 — Triggered Action Exception
402 | func IsTriggeredActionException(code string) bool {
403 | switch code {
404 | case TriggeredActionException:
405 | return true
406 | }
407 | return false
408 | }
409 |
410 | // IsFeatureNotSupported asserts the error code class is Class 0A — Feature Not Supported
411 | func IsFeatureNotSupported(code string) bool {
412 | switch code {
413 | case FeatureNotSupported:
414 | return true
415 | }
416 | return false
417 | }
418 |
419 | // IsInvalidTransactionInitiation asserts the error code class is Class 0B — Invalid Transaction Initiation
420 | func IsInvalidTransactionInitiation(code string) bool {
421 | switch code {
422 | case InvalidTransactionInitiation:
423 | return true
424 | }
425 | return false
426 | }
427 |
428 | // IsLocatorException asserts the error code class is Class 0F — Locator Exception
429 | func IsLocatorException(code string) bool {
430 | switch code {
431 | case LocatorException, InvalidLocatorSpecification:
432 | return true
433 | }
434 | return false
435 | }
436 |
437 | // IsInvalidGrantor asserts the error code class is Class 0L — Invalid Grantor
438 | func IsInvalidGrantor(code string) bool {
439 | switch code {
440 | case InvalidGrantor, InvalidGrantOperation:
441 | return true
442 | }
443 | return false
444 | }
445 |
446 | // IsInvalidRoleSpecification asserts the error code class is Class 0P — Invalid Role Specification
447 | func IsInvalidRoleSpecification(code string) bool {
448 | switch code {
449 | case InvalidRoleSpecification:
450 | return true
451 | }
452 | return false
453 | }
454 |
455 | // IsDiagnosticsException asserts the error code class is Class 0Z — Diagnostics Exception
456 | func IsDiagnosticsException(code string) bool {
457 | switch code {
458 | case DiagnosticsException, StackedDiagnosticsAccessedWithoutActiveHandler:
459 | return true
460 | }
461 | return false
462 | }
463 |
464 | // IsCaseNotFound asserts the error code class is Class 20 — Case Not Found
465 | func IsCaseNotFound(code string) bool {
466 | switch code {
467 | case CaseNotFound:
468 | return true
469 | }
470 | return false
471 | }
472 |
473 | // IsCardinalityViolation asserts the error code class is Class 21 — Cardinality Violation
474 | func IsCardinalityViolation(code string) bool {
475 | switch code {
476 | case CardinalityViolation:
477 | return true
478 | }
479 | return false
480 | }
481 |
482 | // IsDataException asserts the error code class is Class 22 — Data Exception
483 | func IsDataException(code string) bool {
484 | switch code {
485 | case DataException, ArraySubscriptError, CharacterNotInRepertoire, DatetimeFieldOverflow, DivisionByZero, ErrorInAssignment, EscapeCharacterConflict, IndicatorOverflow, IntervalFieldOverflow, InvalidArgumentForLogarithm, InvalidArgumentForNtileFunction, InvalidArgumentForNthValueFunction, InvalidArgumentForPowerFunction, InvalidArgumentForWidthBucketFunction, InvalidCharacterValueForCast, InvalidDatetimeFormat, InvalidEscapeCharacter, InvalidEscapeOctet, InvalidEscapeSequence, NonstandardUseOfEscapeCharacter, InvalidIndicatorParameterValue, InvalidParameterValue, InvalidPrecedingOrFollowingSize, InvalidRegularExpression, InvalidRowCountInLimitClause, InvalidRowCountInResultOffsetClause, InvalidTablesampleArgument, InvalidTablesampleRepeat, InvalidTimeZoneDisplacementValue, InvalidUseOfEscapeCharacter, MostSpecificTypeMismatch, NullValueNotAllowedDataException, NullValueNoIndicatorParameter, NumericValueOutOfRange, SequenceGeneratorLimitExceeded, StringDataLengthMismatch, StringDataRightTruncationDataException, SubstringError, TrimError, UnterminatedCString, ZeroLengthCharacterString, FloatingPointException, InvalidTextRepresentation, InvalidBinaryRepresentation, BadCopyFileFormat, UntranslatableCharacter, NotAnXMLDocument, InvalidXMLDocument, InvalidXMLContent, InvalidXMLComment, InvalidXMLProcessingInstruction, DuplicateJSONObjectKeyValue, InvalidArgumentForSQLJSONDatetimeFunction, InvalidJSONText, InvalidSQLJSONSubscript, MoreThanOneSQLJSONItem, NoSQLJSONItem, NonNumericSQLJSONItem, NonUniqueKeysInAJSONObject, SingletonSQLJSONItemRequired, SQLJSONArrayNotFound, SQLJSONMemberNotFound, SQLJSONNumberNotFound, SQLJSONObjectNotFound, TooManyJSONArrayElements, TooManyJSONObjectMembers, SQLJSONScalarRequired:
486 | return true
487 | }
488 | return false
489 | }
490 |
491 | // IsIntegrityConstraintViolation asserts the error code class is Class 23 — Integrity Constraint Violation
492 | func IsIntegrityConstraintViolation(code string) bool {
493 | switch code {
494 | case IntegrityConstraintViolation, RestrictViolation, NotNullViolation, ForeignKeyViolation, UniqueViolation, CheckViolation, ExclusionViolation:
495 | return true
496 | }
497 | return false
498 | }
499 |
500 | // IsInvalidCursorState asserts the error code class is Class 24 — Invalid Cursor State
501 | func IsInvalidCursorState(code string) bool {
502 | switch code {
503 | case InvalidCursorState:
504 | return true
505 | }
506 | return false
507 | }
508 |
509 | // IsInvalidTransactionState asserts the error code class is Class 25 — Invalid Transaction State
510 | func IsInvalidTransactionState(code string) bool {
511 | switch code {
512 | case InvalidTransactionState, ActiveSQLTransaction, BranchTransactionAlreadyActive, HeldCursorRequiresSameIsolationLevel, InappropriateAccessModeForBranchTransaction, InappropriateIsolationLevelForBranchTransaction, NoActiveSQLTransactionForBranchTransaction, ReadOnlySQLTransaction, SchemaAndDataStatementMixingNotSupported, NoActiveSQLTransaction, InFailedSQLTransaction, IdleInTransactionSessionTimeout:
513 | return true
514 | }
515 | return false
516 | }
517 |
518 | // IsInvalidSQLStatementName asserts the error code class is Class 26 — Invalid SQL Statement Name
519 | func IsInvalidSQLStatementName(code string) bool {
520 | switch code {
521 | case InvalidSQLStatementName:
522 | return true
523 | }
524 | return false
525 | }
526 |
527 | // IsTriggeredDataChangeViolation asserts the error code class is Class 27 — Triggered Data Change Violation
528 | func IsTriggeredDataChangeViolation(code string) bool {
529 | switch code {
530 | case TriggeredDataChangeViolation:
531 | return true
532 | }
533 | return false
534 | }
535 |
536 | // IsInvalidAuthorizationSpecification asserts the error code class is Class 28 — Invalid Authorization Specification
537 | func IsInvalidAuthorizationSpecification(code string) bool {
538 | switch code {
539 | case InvalidAuthorizationSpecification, InvalidPassword:
540 | return true
541 | }
542 | return false
543 | }
544 |
545 | // IsDependentPrivilegeDescriptorsStillExist asserts the error code class is Class 2B — Dependent Privilege Descriptors Still Exist
546 | func IsDependentPrivilegeDescriptorsStillExist(code string) bool {
547 | switch code {
548 | case DependentPrivilegeDescriptorsStillExist, DependentObjectsStillExist:
549 | return true
550 | }
551 | return false
552 | }
553 |
554 | // IsInvalidTransactionTermination asserts the error code class is Class 2D — Invalid Transaction Termination
555 | func IsInvalidTransactionTermination(code string) bool {
556 | switch code {
557 | case InvalidTransactionTermination:
558 | return true
559 | }
560 | return false
561 | }
562 |
563 | // IsSQLRoutineException asserts the error code class is Class 2F — SQL Routine Exception
564 | func IsSQLRoutineException(code string) bool {
565 | switch code {
566 | case SQLRoutineException, FunctionExecutedNoReturnStatement, ModifyingSQLDataNotPermittedSQLRoutineException, ProhibitedSQLStatementAttemptedSQLRoutineException, ReadingSQLDataNotPermittedSQLRoutineException:
567 | return true
568 | }
569 | return false
570 | }
571 |
572 | // IsInvalidCursorName asserts the error code class is Class 34 — Invalid Cursor Name
573 | func IsInvalidCursorName(code string) bool {
574 | switch code {
575 | case InvalidCursorName:
576 | return true
577 | }
578 | return false
579 | }
580 |
581 | // IsExternalRoutineException asserts the error code class is Class 38 — External Routine Exception
582 | func IsExternalRoutineException(code string) bool {
583 | switch code {
584 | case ExternalRoutineException, ContainingSQLNotPermitted, ModifyingSQLDataNotPermittedExternalRoutineException, ProhibitedSQLStatementAttemptedExternalRoutineException, ReadingSQLDataNotPermittedExternalRoutineException:
585 | return true
586 | }
587 | return false
588 | }
589 |
590 | // IsExternalRoutineInvocationException asserts the error code class is Class 39 — External Routine Invocation Exception
591 | func IsExternalRoutineInvocationException(code string) bool {
592 | switch code {
593 | case ExternalRoutineInvocationException, InvalidSQLstateReturned, NullValueNotAllowedExternalRoutineInvocationException, TriggerProtocolViolated, SRFProtocolViolated, EventTriggerProtocolViolated:
594 | return true
595 | }
596 | return false
597 | }
598 |
599 | // IsSavepointException asserts the error code class is Class 3B — Savepoint Exception
600 | func IsSavepointException(code string) bool {
601 | switch code {
602 | case SavepointException, InvalidSavepointSpecification:
603 | return true
604 | }
605 | return false
606 | }
607 |
608 | // IsInvalidCatalogName asserts the error code class is Class 3D — Invalid Catalog Name
609 | func IsInvalidCatalogName(code string) bool {
610 | switch code {
611 | case InvalidCatalogName:
612 | return true
613 | }
614 | return false
615 | }
616 |
617 | // IsInvalidSchemaName asserts the error code class is Class 3F — Invalid Schema Name
618 | func IsInvalidSchemaName(code string) bool {
619 | switch code {
620 | case InvalidSchemaName:
621 | return true
622 | }
623 | return false
624 | }
625 |
626 | // IsTransactionRollback asserts the error code class is Class 40 — Transaction Rollback
627 | func IsTransactionRollback(code string) bool {
628 | switch code {
629 | case TransactionRollback, TransactionIntegrityConstraintViolation, SerializationFailure, StatementCompletionUnknown, DeadlockDetected:
630 | return true
631 | }
632 | return false
633 | }
634 |
635 | // IsSyntaxErrororAccessRuleViolation asserts the error code class is Class 42 — Syntax Error or Access Rule Violation
636 | func IsSyntaxErrororAccessRuleViolation(code string) bool {
637 | switch code {
638 | case SyntaxErrorOrAccessRuleViolation, SyntaxError, InsufficientPrivilege, CannotCoerce, GroupingError, WindowingError, InvalidRecursion, InvalidForeignKey, InvalidName, NameTooLong, ReservedName, DatatypeMismatch, IndeterminateDatatype, CollationMismatch, IndeterminateCollation, WrongObjectType, GeneratedAlways, UndefinedColumn, UndefinedFunction, UndefinedTable, UndefinedParameter, UndefinedObject, DuplicateColumn, DuplicateCursor, DuplicateDatabase, DuplicateFunction, DuplicatePreparedStatement, DuplicateSchema, DuplicateTable, DuplicateAlias, DuplicateObject, AmbiguousColumn, AmbiguousFunction, AmbiguousParameter, AmbiguousAlias, InvalidColumnReference, InvalidColumnDefinition, InvalidCursorDefinition, InvalidDatabaseDefinition, InvalidFunctionDefinition, InvalidPreparedStatementDefinition, InvalidSchemaDefinition, InvalidTableDefinition, InvalidObjectDefinition:
639 | return true
640 | }
641 | return false
642 | }
643 |
644 | // IsWithCheckOptionViolation asserts the error code class is Class 44 — WITH CHECK OPTION Violation
645 | func IsWithCheckOptionViolation(code string) bool {
646 | switch code {
647 | case WithCheckOptionViolation:
648 | return true
649 | }
650 | return false
651 | }
652 |
653 | // IsInsufficientResources asserts the error code class is Class 53 — Insufficient Resources
654 | func IsInsufficientResources(code string) bool {
655 | switch code {
656 | case InsufficientResources, DiskFull, OutOfMemory, TooManyConnections, ConfigurationLimitExceeded:
657 | return true
658 | }
659 | return false
660 | }
661 |
662 | // IsProgramLimitExceeded asserts the error code class is Class 54 — Program Limit Exceeded
663 | func IsProgramLimitExceeded(code string) bool {
664 | switch code {
665 | case ProgramLimitExceeded, StatementTooComplex, TooManyColumns, TooManyArguments:
666 | return true
667 | }
668 | return false
669 | }
670 |
671 | // IsObjectNotInPrerequisiteState asserts the error code class is Class 55 — Object Not In Prerequisite State
672 | func IsObjectNotInPrerequisiteState(code string) bool {
673 | switch code {
674 | case ObjectNotInPrerequisiteState, ObjectInUse, CantChangeRuntimeParam, LockNotAvailable, UnsafeNewEnumValueUsage:
675 | return true
676 | }
677 | return false
678 | }
679 |
680 | // IsOperatorIntervention asserts the error code class is Class 57 — Operator Intervention
681 | func IsOperatorIntervention(code string) bool {
682 | switch code {
683 | case OperatorIntervention, QueryCanceled, AdminShutdown, CrashShutdown, CannotConnectNow, DatabaseDropped, IdleSessionTimeout:
684 | return true
685 | }
686 | return false
687 | }
688 |
689 | // IsSystemError asserts the error code class is Class 58 — System Error (errors external to PostgreSQL itself)
690 | func IsSystemError(code string) bool {
691 | switch code {
692 | case SystemError, IOError, UndefinedFile, DuplicateFile:
693 | return true
694 | }
695 | return false
696 | }
697 |
698 | // IsSnapshotFailure asserts the error code class is Class 72 — Snapshot Failure
699 | func IsSnapshotFailure(code string) bool {
700 | switch code {
701 | case SnapshotTooOld:
702 | return true
703 | }
704 | return false
705 | }
706 |
707 | // IsConfigurationFileError asserts the error code class is Class F0 — Configuration File Error
708 | func IsConfigurationFileError(code string) bool {
709 | switch code {
710 | case ConfigFileError, LockFileExists:
711 | return true
712 | }
713 | return false
714 | }
715 |
716 | // IsForeignDataWrapperError asserts the error code class is Class HV — Foreign Data Wrapper Error (SQL/MED)
717 | func IsForeignDataWrapperError(code string) bool {
718 | switch code {
719 | case FDWError, FDWColumnNameNotFound, FDWDynamicParameterValueNeeded, FDWFunctionSequenceError, FDWInconsistentDescriptorInformation, FDWInvalidAttributeValue, FDWInvalidColumnName, FDWInvalidColumnNumber, FDWInvalidDataType, FDWInvalidDataTypeDescriptors, FDWInvalidDescriptorFieldIdentifier, FDWInvalidHandle, FDWInvalidOptionIndex, FDWInvalidOptionName, FDWInvalidStringLengthOrBufferLength, FDWInvalidStringFormat, FDWInvalidUseOfNullPointer, FDWTooManyHandles, FDWOutOfMemory, FDWNoSchemas, FDWOptionNameNotFound, FDWReplyHandle, FDWSchemaNotFound, FDWTableNotFound, FDWUnableToCreateExecution, FDWUnableToCreateReply, FDWUnableToEstablishConnection:
720 | return true
721 | }
722 | return false
723 | }
724 |
725 | // IsPLpgSQLError asserts the error code class is Class P0 — PL/pgSQL Error
726 | func IsPLpgSQLError(code string) bool {
727 | switch code {
728 | case PLpgSQLError, RaiseException, NoDataFound, TooManyRows, AssertFailure:
729 | return true
730 | }
731 | return false
732 | }
733 |
734 | // IsInternalError asserts the error code class is Class XX — Internal Error
735 | func IsInternalError(code string) bool {
736 | switch code {
737 | case InternalError, DataCorrupted, IndexCorrupted:
738 | return true
739 | }
740 | return false
741 | }
742 |
--------------------------------------------------------------------------------
/internal/handlers/article_handler.go:
--------------------------------------------------------------------------------
1 | package handlers
2 |
3 | import (
4 | "errors"
5 | "net/http"
6 | "strconv"
7 |
8 | "github.com/gin-gonic/gin"
9 | "github.com/jinzhu/copier"
10 | "gorm.io/gorm"
11 |
12 | "github.com/geshtng/go-base-backend/internal/dtos"
13 | "github.com/geshtng/go-base-backend/internal/helpers"
14 | "github.com/geshtng/go-base-backend/internal/models"
15 | )
16 |
17 | func (h *Handler) GetAllArticlesHandler(c *gin.Context) {
18 | result, err := h.articleService.GetAll()
19 | if err != nil {
20 | c.JSON(http.StatusInternalServerError, helpers.BuildErrorResponse(http.StatusInternalServerError, err.Error()))
21 | return
22 | }
23 |
24 | c.JSON(http.StatusOK, helpers.BuildSuccessResponse(http.StatusOK, http.StatusText(http.StatusOK), result))
25 | }
26 |
27 | func (h *Handler) GetArticleByIDHandler(c *gin.Context) {
28 | id, err := strconv.ParseInt(c.Param("id"), 10, 64)
29 | if err != nil {
30 | c.JSON(http.StatusBadRequest, helpers.BuildErrorResponse(http.StatusBadRequest, err.Error()))
31 | return
32 | }
33 |
34 | result, err := h.articleService.GetByID(id)
35 | if err != nil {
36 | if errors.Is(err, gorm.ErrRecordNotFound) {
37 | c.JSON(http.StatusNotFound, helpers.BuildErrorResponse(http.StatusNotFound, err.Error()))
38 | return
39 | }
40 |
41 | c.JSON(http.StatusInternalServerError, helpers.BuildErrorResponse(http.StatusInternalServerError, err.Error()))
42 | return
43 | }
44 |
45 | c.JSON(http.StatusOK, helpers.BuildSuccessResponse(http.StatusOK, http.StatusText(http.StatusOK), result))
46 | }
47 |
48 | func (h *Handler) CreateArticleHandler(c *gin.Context) {
49 | var request dtos.CreateArticleRequestDTO
50 | var response dtos.CreateArticleResponseDTO
51 |
52 | err := c.ShouldBindJSON(&request)
53 | if err != nil {
54 | c.JSON(http.StatusBadRequest, helpers.BuildErrorResponse(http.StatusBadRequest, err.Error()))
55 | return
56 | }
57 |
58 | article := models.Article{
59 | Title: request.Title,
60 | Description: request.Description,
61 | }
62 |
63 | newArticle, err := h.articleService.Create(article)
64 | if err != nil {
65 | c.JSON(http.StatusInternalServerError, helpers.BuildErrorResponse(http.StatusInternalServerError, err.Error()))
66 | return
67 | }
68 |
69 | err = copier.Copy(&response, &newArticle)
70 | if err != nil {
71 | c.JSON(http.StatusInternalServerError, helpers.BuildErrorResponse(http.StatusInternalServerError, err.Error()))
72 | return
73 | }
74 |
75 | c.JSON(http.StatusCreated, helpers.BuildSuccessResponse(http.StatusCreated, http.StatusText(http.StatusCreated), newArticle))
76 | }
77 |
78 | func (h *Handler) UpdateArticleHandler(c *gin.Context) {
79 | var request dtos.UpdateArticleRequestDTO
80 | var response dtos.UpdateArticleResponseDTO
81 |
82 | id, err := strconv.ParseInt(c.Param("id"), 10, 64)
83 | if err != nil {
84 | c.JSON(http.StatusBadRequest, helpers.BuildErrorResponse(http.StatusBadRequest, err.Error()))
85 | return
86 | }
87 |
88 | err = c.ShouldBindJSON(&request)
89 | if err != nil {
90 | c.JSON(http.StatusBadRequest, helpers.BuildErrorResponse(http.StatusBadRequest, err.Error()))
91 | return
92 | }
93 |
94 | article := models.Article{
95 | Title: request.Title,
96 | Description: request.Description,
97 | }
98 |
99 | updatedArticle, err := h.articleService.Update(id, article)
100 | if err != nil {
101 | if errors.Is(err, gorm.ErrRecordNotFound) {
102 | c.JSON(http.StatusNotFound, helpers.BuildErrorResponse(http.StatusNotFound, err.Error()))
103 | return
104 | }
105 |
106 | c.JSON(http.StatusInternalServerError, helpers.BuildErrorResponse(http.StatusInternalServerError, err.Error()))
107 | return
108 | }
109 |
110 | err = copier.Copy(&response, &updatedArticle)
111 | if err != nil {
112 | c.JSON(http.StatusInternalServerError, helpers.BuildErrorResponse(http.StatusInternalServerError, err.Error()))
113 | return
114 | }
115 |
116 | c.JSON(http.StatusOK, helpers.BuildSuccessResponse(http.StatusOK, http.StatusText(http.StatusOK), updatedArticle))
117 | }
118 |
119 | func (h *Handler) DeleteArticleHandler(c *gin.Context) {
120 | id, err := strconv.ParseInt(c.Param("id"), 10, 64)
121 | if err != nil {
122 | c.JSON(http.StatusBadRequest, helpers.BuildErrorResponse(http.StatusBadRequest, err.Error()))
123 | return
124 | }
125 |
126 | err = h.articleService.Delete(id)
127 | if err != nil {
128 | if errors.Is(err, gorm.ErrRecordNotFound) {
129 | c.JSON(http.StatusNotFound, helpers.BuildErrorResponse(http.StatusNotFound, err.Error()))
130 | return
131 | }
132 |
133 | c.JSON(http.StatusInternalServerError, helpers.BuildErrorResponse(http.StatusInternalServerError, err.Error()))
134 | return
135 | }
136 |
137 | c.JSON(http.StatusOK, helpers.BuildSuccessResponse(http.StatusOK, http.StatusText(http.StatusOK), id))
138 | }
139 |
--------------------------------------------------------------------------------
/internal/handlers/article_handler_test.go:
--------------------------------------------------------------------------------
1 | package handlers
2 |
3 | import (
4 | "encoding/json"
5 | "errors"
6 | "io"
7 | "net/http"
8 | "net/http/httptest"
9 | "testing"
10 |
11 | "github.com/gin-gonic/gin"
12 | "github.com/stretchr/testify/assert"
13 | "github.com/stretchr/testify/mock"
14 | "github.com/stretchr/testify/require"
15 | "gorm.io/gorm"
16 |
17 | "github.com/geshtng/go-base-backend/internal/dtos"
18 | "github.com/geshtng/go-base-backend/internal/helpers"
19 | smock "github.com/geshtng/go-base-backend/internal/mocks/services"
20 | "github.com/geshtng/go-base-backend/internal/models"
21 | )
22 |
23 | func TestHandler_GetAllArticlesHandler(t *testing.T) {
24 | tests := []struct {
25 | name string
26 | articleService *smock.ArticleService
27 | mock func(*smock.ArticleService)
28 | want helpers.Response
29 | }{
30 | {
31 | name: "Success",
32 | articleService: smock.NewArticleService(t),
33 | mock: func(ms *smock.ArticleService) {
34 | expectedArticles := []*models.Article{
35 | {
36 | ID: 1,
37 | Title: "title",
38 | Description: "description",
39 | },
40 | }
41 |
42 | ms.On("GetAll").Return(expectedArticles, nil)
43 | },
44 | want: helpers.Response{
45 | Data: []interface{}{
46 | map[string]interface{}{
47 | "ID": float64(1),
48 | "Title": "title",
49 | "Description": "description",
50 | },
51 | },
52 | Code: 200,
53 | Message: "OK",
54 | Error: false,
55 | },
56 | },
57 | {
58 | name: "Error Service.GetAll",
59 | articleService: smock.NewArticleService(t),
60 | mock: func(ms *smock.ArticleService) {
61 | ms.On("GetAll").Return(nil, errors.New("something error"))
62 | },
63 | want: helpers.Response{
64 | Code: 500,
65 | Message: "something error",
66 | Error: true,
67 | },
68 | },
69 | }
70 | for _, tt := range tests {
71 | t.Run(tt.name, func(t *testing.T) {
72 | h := &Handler{
73 | articleService: tt.articleService,
74 | }
75 |
76 | tt.mock(tt.articleService)
77 |
78 | r := gin.Default()
79 | endpoint := "/articles"
80 |
81 | r.GET(endpoint, h.GetAllArticlesHandler)
82 |
83 | req, _ := http.NewRequest(http.MethodGet, endpoint, nil)
84 |
85 | w := httptest.NewRecorder()
86 | r.ServeHTTP(w, req)
87 |
88 | var response helpers.Response
89 | err := json.Unmarshal(w.Body.Bytes(), &response)
90 | require.NoError(t, err)
91 |
92 | assert.Equal(t, tt.want.Code, w.Code)
93 | assert.Equal(t, tt.want, response)
94 | })
95 | }
96 | }
97 |
98 | func TestHandler_GetArticleByIDHandler(t *testing.T) {
99 | tests := []struct {
100 | name string
101 | articleService *smock.ArticleService
102 | mock func(*smock.ArticleService)
103 | want helpers.Response
104 | }{
105 | {
106 | name: "Success",
107 | articleService: smock.NewArticleService(t),
108 | mock: func(ms *smock.ArticleService) {
109 | expectedArticle := &models.Article{
110 | ID: 1,
111 | Title: "title",
112 | Description: "description",
113 | }
114 |
115 | ms.On("GetByID", mock.Anything).Return(expectedArticle, nil)
116 | },
117 | want: helpers.Response{
118 | Data: map[string]interface{}{
119 | "ID": float64(1),
120 | "Title": "title",
121 | "Description": "description",
122 | },
123 | Code: 200,
124 | Message: "OK",
125 | Error: false,
126 | },
127 | },
128 | {
129 | name: "Error Parse articleID",
130 | articleService: smock.NewArticleService(t),
131 | mock: func(ms *smock.ArticleService) {},
132 | want: helpers.Response{
133 | Code: 400,
134 | Message: "strconv.ParseInt: parsing \"xx\": invalid syntax",
135 | Error: true,
136 | },
137 | },
138 | {
139 | name: "Error Service.ID record not found",
140 | articleService: smock.NewArticleService(t),
141 | mock: func(ms *smock.ArticleService) {
142 | ms.On("GetByID", mock.Anything).Return(nil, gorm.ErrRecordNotFound)
143 | },
144 | want: helpers.Response{
145 | Code: 404,
146 | Message: "record not found",
147 | Error: true,
148 | },
149 | },
150 | {
151 | name: "Error Service.GetByID",
152 | articleService: smock.NewArticleService(t),
153 | mock: func(ms *smock.ArticleService) {
154 | ms.On("GetByID", mock.Anything).Return(nil, errors.New("something error"))
155 | },
156 | want: helpers.Response{
157 | Code: 500,
158 | Message: "something error",
159 | Error: true,
160 | },
161 | },
162 | }
163 | for _, tt := range tests {
164 | h := &Handler{
165 | articleService: tt.articleService,
166 | }
167 |
168 | tt.mock(tt.articleService)
169 |
170 | r := gin.Default()
171 | endpoint := "/articles"
172 |
173 | if tt.name == "Error Parse articleID" {
174 | r.GET(endpoint, MiddlewareMockID("xx"), h.GetArticleByIDHandler)
175 | } else {
176 | r.GET(endpoint, MiddlewareMockID("1"), h.GetArticleByIDHandler)
177 | }
178 |
179 | req, _ := http.NewRequest(http.MethodGet, endpoint, nil)
180 |
181 | w := httptest.NewRecorder()
182 | r.ServeHTTP(w, req)
183 |
184 | var response helpers.Response
185 | err := json.Unmarshal(w.Body.Bytes(), &response)
186 | require.NoError(t, err)
187 |
188 | assert.Equal(t, tt.want.Code, w.Code)
189 | assert.Equal(t, tt.want, response)
190 | }
191 | }
192 |
193 | func TestHandler_CreateArticleHandler(t *testing.T) {
194 | tests := []struct {
195 | name string
196 | body io.Reader
197 | articleService *smock.ArticleService
198 | mock func(*smock.ArticleService)
199 | want helpers.Response
200 | }{
201 | {
202 | name: "Success",
203 | body: MakeRequestBody(dtos.CreateArticleRequestDTO{
204 | Title: "title",
205 | Description: "description",
206 | }),
207 | articleService: smock.NewArticleService(t),
208 | mock: func(ms *smock.ArticleService) {
209 | expectedArticle := &models.Article{
210 | ID: 1,
211 | Title: "title",
212 | Description: "description",
213 | }
214 |
215 | ms.On("Create", mock.Anything).Return(expectedArticle, nil)
216 | },
217 | want: helpers.Response{
218 | Data: map[string]interface{}{
219 | "ID": float64(1),
220 | "Title": "title",
221 | "Description": "description",
222 | },
223 | Code: 201,
224 | Message: "Created",
225 | Error: false,
226 | },
227 | },
228 | {
229 | name: "Error ShouldBindJSON",
230 | body: MakeRequestBody(dtos.CreateArticleRequestDTO{
231 | Title: "title",
232 | }),
233 | articleService: smock.NewArticleService(t),
234 | mock: func(ms *smock.ArticleService) {},
235 | want: helpers.Response{
236 | Code: 400,
237 | Message: "Key: 'CreateArticleRequestDTO.Description' Error:Field validation for 'Description' failed on the 'required' tag",
238 | Error: true,
239 | },
240 | },
241 | {
242 | name: "Error Service.Create",
243 | body: MakeRequestBody(dtos.CreateArticleRequestDTO{
244 | Title: "title",
245 | Description: "description",
246 | }),
247 | articleService: smock.NewArticleService(t),
248 | mock: func(ms *smock.ArticleService) {
249 | ms.On("Create", mock.Anything).Return(nil, errors.New("something error"))
250 | },
251 | want: helpers.Response{
252 | Code: 500,
253 | Message: "something error",
254 | Error: true,
255 | },
256 | },
257 | {
258 | name: "Error copier",
259 | body: MakeRequestBody(dtos.CreateArticleRequestDTO{
260 | Title: "title",
261 | Description: "description",
262 | }),
263 | articleService: smock.NewArticleService(t),
264 | mock: func(ms *smock.ArticleService) {
265 | ms.On("Create", mock.Anything).Return(nil, nil)
266 | },
267 | want: helpers.Response{
268 | Code: 500,
269 | Message: "copy from is invalid",
270 | Error: true,
271 | },
272 | },
273 | }
274 | for _, tt := range tests {
275 | t.Run(tt.name, func(t *testing.T) {
276 | h := &Handler{
277 | articleService: tt.articleService,
278 | }
279 |
280 | tt.mock(tt.articleService)
281 |
282 | r := gin.Default()
283 | endpoint := "/articles"
284 |
285 | r.POST(endpoint, h.CreateArticleHandler)
286 |
287 | req, _ := http.NewRequest(http.MethodPost, endpoint, tt.body)
288 |
289 | w := httptest.NewRecorder()
290 | r.ServeHTTP(w, req)
291 |
292 | var response helpers.Response
293 | err := json.Unmarshal(w.Body.Bytes(), &response)
294 | require.NoError(t, err)
295 |
296 | assert.Equal(t, tt.want.Code, w.Code)
297 | assert.Equal(t, tt.want, response)
298 | })
299 | }
300 | }
301 |
302 | func TestHandler_UpdateArticleHandler(t *testing.T) {
303 | tests := []struct {
304 | name string
305 | body io.Reader
306 | articleService *smock.ArticleService
307 | mock func(*smock.ArticleService)
308 | want helpers.Response
309 | }{
310 | {
311 | name: "Success",
312 | body: MakeRequestBody(dtos.UpdateArticleRequestDTO{
313 | Title: "title update",
314 | Description: "description update",
315 | }),
316 | articleService: smock.NewArticleService(t),
317 | mock: func(ms *smock.ArticleService) {
318 | expectedArticle := &models.Article{
319 | ID: 1,
320 | Title: "title update",
321 | Description: "description update",
322 | }
323 |
324 | ms.On("Update", mock.Anything, mock.Anything).Return(expectedArticle, nil)
325 | },
326 | want: helpers.Response{
327 | Data: map[string]interface{}{
328 | "ID": float64(1),
329 | "Title": "title update",
330 | "Description": "description update",
331 | },
332 | Code: 200,
333 | Message: "OK",
334 | Error: false,
335 | },
336 | },
337 | {
338 | name: "Error Parse articleID",
339 | body: MakeRequestBody(dtos.UpdateArticleRequestDTO{
340 | Title: "title update",
341 | Description: "description update",
342 | }),
343 | articleService: smock.NewArticleService(t),
344 | mock: func(ms *smock.ArticleService) {},
345 | want: helpers.Response{
346 | Code: 400,
347 | Message: "strconv.ParseInt: parsing \"xx\": invalid syntax",
348 | Error: true,
349 | },
350 | },
351 | {
352 | name: "Error ShouldBindJSON",
353 | body: nil,
354 | articleService: smock.NewArticleService(t),
355 | mock: func(ms *smock.ArticleService) {},
356 | want: helpers.Response{
357 | Code: 400,
358 | Message: "invalid request",
359 | Error: true,
360 | },
361 | },
362 | {
363 | name: "Error Service.Update",
364 | body: MakeRequestBody(dtos.UpdateArticleRequestDTO{
365 | Title: "title update",
366 | Description: "description update",
367 | }),
368 | articleService: smock.NewArticleService(t),
369 | mock: func(ms *smock.ArticleService) {
370 | ms.On("Update", mock.Anything, mock.Anything).Return(nil, errors.New("something error"))
371 | },
372 | want: helpers.Response{
373 | Code: 500,
374 | Message: "something error",
375 | Error: true,
376 | },
377 | },
378 | {
379 | name: "Error Service.Update record not found",
380 | body: MakeRequestBody(dtos.UpdateArticleRequestDTO{
381 | Title: "title update",
382 | Description: "description update",
383 | }),
384 | articleService: smock.NewArticleService(t),
385 | mock: func(ms *smock.ArticleService) {
386 | ms.On("Update", mock.Anything, mock.Anything).Return(nil, gorm.ErrRecordNotFound)
387 | },
388 | want: helpers.Response{
389 | Code: 404,
390 | Message: "record not found",
391 | Error: true,
392 | },
393 | },
394 | {
395 | name: "Error copier",
396 | body: MakeRequestBody(dtos.UpdateArticleRequestDTO{
397 | Title: "title update",
398 | Description: "description update",
399 | }),
400 | articleService: smock.NewArticleService(t),
401 | mock: func(ms *smock.ArticleService) {
402 | ms.On("Update", mock.Anything, mock.Anything).Return(nil, nil)
403 | },
404 | want: helpers.Response{
405 | Code: 500,
406 | Message: "copy from is invalid",
407 | Error: true,
408 | },
409 | },
410 | }
411 | for _, tt := range tests {
412 | t.Run(tt.name, func(t *testing.T) {
413 | h := &Handler{
414 | articleService: tt.articleService,
415 | }
416 |
417 | tt.mock(tt.articleService)
418 |
419 | r := gin.Default()
420 | endpoint := "/articles"
421 |
422 | if tt.name == "Error Parse articleID" {
423 | r.PUT(endpoint, MiddlewareMockID("xx"), h.UpdateArticleHandler)
424 | } else {
425 | r.PUT(endpoint, MiddlewareMockID("1"), h.UpdateArticleHandler)
426 | }
427 |
428 | req, _ := http.NewRequest(http.MethodPut, endpoint, tt.body)
429 |
430 | w := httptest.NewRecorder()
431 | r.ServeHTTP(w, req)
432 |
433 | var response helpers.Response
434 | err := json.Unmarshal(w.Body.Bytes(), &response)
435 | require.NoError(t, err)
436 |
437 | assert.Equal(t, tt.want.Code, w.Code)
438 | assert.Equal(t, tt.want, response)
439 | })
440 | }
441 | }
442 |
443 | func TestHandler_DeleteArticleHandler(t *testing.T) {
444 | tests := []struct {
445 | name string
446 | articleService *smock.ArticleService
447 | mock func(*smock.ArticleService)
448 | want helpers.Response
449 | }{
450 | {
451 | name: "Success",
452 | articleService: smock.NewArticleService(t),
453 | mock: func(ms *smock.ArticleService) {
454 | ms.On("Delete", mock.Anything).Return(nil)
455 | },
456 | want: helpers.Response{
457 | Data: float64(1),
458 | Code: 200,
459 | Message: "OK",
460 | Error: false,
461 | },
462 | },
463 | {
464 | name: "Error Parse articleID",
465 | articleService: smock.NewArticleService(t),
466 | mock: func(ms *smock.ArticleService) {},
467 | want: helpers.Response{
468 | Code: 400,
469 | Message: "strconv.ParseInt: parsing \"xx\": invalid syntax",
470 | Error: true,
471 | },
472 | },
473 | {
474 | name: "Error Service.Delete",
475 | articleService: smock.NewArticleService(t),
476 | mock: func(ms *smock.ArticleService) {
477 | ms.On("Delete", mock.Anything).Return(errors.New("something error"))
478 | },
479 | want: helpers.Response{
480 | Code: 500,
481 | Message: "something error",
482 | Error: true,
483 | },
484 | },
485 | {
486 | name: "Error Service.Delete record not found",
487 | articleService: smock.NewArticleService(t),
488 | mock: func(ms *smock.ArticleService) {
489 | ms.On("Delete", mock.Anything).Return(gorm.ErrRecordNotFound)
490 | },
491 | want: helpers.Response{
492 | Code: 404,
493 | Message: "record not found",
494 | Error: true,
495 | },
496 | },
497 | }
498 | for _, tt := range tests {
499 | h := &Handler{
500 | articleService: tt.articleService,
501 | }
502 |
503 | tt.mock(tt.articleService)
504 |
505 | r := gin.Default()
506 | endpoint := "/articles"
507 |
508 | if tt.name == "Error Parse articleID" {
509 | r.DELETE(endpoint, MiddlewareMockID("xx"), h.DeleteArticleHandler)
510 | } else {
511 | r.DELETE(endpoint, MiddlewareMockID("1"), h.DeleteArticleHandler)
512 | }
513 |
514 | req, _ := http.NewRequest(http.MethodDelete, endpoint, nil)
515 |
516 | w := httptest.NewRecorder()
517 | r.ServeHTTP(w, req)
518 |
519 | var response helpers.Response
520 | err := json.Unmarshal(w.Body.Bytes(), &response)
521 | require.NoError(t, err)
522 |
523 | assert.Equal(t, tt.want.Code, w.Code)
524 | assert.Equal(t, tt.want, response)
525 | }
526 | }
527 |
--------------------------------------------------------------------------------
/internal/handlers/auth_handler.go:
--------------------------------------------------------------------------------
1 | package handlers
2 |
3 | import (
4 | "errors"
5 | "net/http"
6 |
7 | "github.com/gin-gonic/gin"
8 | "github.com/jackc/pgconn"
9 | "gorm.io/gorm"
10 |
11 | "github.com/geshtng/go-base-backend/internal/dtos"
12 | errn "github.com/geshtng/go-base-backend/internal/errors"
13 | "github.com/geshtng/go-base-backend/internal/helpers"
14 | "github.com/geshtng/go-base-backend/internal/models"
15 | )
16 |
17 | func (h *Handler) RegisterHandler(c *gin.Context) {
18 | var request dtos.RegisterRequestDTO
19 | var response dtos.RegisterResponseDTO
20 |
21 | err := c.ShouldBindJSON(&request)
22 | if err != nil {
23 | c.JSON(http.StatusBadRequest, helpers.BuildErrorResponse(http.StatusBadRequest, err.Error()))
24 | return
25 | }
26 |
27 | auth := models.User{
28 | Username: request.Username,
29 | Password: request.Password,
30 | }
31 |
32 | newAuth, token, err := h.authService.Register(auth)
33 | if err != nil {
34 | var pgErr *pgconn.PgError
35 |
36 | if errors.As(err, &pgErr); pgErr.Code == errn.UniqueViolation {
37 | c.JSON(http.StatusUnauthorized, helpers.BuildErrorResponse(http.StatusUnauthorized, errn.ErrUsernameAlreadyExist.Error()))
38 | return
39 | }
40 |
41 | c.JSON(http.StatusInternalServerError, helpers.BuildErrorResponse(http.StatusInternalServerError, err.Error()))
42 | return
43 | }
44 |
45 | response = dtos.RegisterResponseDTO{
46 | ID: newAuth.ID,
47 | Username: newAuth.Username,
48 | Token: *token,
49 | }
50 |
51 | c.JSON(http.StatusCreated, helpers.BuildSuccessResponse(http.StatusCreated, http.StatusText(http.StatusCreated), response))
52 | }
53 |
54 | func (h *Handler) LoginHandler(c *gin.Context) {
55 | var request dtos.LoginRequestDTO
56 | var response dtos.LoginResponseDTO
57 |
58 | err := c.ShouldBindJSON(&request)
59 | if err != nil {
60 | c.JSON(http.StatusBadRequest, helpers.BuildErrorResponse(http.StatusBadRequest, err.Error()))
61 | return
62 | }
63 |
64 | auth, token, err := h.authService.Login(request.Username, request.Password)
65 | if err != nil {
66 | if errors.Is(err, gorm.ErrRecordNotFound) {
67 | c.JSON(http.StatusUnauthorized, helpers.BuildErrorResponse(http.StatusUnauthorized, errn.ErrWrongUsernameOrPassword.Error()))
68 | return
69 | }
70 |
71 | c.JSON(http.StatusInternalServerError, helpers.BuildErrorResponse(http.StatusInternalServerError, err.Error()))
72 | return
73 | }
74 |
75 | response = dtos.LoginResponseDTO{
76 | ID: auth.ID,
77 | Username: auth.Username,
78 | Token: *token,
79 | }
80 |
81 | c.JSON(http.StatusOK, helpers.BuildSuccessResponse(http.StatusOK, http.StatusText(http.StatusOK), response))
82 | }
83 |
--------------------------------------------------------------------------------
/internal/handlers/auth_handler_test.go:
--------------------------------------------------------------------------------
1 | package handlers
2 |
3 | import (
4 | "encoding/json"
5 | "errors"
6 | "io"
7 | "net/http"
8 | "net/http/httptest"
9 | "testing"
10 |
11 | "github.com/gin-gonic/gin"
12 | "github.com/jackc/pgconn"
13 | "github.com/stretchr/testify/assert"
14 | "github.com/stretchr/testify/mock"
15 | "github.com/stretchr/testify/require"
16 | "gorm.io/gorm"
17 |
18 | "github.com/geshtng/go-base-backend/internal/dtos"
19 | "github.com/geshtng/go-base-backend/internal/helpers"
20 | smock "github.com/geshtng/go-base-backend/internal/mocks/services"
21 | "github.com/geshtng/go-base-backend/internal/models"
22 | )
23 |
24 | func TestHandler_RegisterHandler(t *testing.T) {
25 | sampleToken := "sample"
26 |
27 | tests := []struct {
28 | name string
29 | body io.Reader
30 | authService *smock.AuthService
31 | mock func(*smock.AuthService)
32 | want helpers.Response
33 | }{
34 | {
35 | name: "Success",
36 | body: MakeRequestBody(dtos.RegisterRequestDTO{
37 | Username: "username",
38 | Password: "password",
39 | }),
40 | authService: smock.NewAuthService(t),
41 | mock: func(as *smock.AuthService) {
42 | expectedUser := models.User{
43 | ID: 1,
44 | Username: "username",
45 | Password: "password",
46 | }
47 |
48 | as.On("Register", mock.Anything).Return(&expectedUser, &sampleToken, nil)
49 | },
50 | want: helpers.Response{
51 | Data: map[string]interface{}{
52 | "id": float64(1),
53 | "username": "username",
54 | "token": "sample",
55 | },
56 | Code: 201,
57 | Message: "Created",
58 | Error: false,
59 | },
60 | },
61 | {
62 | name: "Error ShouldBindJSON",
63 | body: MakeRequestBody(dtos.RegisterRequestDTO{
64 | Username: "username",
65 | }),
66 | authService: smock.NewAuthService(t),
67 | mock: func(as *smock.AuthService) {},
68 | want: helpers.Response{
69 | Code: 400,
70 | Message: "Key: 'RegisterRequestDTO.Password' Error:Field validation for 'Password' failed on the 'required' tag",
71 | Error: true,
72 | },
73 | },
74 | {
75 | name: "Error Service.Register unique violation",
76 | body: MakeRequestBody(dtos.RegisterRequestDTO{
77 | Username: "username",
78 | Password: "password",
79 | }),
80 | authService: smock.NewAuthService(t),
81 | mock: func(as *smock.AuthService) {
82 | as.On("Register", mock.Anything).Return(nil, nil, &pgconn.PgError{Code: "23505"})
83 | },
84 | want: helpers.Response{
85 | Code: 401,
86 | Message: "username is already exist",
87 | Error: true,
88 | },
89 | },
90 | {
91 | name: "Error Service.Register",
92 | body: MakeRequestBody(dtos.RegisterRequestDTO{
93 | Username: "username",
94 | Password: "password",
95 | }),
96 | authService: smock.NewAuthService(t),
97 | mock: func(as *smock.AuthService) {
98 | as.On("Register", mock.Anything).Return(nil, nil, &pgconn.PgError{Message: "something error"})
99 | },
100 | want: helpers.Response{
101 | Code: 500,
102 | Message: ": something error (SQLSTATE )",
103 | Error: true,
104 | },
105 | },
106 | }
107 | for _, tt := range tests {
108 | t.Run(tt.name, func(t *testing.T) {
109 | h := &Handler{
110 | authService: tt.authService,
111 | }
112 |
113 | tt.mock(tt.authService)
114 |
115 | r := gin.Default()
116 | endpoint := "/register"
117 |
118 | r.POST(endpoint, h.RegisterHandler)
119 | req, _ := http.NewRequest(http.MethodPost, endpoint, tt.body)
120 |
121 | w := httptest.NewRecorder()
122 | r.ServeHTTP(w, req)
123 |
124 | var response helpers.Response
125 | err := json.Unmarshal(w.Body.Bytes(), &response)
126 | require.NoError(t, err)
127 |
128 | assert.Equal(t, tt.want.Code, w.Code)
129 | assert.Equal(t, tt.want, response)
130 | })
131 | }
132 | }
133 |
134 | func TestHandler_LoginHandler(t *testing.T) {
135 | sampleToken := "sample"
136 |
137 | tests := []struct {
138 | name string
139 | body io.Reader
140 | authService *smock.AuthService
141 | mock func(*smock.AuthService)
142 | want helpers.Response
143 | }{
144 | {
145 | name: "Success",
146 | body: MakeRequestBody(dtos.LoginRequestDTO{
147 | Username: "username",
148 | Password: "password",
149 | }),
150 | authService: smock.NewAuthService(t),
151 | mock: func(as *smock.AuthService) {
152 | expectedUser := models.User{
153 | ID: 1,
154 | Username: "username",
155 | Password: "password",
156 | }
157 |
158 | as.On("Login", mock.Anything, mock.Anything).Return(&expectedUser, &sampleToken, nil)
159 | },
160 | want: helpers.Response{
161 | Data: map[string]interface{}{
162 | "id": float64(1),
163 | "username": "username",
164 | "token": "sample",
165 | },
166 | Code: 200,
167 | Message: "OK",
168 | Error: false,
169 | },
170 | },
171 | {
172 | name: "Error ShouldBindJSON",
173 | body: MakeRequestBody(dtos.LoginRequestDTO{
174 | Username: "username",
175 | }),
176 | authService: smock.NewAuthService(t),
177 | mock: func(as *smock.AuthService) {},
178 | want: helpers.Response{
179 | Code: 400,
180 | Message: "Key: 'LoginRequestDTO.Password' Error:Field validation for 'Password' failed on the 'required' tag",
181 | Error: true,
182 | },
183 | },
184 | {
185 | name: "Error Service.Login record not found",
186 | body: MakeRequestBody(dtos.LoginRequestDTO{
187 | Username: "username",
188 | Password: "password",
189 | }),
190 | authService: smock.NewAuthService(t),
191 | mock: func(as *smock.AuthService) {
192 | as.On("Login", mock.Anything, mock.Anything).Return(nil, nil, gorm.ErrRecordNotFound)
193 | },
194 | want: helpers.Response{
195 | Code: 401,
196 | Message: "wrong username or password",
197 | Error: true,
198 | },
199 | },
200 | {
201 | name: "Error Service.Login",
202 | body: MakeRequestBody(dtos.LoginRequestDTO{
203 | Username: "username",
204 | Password: "password",
205 | }),
206 | authService: smock.NewAuthService(t),
207 | mock: func(as *smock.AuthService) {
208 | as.On("Login", mock.Anything, mock.Anything).Return(nil, nil, errors.New("something error"))
209 | },
210 | want: helpers.Response{
211 | Code: 500,
212 | Message: "something error",
213 | Error: true,
214 | },
215 | },
216 | }
217 | for _, tt := range tests {
218 | t.Run(tt.name, func(t *testing.T) {
219 | h := &Handler{
220 | authService: tt.authService,
221 | }
222 |
223 | tt.mock(tt.authService)
224 |
225 | r := gin.Default()
226 | endpoint := "/login"
227 |
228 | r.POST(endpoint, h.LoginHandler)
229 | req, _ := http.NewRequest(http.MethodPost, endpoint, tt.body)
230 |
231 | w := httptest.NewRecorder()
232 | r.ServeHTTP(w, req)
233 |
234 | var response helpers.Response
235 | err := json.Unmarshal(w.Body.Bytes(), &response)
236 | require.NoError(t, err)
237 |
238 | assert.Equal(t, tt.want.Code, w.Code)
239 | assert.Equal(t, tt.want, response)
240 | })
241 | }
242 | }
243 |
--------------------------------------------------------------------------------
/internal/handlers/init.go:
--------------------------------------------------------------------------------
1 | package handlers
2 |
3 | import (
4 | "github.com/geshtng/go-base-backend/db"
5 | r "github.com/geshtng/go-base-backend/internal/repositories"
6 | s "github.com/geshtng/go-base-backend/internal/services"
7 | )
8 |
9 | type Handler struct {
10 | articleService s.ArticleService
11 | authService s.AuthService
12 | userService s.UserService
13 | }
14 |
15 | type HandlerConfig struct {
16 | ArticleService s.ArticleService
17 | AuthService s.AuthService
18 | UserService s.UserService
19 | }
20 |
21 | func New(c *HandlerConfig) *Handler {
22 | return &Handler{
23 | articleService: c.ArticleService,
24 | authService: c.AuthService,
25 | userService: c.UserService,
26 | }
27 | }
28 |
29 | func InitAllHandlers() *Handler {
30 | db := db.Get()
31 |
32 | articleRepo := r.NewArticleRepository((&r.ArticleRepoConfig{DB: db}))
33 | userRepo := r.NewUserRepository((&r.UserRepoConfig{DB: db}))
34 |
35 | articleService := s.NewArticleService(&s.ArticleServiceConfig{ArticleRepository: articleRepo})
36 | authService := s.NewAuthService(&s.AuthServiceConfig{UserRepository: userRepo})
37 | userService := s.NewUserService(&s.UserServiceConfig{UserRepository: userRepo})
38 |
39 | h := New(&HandlerConfig{
40 | ArticleService: articleService,
41 | AuthService: authService,
42 | UserService: userService,
43 | })
44 |
45 | return h
46 | }
47 |
--------------------------------------------------------------------------------
/internal/handlers/init_test.go:
--------------------------------------------------------------------------------
1 | package handlers
2 |
3 | import (
4 | "encoding/json"
5 | "strings"
6 | "testing"
7 |
8 | "github.com/gin-gonic/gin"
9 | "github.com/stretchr/testify/assert"
10 |
11 | "github.com/geshtng/go-base-backend/internal/dtos"
12 | smock "github.com/geshtng/go-base-backend/internal/mocks/services"
13 | )
14 |
15 | var MockTokenized dtos.JwtData = dtos.JwtData{
16 | ID: 1,
17 | Username: "username",
18 | }
19 |
20 | func MakeRequestBody(dto interface{}) *strings.Reader {
21 | payload, _ := json.Marshal(dto)
22 | return strings.NewReader(string(payload))
23 | }
24 |
25 | // To mock endpoint param
26 | func MiddlewareMockID(mockID string) gin.HandlerFunc {
27 | return func(c *gin.Context) {
28 | c.Params = []gin.Param{
29 | {
30 | Key: "id",
31 | Value: mockID,
32 | },
33 | }
34 | }
35 | }
36 |
37 | // To mock token
38 | func MiddlewareMockToken(ctx *gin.Context) {
39 | ctx.Set("user", MockTokenized)
40 | ctx.Next()
41 | }
42 |
43 | func TestNew(t *testing.T) {
44 | articleService := smock.NewArticleService(t)
45 |
46 | handlerConfig := HandlerConfig{
47 | ArticleService: articleService,
48 | }
49 |
50 | New(&handlerConfig)
51 | }
52 |
53 | func TestInitAllHandlers(t *testing.T) {
54 | tests := []struct {
55 | name string
56 | want *Handler
57 | }{
58 | {
59 | name: "Pass",
60 | want: &Handler{
61 | articleService: smock.NewArticleService(t),
62 | authService: smock.NewAuthService(t),
63 | userService: smock.NewUserService(t),
64 | },
65 | },
66 | }
67 | for _, tt := range tests {
68 | t.Run(tt.name, func(t *testing.T) {
69 | got := InitAllHandlers()
70 | assert.NotNil(t, got, tt.want)
71 | })
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/internal/handlers/user_handler.go:
--------------------------------------------------------------------------------
1 | package handlers
2 |
3 | import (
4 | "errors"
5 | "net/http"
6 |
7 | "github.com/gin-gonic/gin"
8 | "github.com/jinzhu/copier"
9 | "gorm.io/gorm"
10 |
11 | "github.com/geshtng/go-base-backend/internal/dtos"
12 | "github.com/geshtng/go-base-backend/internal/helpers"
13 | )
14 |
15 | func (h *Handler) ShowProfileHandler(c *gin.Context) {
16 | userContext := c.MustGet("user").(dtos.JwtData)
17 |
18 | var response dtos.ShowProfileResponseDTO
19 |
20 | user, err := h.userService.GetByID(userContext.ID)
21 | if err != nil {
22 | if errors.Is(err, gorm.ErrRecordNotFound) {
23 | c.JSON(http.StatusNotFound, helpers.BuildErrorResponse(http.StatusNotFound, err.Error()))
24 | return
25 | }
26 |
27 | c.JSON(http.StatusInternalServerError, helpers.BuildErrorResponse(http.StatusInternalServerError, err.Error()))
28 | return
29 | }
30 |
31 | err = copier.Copy(&response, &user)
32 | if err != nil {
33 | c.JSON(http.StatusInternalServerError, helpers.BuildErrorResponse(http.StatusInternalServerError, err.Error()))
34 | return
35 | }
36 |
37 | c.JSON(http.StatusOK, helpers.BuildSuccessResponse(http.StatusOK, http.StatusText(http.StatusOK), response))
38 | }
39 |
--------------------------------------------------------------------------------
/internal/handlers/user_handler_test.go:
--------------------------------------------------------------------------------
1 | package handlers
2 |
3 | import (
4 | "encoding/json"
5 | "errors"
6 | "net/http"
7 | "net/http/httptest"
8 | "testing"
9 |
10 | "github.com/gin-gonic/gin"
11 | "github.com/stretchr/testify/assert"
12 | "github.com/stretchr/testify/mock"
13 | "github.com/stretchr/testify/require"
14 | "gorm.io/gorm"
15 |
16 | "github.com/geshtng/go-base-backend/internal/helpers"
17 | smock "github.com/geshtng/go-base-backend/internal/mocks/services"
18 | "github.com/geshtng/go-base-backend/internal/models"
19 | )
20 |
21 | func TestHandler_ShowProfileHandler(t *testing.T) {
22 | tests := []struct {
23 | name string
24 | userService *smock.UserService
25 | mock func(*smock.UserService)
26 | want helpers.Response
27 | }{
28 | {
29 | name: "Success",
30 | userService: smock.NewUserService(t),
31 | mock: func(us *smock.UserService) {
32 | expectedUser := models.User{
33 | ID: 1,
34 | Username: "username",
35 | Password: "password",
36 | }
37 |
38 | us.On("GetByID", mock.Anything).Return(&expectedUser, nil)
39 | },
40 |
41 | want: helpers.Response{
42 | Data: map[string]interface{}{
43 | "id": float64(1),
44 | "username": "username",
45 | },
46 | Code: 200,
47 | Message: "OK",
48 | Error: false,
49 | },
50 | },
51 | {
52 | name: "Error copier",
53 | userService: smock.NewUserService(t),
54 | mock: func(us *smock.UserService) {
55 | us.On("GetByID", mock.Anything).Return(nil, nil)
56 | },
57 |
58 | want: helpers.Response{
59 | Code: 500,
60 | Message: "copy from is invalid",
61 | Error: true,
62 | },
63 | },
64 | {
65 | name: "Error Service.GetByID record not found",
66 | userService: smock.NewUserService(t),
67 | mock: func(us *smock.UserService) {
68 | us.On("GetByID", mock.Anything).Return(nil, gorm.ErrRecordNotFound)
69 | },
70 |
71 | want: helpers.Response{
72 | Code: 404,
73 | Message: "record not found",
74 | Error: true,
75 | },
76 | },
77 | {
78 | name: "Error Service.GetByID",
79 | userService: smock.NewUserService(t),
80 | mock: func(us *smock.UserService) {
81 | us.On("GetByID", mock.Anything).Return(nil, errors.New("something error"))
82 | },
83 |
84 | want: helpers.Response{
85 | Code: 500,
86 | Message: "something error",
87 | Error: true,
88 | },
89 | },
90 | }
91 | for _, tt := range tests {
92 | t.Run(tt.name, func(t *testing.T) {
93 | h := &Handler{
94 | userService: tt.userService,
95 | }
96 |
97 | tt.mock((*smock.UserService)(tt.userService))
98 |
99 | r := gin.Default()
100 | endpoint := "/profiles"
101 |
102 | r.GET(endpoint, MiddlewareMockToken, h.ShowProfileHandler)
103 |
104 | req, _ := http.NewRequest(http.MethodGet, endpoint, nil)
105 |
106 | w := httptest.NewRecorder()
107 | r.ServeHTTP(w, req)
108 |
109 | var response helpers.Response
110 | err := json.Unmarshal(w.Body.Bytes(), &response)
111 | require.NoError(t, err)
112 |
113 | assert.Equal(t, tt.want.Code, w.Code)
114 | assert.Equal(t, tt.want, response)
115 | })
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/internal/helpers/hash.go:
--------------------------------------------------------------------------------
1 | package helpers
2 |
3 | import (
4 | "golang.org/x/crypto/bcrypt"
5 | )
6 |
7 | func Encrypt(str string) (string, error) {
8 | hash, err := bcrypt.GenerateFromPassword([]byte(str), bcrypt.DefaultCost)
9 | return string(hash), err
10 | }
11 |
12 | func CompareEncrypt(str, hash string) bool {
13 | err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(str))
14 | return err == nil
15 | }
16 |
--------------------------------------------------------------------------------
/internal/helpers/jwt.go:
--------------------------------------------------------------------------------
1 | package helpers
2 |
3 | import (
4 | "strconv"
5 | "time"
6 |
7 | "github.com/golang-jwt/jwt/v4"
8 |
9 | "github.com/geshtng/go-base-backend/config"
10 | )
11 |
12 | type IdTokenClaims struct {
13 | jwt.RegisteredClaims
14 | Data interface{} `json:"data"`
15 | }
16 |
17 | func GenerateJwtToken(data interface{}) (*string, error) {
18 | var idExp int64
19 |
20 | jwtEnv := config.InitJwt()
21 |
22 | expired, _ := strconv.ParseInt(jwtEnv.Expired, 10, 64)
23 |
24 | idExp = expired * 60
25 | unixTime := time.Now().Unix()
26 | tokenExp := unixTime + idExp
27 |
28 | claims := &IdTokenClaims{
29 | RegisteredClaims: jwt.RegisteredClaims{
30 | Issuer: jwtEnv.Issuer,
31 | ExpiresAt: &jwt.NumericDate{
32 | Time: time.Unix(tokenExp, 0),
33 | },
34 | IssuedAt: &jwt.NumericDate{
35 | Time: time.Now(),
36 | },
37 | },
38 | Data: data,
39 | }
40 |
41 | token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
42 | tokenString, err := token.SignedString([]byte(jwtEnv.Secret))
43 | if err != nil {
44 | return nil, err
45 | }
46 |
47 | return &tokenString, nil
48 | }
49 |
--------------------------------------------------------------------------------
/internal/helpers/response.go:
--------------------------------------------------------------------------------
1 | package helpers
2 |
3 | type Response struct {
4 | Data interface{} `json:"data"`
5 | Code int `json:"code"`
6 | Message string `json:"message"`
7 | Error bool `json:"error"`
8 | }
9 |
10 | func BuildSuccessResponse(code int, message string, data interface{}) Response {
11 | return Response{
12 | Code: code,
13 | Error: false,
14 | Message: message,
15 | Data: data,
16 | }
17 | }
18 |
19 | func BuildErrorResponse(code int, errorMessage string) Response {
20 | return Response{
21 | Code: code,
22 | Error: true,
23 | Message: errorMessage,
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/internal/middlewares/authorize_jwt.go:
--------------------------------------------------------------------------------
1 | package middlewares
2 |
3 | import (
4 | "encoding/json"
5 | "errors"
6 | "net/http"
7 |
8 | "github.com/gin-gonic/gin"
9 | "github.com/golang-jwt/jwt/v4"
10 |
11 | "github.com/geshtng/go-base-backend/config"
12 | "github.com/geshtng/go-base-backend/internal/dtos"
13 | "github.com/geshtng/go-base-backend/internal/helpers"
14 | )
15 |
16 | func validateToken(encodedToken string) (*jwt.Token, error) {
17 | jwtEnv := config.InitJwt()
18 |
19 | return jwt.Parse(encodedToken, func(token *jwt.Token) (interface{}, error) {
20 | if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
21 | return nil, errors.New("invalid token")
22 | }
23 |
24 | return []byte(jwtEnv.Secret), nil
25 | })
26 | }
27 |
28 | func AuthorizeJWT(c *gin.Context) {
29 | authHeader := c.GetHeader("Authorization")
30 |
31 | if authHeader == "" {
32 | c.AbortWithStatusJSON(
33 | http.StatusUnauthorized,
34 | helpers.BuildErrorResponse(http.StatusUnauthorized, http.StatusText(http.StatusUnauthorized)),
35 | )
36 | return
37 | }
38 |
39 | tokenString := authHeader[7:]
40 |
41 | token, err := validateToken(tokenString)
42 | if err != nil {
43 | c.AbortWithStatusJSON(
44 | http.StatusUnauthorized,
45 | helpers.BuildErrorResponse(http.StatusUnauthorized, http.StatusText(http.StatusUnauthorized)),
46 | )
47 | return
48 | }
49 |
50 | claims, ok := token.Claims.(jwt.MapClaims)
51 | if !ok {
52 | c.AbortWithStatusJSON(
53 | http.StatusUnauthorized,
54 | helpers.BuildErrorResponse(http.StatusUnauthorized, http.StatusText(http.StatusUnauthorized)),
55 | )
56 | return
57 | }
58 |
59 | userJson, _ := json.Marshal(claims["data"]) // change from type map[string]interface{} to []byte
60 | jwtData := dtos.JwtData{}
61 | err = json.Unmarshal(userJson, &jwtData) // change from type []byte to models.User{}
62 | if err != nil {
63 | c.AbortWithStatusJSON(
64 | http.StatusUnauthorized,
65 | helpers.BuildErrorResponse(http.StatusUnauthorized, http.StatusText(http.StatusUnauthorized)),
66 | )
67 | return
68 | }
69 |
70 | c.Set("user", jwtData)
71 | }
72 |
--------------------------------------------------------------------------------
/internal/mocks/repositories/mock_article_repository.go:
--------------------------------------------------------------------------------
1 | // Code generated by mockery v2.44.1. DO NOT EDIT.
2 |
3 | package repomocks
4 |
5 | import (
6 | models "github.com/geshtng/go-base-backend/internal/models"
7 | mock "github.com/stretchr/testify/mock"
8 | )
9 |
10 | // ArticleRepository is an autogenerated mock type for the ArticleRepository type
11 | type ArticleRepository struct {
12 | mock.Mock
13 | }
14 |
15 | // Delete provides a mock function with given fields: id
16 | func (_m *ArticleRepository) Delete(id int64) error {
17 | ret := _m.Called(id)
18 |
19 | if len(ret) == 0 {
20 | panic("no return value specified for Delete")
21 | }
22 |
23 | var r0 error
24 | if rf, ok := ret.Get(0).(func(int64) error); ok {
25 | r0 = rf(id)
26 | } else {
27 | r0 = ret.Error(0)
28 | }
29 |
30 | return r0
31 | }
32 |
33 | // GetAll provides a mock function with given fields:
34 | func (_m *ArticleRepository) GetAll() ([]*models.Article, error) {
35 | ret := _m.Called()
36 |
37 | if len(ret) == 0 {
38 | panic("no return value specified for GetAll")
39 | }
40 |
41 | var r0 []*models.Article
42 | var r1 error
43 | if rf, ok := ret.Get(0).(func() ([]*models.Article, error)); ok {
44 | return rf()
45 | }
46 | if rf, ok := ret.Get(0).(func() []*models.Article); ok {
47 | r0 = rf()
48 | } else {
49 | if ret.Get(0) != nil {
50 | r0 = ret.Get(0).([]*models.Article)
51 | }
52 | }
53 |
54 | if rf, ok := ret.Get(1).(func() error); ok {
55 | r1 = rf()
56 | } else {
57 | r1 = ret.Error(1)
58 | }
59 |
60 | return r0, r1
61 | }
62 |
63 | // GetByID provides a mock function with given fields: id
64 | func (_m *ArticleRepository) GetByID(id int64) (*models.Article, error) {
65 | ret := _m.Called(id)
66 |
67 | if len(ret) == 0 {
68 | panic("no return value specified for GetByID")
69 | }
70 |
71 | var r0 *models.Article
72 | var r1 error
73 | if rf, ok := ret.Get(0).(func(int64) (*models.Article, error)); ok {
74 | return rf(id)
75 | }
76 | if rf, ok := ret.Get(0).(func(int64) *models.Article); ok {
77 | r0 = rf(id)
78 | } else {
79 | if ret.Get(0) != nil {
80 | r0 = ret.Get(0).(*models.Article)
81 | }
82 | }
83 |
84 | if rf, ok := ret.Get(1).(func(int64) error); ok {
85 | r1 = rf(id)
86 | } else {
87 | r1 = ret.Error(1)
88 | }
89 |
90 | return r0, r1
91 | }
92 |
93 | // Insert provides a mock function with given fields: article
94 | func (_m *ArticleRepository) Insert(article models.Article) (*models.Article, error) {
95 | ret := _m.Called(article)
96 |
97 | if len(ret) == 0 {
98 | panic("no return value specified for Insert")
99 | }
100 |
101 | var r0 *models.Article
102 | var r1 error
103 | if rf, ok := ret.Get(0).(func(models.Article) (*models.Article, error)); ok {
104 | return rf(article)
105 | }
106 | if rf, ok := ret.Get(0).(func(models.Article) *models.Article); ok {
107 | r0 = rf(article)
108 | } else {
109 | if ret.Get(0) != nil {
110 | r0 = ret.Get(0).(*models.Article)
111 | }
112 | }
113 |
114 | if rf, ok := ret.Get(1).(func(models.Article) error); ok {
115 | r1 = rf(article)
116 | } else {
117 | r1 = ret.Error(1)
118 | }
119 |
120 | return r0, r1
121 | }
122 |
123 | // Update provides a mock function with given fields: id, article
124 | func (_m *ArticleRepository) Update(id int64, article models.Article) (*models.Article, error) {
125 | ret := _m.Called(id, article)
126 |
127 | if len(ret) == 0 {
128 | panic("no return value specified for Update")
129 | }
130 |
131 | var r0 *models.Article
132 | var r1 error
133 | if rf, ok := ret.Get(0).(func(int64, models.Article) (*models.Article, error)); ok {
134 | return rf(id, article)
135 | }
136 | if rf, ok := ret.Get(0).(func(int64, models.Article) *models.Article); ok {
137 | r0 = rf(id, article)
138 | } else {
139 | if ret.Get(0) != nil {
140 | r0 = ret.Get(0).(*models.Article)
141 | }
142 | }
143 |
144 | if rf, ok := ret.Get(1).(func(int64, models.Article) error); ok {
145 | r1 = rf(id, article)
146 | } else {
147 | r1 = ret.Error(1)
148 | }
149 |
150 | return r0, r1
151 | }
152 |
153 | // NewArticleRepository creates a new instance of ArticleRepository. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
154 | // The first argument is typically a *testing.T value.
155 | func NewArticleRepository(t interface {
156 | mock.TestingT
157 | Cleanup(func())
158 | }) *ArticleRepository {
159 | mock := &ArticleRepository{}
160 | mock.Mock.Test(t)
161 |
162 | t.Cleanup(func() { mock.AssertExpectations(t) })
163 |
164 | return mock
165 | }
166 |
--------------------------------------------------------------------------------
/internal/mocks/repositories/mock_user_repository.go:
--------------------------------------------------------------------------------
1 | // Code generated by mockery v2.44.1. DO NOT EDIT.
2 |
3 | package repomocks
4 |
5 | import (
6 | models "github.com/geshtng/go-base-backend/internal/models"
7 | mock "github.com/stretchr/testify/mock"
8 | )
9 |
10 | // UserRepository is an autogenerated mock type for the UserRepository type
11 | type UserRepository struct {
12 | mock.Mock
13 | }
14 |
15 | // GetByID provides a mock function with given fields: id
16 | func (_m *UserRepository) GetByID(id int64) (*models.User, error) {
17 | ret := _m.Called(id)
18 |
19 | if len(ret) == 0 {
20 | panic("no return value specified for GetByID")
21 | }
22 |
23 | var r0 *models.User
24 | var r1 error
25 | if rf, ok := ret.Get(0).(func(int64) (*models.User, error)); ok {
26 | return rf(id)
27 | }
28 | if rf, ok := ret.Get(0).(func(int64) *models.User); ok {
29 | r0 = rf(id)
30 | } else {
31 | if ret.Get(0) != nil {
32 | r0 = ret.Get(0).(*models.User)
33 | }
34 | }
35 |
36 | if rf, ok := ret.Get(1).(func(int64) error); ok {
37 | r1 = rf(id)
38 | } else {
39 | r1 = ret.Error(1)
40 | }
41 |
42 | return r0, r1
43 | }
44 |
45 | // GetByUsername provides a mock function with given fields: username
46 | func (_m *UserRepository) GetByUsername(username string) (*models.User, error) {
47 | ret := _m.Called(username)
48 |
49 | if len(ret) == 0 {
50 | panic("no return value specified for GetByUsername")
51 | }
52 |
53 | var r0 *models.User
54 | var r1 error
55 | if rf, ok := ret.Get(0).(func(string) (*models.User, error)); ok {
56 | return rf(username)
57 | }
58 | if rf, ok := ret.Get(0).(func(string) *models.User); ok {
59 | r0 = rf(username)
60 | } else {
61 | if ret.Get(0) != nil {
62 | r0 = ret.Get(0).(*models.User)
63 | }
64 | }
65 |
66 | if rf, ok := ret.Get(1).(func(string) error); ok {
67 | r1 = rf(username)
68 | } else {
69 | r1 = ret.Error(1)
70 | }
71 |
72 | return r0, r1
73 | }
74 |
75 | // Insert provides a mock function with given fields: user
76 | func (_m *UserRepository) Insert(user models.User) (*models.User, error) {
77 | ret := _m.Called(user)
78 |
79 | if len(ret) == 0 {
80 | panic("no return value specified for Insert")
81 | }
82 |
83 | var r0 *models.User
84 | var r1 error
85 | if rf, ok := ret.Get(0).(func(models.User) (*models.User, error)); ok {
86 | return rf(user)
87 | }
88 | if rf, ok := ret.Get(0).(func(models.User) *models.User); ok {
89 | r0 = rf(user)
90 | } else {
91 | if ret.Get(0) != nil {
92 | r0 = ret.Get(0).(*models.User)
93 | }
94 | }
95 |
96 | if rf, ok := ret.Get(1).(func(models.User) error); ok {
97 | r1 = rf(user)
98 | } else {
99 | r1 = ret.Error(1)
100 | }
101 |
102 | return r0, r1
103 | }
104 |
105 | // NewUserRepository creates a new instance of UserRepository. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
106 | // The first argument is typically a *testing.T value.
107 | func NewUserRepository(t interface {
108 | mock.TestingT
109 | Cleanup(func())
110 | }) *UserRepository {
111 | mock := &UserRepository{}
112 | mock.Mock.Test(t)
113 |
114 | t.Cleanup(func() { mock.AssertExpectations(t) })
115 |
116 | return mock
117 | }
118 |
--------------------------------------------------------------------------------
/internal/mocks/services/mock_article_service.go:
--------------------------------------------------------------------------------
1 | // Code generated by mockery v2.44.1. DO NOT EDIT.
2 |
3 | package servicemocks
4 |
5 | import (
6 | models "github.com/geshtng/go-base-backend/internal/models"
7 | mock "github.com/stretchr/testify/mock"
8 | )
9 |
10 | // ArticleService is an autogenerated mock type for the ArticleService type
11 | type ArticleService struct {
12 | mock.Mock
13 | }
14 |
15 | // Create provides a mock function with given fields: article
16 | func (_m *ArticleService) Create(article models.Article) (*models.Article, error) {
17 | ret := _m.Called(article)
18 |
19 | if len(ret) == 0 {
20 | panic("no return value specified for Create")
21 | }
22 |
23 | var r0 *models.Article
24 | var r1 error
25 | if rf, ok := ret.Get(0).(func(models.Article) (*models.Article, error)); ok {
26 | return rf(article)
27 | }
28 | if rf, ok := ret.Get(0).(func(models.Article) *models.Article); ok {
29 | r0 = rf(article)
30 | } else {
31 | if ret.Get(0) != nil {
32 | r0 = ret.Get(0).(*models.Article)
33 | }
34 | }
35 |
36 | if rf, ok := ret.Get(1).(func(models.Article) error); ok {
37 | r1 = rf(article)
38 | } else {
39 | r1 = ret.Error(1)
40 | }
41 |
42 | return r0, r1
43 | }
44 |
45 | // Delete provides a mock function with given fields: id
46 | func (_m *ArticleService) Delete(id int64) error {
47 | ret := _m.Called(id)
48 |
49 | if len(ret) == 0 {
50 | panic("no return value specified for Delete")
51 | }
52 |
53 | var r0 error
54 | if rf, ok := ret.Get(0).(func(int64) error); ok {
55 | r0 = rf(id)
56 | } else {
57 | r0 = ret.Error(0)
58 | }
59 |
60 | return r0
61 | }
62 |
63 | // GetAll provides a mock function with given fields:
64 | func (_m *ArticleService) GetAll() ([]*models.Article, error) {
65 | ret := _m.Called()
66 |
67 | if len(ret) == 0 {
68 | panic("no return value specified for GetAll")
69 | }
70 |
71 | var r0 []*models.Article
72 | var r1 error
73 | if rf, ok := ret.Get(0).(func() ([]*models.Article, error)); ok {
74 | return rf()
75 | }
76 | if rf, ok := ret.Get(0).(func() []*models.Article); ok {
77 | r0 = rf()
78 | } else {
79 | if ret.Get(0) != nil {
80 | r0 = ret.Get(0).([]*models.Article)
81 | }
82 | }
83 |
84 | if rf, ok := ret.Get(1).(func() error); ok {
85 | r1 = rf()
86 | } else {
87 | r1 = ret.Error(1)
88 | }
89 |
90 | return r0, r1
91 | }
92 |
93 | // GetByID provides a mock function with given fields: id
94 | func (_m *ArticleService) GetByID(id int64) (*models.Article, error) {
95 | ret := _m.Called(id)
96 |
97 | if len(ret) == 0 {
98 | panic("no return value specified for GetByID")
99 | }
100 |
101 | var r0 *models.Article
102 | var r1 error
103 | if rf, ok := ret.Get(0).(func(int64) (*models.Article, error)); ok {
104 | return rf(id)
105 | }
106 | if rf, ok := ret.Get(0).(func(int64) *models.Article); ok {
107 | r0 = rf(id)
108 | } else {
109 | if ret.Get(0) != nil {
110 | r0 = ret.Get(0).(*models.Article)
111 | }
112 | }
113 |
114 | if rf, ok := ret.Get(1).(func(int64) error); ok {
115 | r1 = rf(id)
116 | } else {
117 | r1 = ret.Error(1)
118 | }
119 |
120 | return r0, r1
121 | }
122 |
123 | // Update provides a mock function with given fields: id, article
124 | func (_m *ArticleService) Update(id int64, article models.Article) (*models.Article, error) {
125 | ret := _m.Called(id, article)
126 |
127 | if len(ret) == 0 {
128 | panic("no return value specified for Update")
129 | }
130 |
131 | var r0 *models.Article
132 | var r1 error
133 | if rf, ok := ret.Get(0).(func(int64, models.Article) (*models.Article, error)); ok {
134 | return rf(id, article)
135 | }
136 | if rf, ok := ret.Get(0).(func(int64, models.Article) *models.Article); ok {
137 | r0 = rf(id, article)
138 | } else {
139 | if ret.Get(0) != nil {
140 | r0 = ret.Get(0).(*models.Article)
141 | }
142 | }
143 |
144 | if rf, ok := ret.Get(1).(func(int64, models.Article) error); ok {
145 | r1 = rf(id, article)
146 | } else {
147 | r1 = ret.Error(1)
148 | }
149 |
150 | return r0, r1
151 | }
152 |
153 | // NewArticleService creates a new instance of ArticleService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
154 | // The first argument is typically a *testing.T value.
155 | func NewArticleService(t interface {
156 | mock.TestingT
157 | Cleanup(func())
158 | }) *ArticleService {
159 | mock := &ArticleService{}
160 | mock.Mock.Test(t)
161 |
162 | t.Cleanup(func() { mock.AssertExpectations(t) })
163 |
164 | return mock
165 | }
166 |
--------------------------------------------------------------------------------
/internal/mocks/services/mock_auth_service.go:
--------------------------------------------------------------------------------
1 | // Code generated by mockery v2.44.1. DO NOT EDIT.
2 |
3 | package servicemocks
4 |
5 | import (
6 | models "github.com/geshtng/go-base-backend/internal/models"
7 | mock "github.com/stretchr/testify/mock"
8 | )
9 |
10 | // AuthService is an autogenerated mock type for the AuthService type
11 | type AuthService struct {
12 | mock.Mock
13 | }
14 |
15 | // Login provides a mock function with given fields: username, password
16 | func (_m *AuthService) Login(username string, password string) (*models.User, *string, error) {
17 | ret := _m.Called(username, password)
18 |
19 | if len(ret) == 0 {
20 | panic("no return value specified for Login")
21 | }
22 |
23 | var r0 *models.User
24 | var r1 *string
25 | var r2 error
26 | if rf, ok := ret.Get(0).(func(string, string) (*models.User, *string, error)); ok {
27 | return rf(username, password)
28 | }
29 | if rf, ok := ret.Get(0).(func(string, string) *models.User); ok {
30 | r0 = rf(username, password)
31 | } else {
32 | if ret.Get(0) != nil {
33 | r0 = ret.Get(0).(*models.User)
34 | }
35 | }
36 |
37 | if rf, ok := ret.Get(1).(func(string, string) *string); ok {
38 | r1 = rf(username, password)
39 | } else {
40 | if ret.Get(1) != nil {
41 | r1 = ret.Get(1).(*string)
42 | }
43 | }
44 |
45 | if rf, ok := ret.Get(2).(func(string, string) error); ok {
46 | r2 = rf(username, password)
47 | } else {
48 | r2 = ret.Error(2)
49 | }
50 |
51 | return r0, r1, r2
52 | }
53 |
54 | // Register provides a mock function with given fields: auth
55 | func (_m *AuthService) Register(auth models.User) (*models.User, *string, error) {
56 | ret := _m.Called(auth)
57 |
58 | if len(ret) == 0 {
59 | panic("no return value specified for Register")
60 | }
61 |
62 | var r0 *models.User
63 | var r1 *string
64 | var r2 error
65 | if rf, ok := ret.Get(0).(func(models.User) (*models.User, *string, error)); ok {
66 | return rf(auth)
67 | }
68 | if rf, ok := ret.Get(0).(func(models.User) *models.User); ok {
69 | r0 = rf(auth)
70 | } else {
71 | if ret.Get(0) != nil {
72 | r0 = ret.Get(0).(*models.User)
73 | }
74 | }
75 |
76 | if rf, ok := ret.Get(1).(func(models.User) *string); ok {
77 | r1 = rf(auth)
78 | } else {
79 | if ret.Get(1) != nil {
80 | r1 = ret.Get(1).(*string)
81 | }
82 | }
83 |
84 | if rf, ok := ret.Get(2).(func(models.User) error); ok {
85 | r2 = rf(auth)
86 | } else {
87 | r2 = ret.Error(2)
88 | }
89 |
90 | return r0, r1, r2
91 | }
92 |
93 | // NewAuthService creates a new instance of AuthService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
94 | // The first argument is typically a *testing.T value.
95 | func NewAuthService(t interface {
96 | mock.TestingT
97 | Cleanup(func())
98 | }) *AuthService {
99 | mock := &AuthService{}
100 | mock.Mock.Test(t)
101 |
102 | t.Cleanup(func() { mock.AssertExpectations(t) })
103 |
104 | return mock
105 | }
106 |
--------------------------------------------------------------------------------
/internal/mocks/services/mock_user_service.go:
--------------------------------------------------------------------------------
1 | // Code generated by mockery v2.44.1. DO NOT EDIT.
2 |
3 | package servicemocks
4 |
5 | import (
6 | models "github.com/geshtng/go-base-backend/internal/models"
7 | mock "github.com/stretchr/testify/mock"
8 | )
9 |
10 | // UserService is an autogenerated mock type for the UserService type
11 | type UserService struct {
12 | mock.Mock
13 | }
14 |
15 | // GetByID provides a mock function with given fields: id
16 | func (_m *UserService) GetByID(id int64) (*models.User, error) {
17 | ret := _m.Called(id)
18 |
19 | if len(ret) == 0 {
20 | panic("no return value specified for GetByID")
21 | }
22 |
23 | var r0 *models.User
24 | var r1 error
25 | if rf, ok := ret.Get(0).(func(int64) (*models.User, error)); ok {
26 | return rf(id)
27 | }
28 | if rf, ok := ret.Get(0).(func(int64) *models.User); ok {
29 | r0 = rf(id)
30 | } else {
31 | if ret.Get(0) != nil {
32 | r0 = ret.Get(0).(*models.User)
33 | }
34 | }
35 |
36 | if rf, ok := ret.Get(1).(func(int64) error); ok {
37 | r1 = rf(id)
38 | } else {
39 | r1 = ret.Error(1)
40 | }
41 |
42 | return r0, r1
43 | }
44 |
45 | // NewUserService creates a new instance of UserService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
46 | // The first argument is typically a *testing.T value.
47 | func NewUserService(t interface {
48 | mock.TestingT
49 | Cleanup(func())
50 | }) *UserService {
51 | mock := &UserService{}
52 | mock.Mock.Test(t)
53 |
54 | t.Cleanup(func() { mock.AssertExpectations(t) })
55 |
56 | return mock
57 | }
58 |
--------------------------------------------------------------------------------
/internal/models/article.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | type Article struct {
4 | ID int64 `gorm:"primary_key"`
5 | Title string
6 | Description string
7 | }
8 |
--------------------------------------------------------------------------------
/internal/models/user.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | type User struct {
4 | ID int64 `gorm:"primary_key"`
5 | Username string `gorm:"unique"`
6 | Password string
7 | }
8 |
--------------------------------------------------------------------------------
/internal/repositories/article_repository.go:
--------------------------------------------------------------------------------
1 | package repositories
2 |
3 | import (
4 | "gorm.io/gorm"
5 | "gorm.io/gorm/clause"
6 |
7 | "github.com/geshtng/go-base-backend/internal/models"
8 | )
9 |
10 | type ArticleRepository interface {
11 | GetAll() ([]*models.Article, error)
12 | GetByID(id int64) (*models.Article, error)
13 | Insert(article models.Article) (*models.Article, error)
14 | Update(id int64, article models.Article) (*models.Article, error)
15 | Delete(id int64) error
16 | }
17 |
18 | type articleRepository struct {
19 | db *gorm.DB
20 | }
21 |
22 | type ArticleRepoConfig struct {
23 | DB *gorm.DB
24 | }
25 |
26 | func NewArticleRepository(a *ArticleRepoConfig) ArticleRepository {
27 | return &articleRepository{
28 | db: a.DB,
29 | }
30 | }
31 |
32 | func (a *articleRepository) GetAll() ([]*models.Article, error) {
33 | var articles []*models.Article
34 |
35 | result := a.db.Find(&articles)
36 | if result.Error != nil {
37 | return nil, result.Error
38 | }
39 |
40 | return articles, nil
41 | }
42 |
43 | func (a *articleRepository) GetByID(id int64) (*models.Article, error) {
44 | var article *models.Article
45 |
46 | result := a.db.First(&article, id)
47 | if result.Error != nil {
48 | return nil, result.Error
49 | }
50 |
51 | return article, nil
52 | }
53 |
54 | func (a *articleRepository) Insert(article models.Article) (*models.Article, error) {
55 | result := a.db.Create(&article)
56 | if result.Error != nil {
57 | return nil, result.Error
58 | }
59 |
60 | return &article, nil
61 | }
62 |
63 | func (a *articleRepository) Update(id int64, article models.Article) (*models.Article, error) {
64 | result := a.db.
65 | Clauses(clause.Returning{}).
66 | Where("id = ?", id).
67 | Updates(&article)
68 |
69 | if result.Error != nil {
70 | return nil, result.Error
71 | }
72 |
73 | if result.RowsAffected == 0 {
74 | return nil, gorm.ErrRecordNotFound
75 | }
76 |
77 | return &article, nil
78 | }
79 |
80 | func (a *articleRepository) Delete(id int64) error {
81 | result := a.db.Delete(&models.Article{}, id)
82 | if result.Error != nil {
83 | return result.Error
84 | }
85 |
86 | if result.RowsAffected == 0 {
87 | return gorm.ErrRecordNotFound
88 | }
89 |
90 | return nil
91 | }
92 |
--------------------------------------------------------------------------------
/internal/repositories/article_repository_test.go:
--------------------------------------------------------------------------------
1 | package repositories
2 |
3 | import (
4 | "errors"
5 | "reflect"
6 | "regexp"
7 | "testing"
8 |
9 | "github.com/DATA-DOG/go-sqlmock"
10 | "github.com/stretchr/testify/assert"
11 | "gorm.io/driver/postgres"
12 | "gorm.io/gorm"
13 |
14 | "github.com/geshtng/go-base-backend/internal/models"
15 | )
16 |
17 | func TestNewArticleRepository(t *testing.T) {
18 | dbConn, _, _ := sqlmock.New()
19 | dia := postgres.New(postgres.Config{
20 | DriverName: "postgres",
21 | Conn: dbConn,
22 | })
23 |
24 | db, _ := gorm.Open(dia)
25 | articleRepo := NewArticleRepository(&ArticleRepoConfig{DB: db})
26 |
27 | assert.NotNil(t, articleRepo)
28 | }
29 |
30 | func Test_articleRepository_GetAll(t *testing.T) {
31 | dbConn, mock, _ := sqlmock.New()
32 | dia := postgres.New(postgres.Config{
33 | DriverName: "postgres",
34 | Conn: dbConn,
35 | })
36 |
37 | db, _ := gorm.Open(dia)
38 |
39 | tests := []struct {
40 | name string
41 | a *articleRepository
42 | mock func()
43 | want []*models.Article
44 | wantErr bool
45 | }{
46 | {
47 | name: "Success",
48 | a: &articleRepository{
49 | db: db,
50 | },
51 | mock: func() {
52 | mock.ExpectQuery("SELECT (.+)").
53 | WillReturnRows(sqlmock.NewRows([]string{"id", "title", "description"}).
54 | AddRow(1, "title", "description"))
55 | },
56 | want: []*models.Article{
57 | {
58 | ID: 1,
59 | Title: "title",
60 | Description: "description",
61 | },
62 | },
63 | wantErr: false,
64 | },
65 | {
66 | name: "Error DB Find",
67 | a: &articleRepository{
68 | db: db,
69 | },
70 | mock: func() {
71 | mock.ExpectQuery("SELECT (.+)").
72 | WillReturnError(errors.New("something error"))
73 | },
74 | want: nil,
75 | wantErr: true,
76 | },
77 | }
78 | for _, tt := range tests {
79 | t.Run(tt.name, func(t *testing.T) {
80 | tt.mock()
81 |
82 | got, err := tt.a.GetAll()
83 | if (err != nil) != tt.wantErr {
84 | t.Errorf("articleRepository.GetAll() error = %v, wantErr %v", err, tt.wantErr)
85 | return
86 | }
87 |
88 | if !reflect.DeepEqual(got, tt.want) {
89 | t.Errorf("articleRepository.GetAll() = %v, want %v", got, tt.want)
90 | }
91 | })
92 | }
93 | }
94 |
95 | func Test_articleRepository_GetByID(t *testing.T) {
96 | dbConn, mock, _ := sqlmock.New()
97 | dia := postgres.New(postgres.Config{
98 | DriverName: "postgres",
99 | Conn: dbConn,
100 | })
101 |
102 | db, _ := gorm.Open(dia)
103 |
104 | type args struct {
105 | id int64
106 | }
107 | tests := []struct {
108 | name string
109 | a *articleRepository
110 | mock func()
111 | args args
112 | want *models.Article
113 | wantErr bool
114 | }{
115 | {
116 | name: "Success",
117 | a: &articleRepository{
118 | db: db,
119 | },
120 | mock: func() {
121 | mock.ExpectQuery("SELECT (.+)").
122 | WithArgs(1).
123 | WillReturnRows(sqlmock.NewRows([]string{"id", "title", "description"}).
124 | AddRow(1, "title", "description"))
125 | },
126 | args: args{
127 | id: 1,
128 | },
129 | want: &models.Article{
130 | ID: 1,
131 | Title: "title",
132 | Description: "description",
133 | },
134 | wantErr: false,
135 | },
136 | {
137 | name: "Error DB First",
138 | a: &articleRepository{
139 | db: db,
140 | },
141 | mock: func() {
142 | mock.ExpectQuery("SELECT (.+)").
143 | WithArgs(1).
144 | WillReturnError(errors.New("something error"))
145 | },
146 | args: args{
147 | id: 1,
148 | },
149 | want: nil,
150 | wantErr: true,
151 | },
152 | }
153 | for _, tt := range tests {
154 | t.Run(tt.name, func(t *testing.T) {
155 | tt.mock()
156 |
157 | got, err := tt.a.GetByID(tt.args.id)
158 | if (err != nil) != tt.wantErr {
159 | t.Errorf("articleRepository.GetByID() error = %v, wantErr %v", err, tt.wantErr)
160 | return
161 | }
162 | if !reflect.DeepEqual(got, tt.want) {
163 | t.Errorf("articleRepository.GetByID() = %v, want %v", got, tt.want)
164 | }
165 | })
166 | }
167 | }
168 |
169 | func Test_articleRepository_Insert(t *testing.T) {
170 | dbConn, mock, _ := sqlmock.New()
171 | dia := postgres.New(postgres.Config{
172 | DriverName: "postgres",
173 | Conn: dbConn,
174 | })
175 |
176 | db, _ := gorm.Open(dia)
177 |
178 | type args struct {
179 | article models.Article
180 | }
181 | tests := []struct {
182 | name string
183 | a *articleRepository
184 | mock func()
185 | args args
186 | want *models.Article
187 | wantErr bool
188 | }{
189 | {
190 | name: "Success",
191 | a: &articleRepository{
192 | db: db,
193 | },
194 | mock: func() {
195 | mock.ExpectBegin()
196 | mock.ExpectQuery(`INSERT INTO "articles" (.+) VALUES (.+)`).
197 | WithArgs("title", "description").
198 | WillReturnRows(sqlmock.NewRows([]string{"id", "title", "description"}).
199 | AddRow(1, "title", "description"))
200 | mock.ExpectCommit()
201 | },
202 | args: args{
203 | article: models.Article{
204 | Title: "title",
205 | Description: "description",
206 | },
207 | },
208 | want: &models.Article{
209 | ID: 1,
210 | Title: "title",
211 | Description: "description",
212 | },
213 | wantErr: false,
214 | },
215 | {
216 | name: "Error DB Create",
217 | a: &articleRepository{
218 | db: db,
219 | },
220 | mock: func() {
221 | mock.ExpectBegin()
222 | mock.ExpectQuery(`INSERT INTO "articles" (.+) VALUES (.+)`).
223 | WithArgs("title", "description").
224 | WillReturnError(errors.New("something error"))
225 | mock.ExpectCommit()
226 | },
227 | args: args{
228 | article: models.Article{
229 | Title: "title",
230 | Description: "description",
231 | },
232 | },
233 | want: nil,
234 | wantErr: true,
235 | },
236 | }
237 | for _, tt := range tests {
238 | t.Run(tt.name, func(t *testing.T) {
239 | tt.mock()
240 |
241 | got, err := tt.a.Insert(tt.args.article)
242 | if (err != nil) != tt.wantErr {
243 | t.Errorf("articleRepository.Insert() error = %v, wantErr %v", err, tt.wantErr)
244 | return
245 | }
246 | if !reflect.DeepEqual(got, tt.want) {
247 | t.Errorf("articleRepository.Insert() = %v, want %v", got, tt.want)
248 | }
249 | })
250 | }
251 | }
252 |
253 | func Test_articleRepository_Update(t *testing.T) {
254 | dbConn, mock, _ := sqlmock.New()
255 | dia := postgres.New(postgres.Config{
256 | DriverName: "postgres",
257 | Conn: dbConn,
258 | })
259 |
260 | db, _ := gorm.Open(dia)
261 |
262 | type args struct {
263 | id int64
264 | article models.Article
265 | }
266 | tests := []struct {
267 | name string
268 | a *articleRepository
269 | mock func()
270 | args args
271 | want *models.Article
272 | wantErr bool
273 | }{
274 | {
275 | name: "Success",
276 | a: &articleRepository{
277 | db: db,
278 | },
279 | mock: func() {
280 | mock.ExpectBegin()
281 | mock.ExpectQuery(`UPDATE "articles" SET (.+) WHERE id = (.+)`).
282 | WithArgs("title update", "description update", 1).
283 | WillReturnRows(sqlmock.NewRows([]string{"id", "title", "description"}).
284 | AddRow(1, "title update", "description update"))
285 | mock.ExpectCommit()
286 | },
287 | args: args{
288 | id: 1,
289 | article: models.Article{
290 | Title: "title update",
291 | Description: "description update",
292 | },
293 | },
294 | want: &models.Article{
295 | ID: 1,
296 | Title: "title update",
297 | Description: "description update",
298 | },
299 | wantErr: false,
300 | },
301 | {
302 | name: "Error DB Update",
303 | a: &articleRepository{
304 | db: db,
305 | },
306 | mock: func() {
307 | mock.ExpectBegin()
308 | mock.ExpectQuery(`UPDATE "articles" SET (.+) WHERE id = (.+)`).
309 | WithArgs("title update", "description update", 1).
310 | WillReturnError(errors.New("something error"))
311 | mock.ExpectCommit()
312 | },
313 | args: args{
314 | id: 1,
315 | article: models.Article{
316 | Title: "title update",
317 | Description: "description update",
318 | },
319 | },
320 | want: nil,
321 | wantErr: true,
322 | },
323 | {
324 | name: "Rows Affected Zero",
325 | a: &articleRepository{
326 | db: db,
327 | },
328 | mock: func() {
329 | mock.ExpectBegin()
330 | mock.ExpectExec(`UPDATE "articles" SET (.+) WHERE id = (.+)`).
331 | WithArgs("title update", "description update", 1).
332 | WillReturnResult(sqlmock.NewResult(0, 0))
333 | mock.ExpectCommit()
334 | },
335 | args: args{
336 | id: 1,
337 | article: models.Article{
338 | Title: "title update",
339 | Description: "description update",
340 | },
341 | },
342 | want: nil,
343 | wantErr: true,
344 | },
345 | }
346 | for _, tt := range tests {
347 | t.Run(tt.name, func(t *testing.T) {
348 | tt.mock()
349 |
350 | got, err := tt.a.Update(tt.args.id, tt.args.article)
351 | if (err != nil) != tt.wantErr {
352 | t.Errorf("articleRepository.Update() error = %v, wantErr %v", err, tt.wantErr)
353 | return
354 | }
355 | if !reflect.DeepEqual(got, tt.want) {
356 | t.Errorf("articleRepository.Update() = %v, want %v", got, tt.want)
357 | }
358 | })
359 | }
360 | }
361 |
362 | func Test_articleRepository_Delete(t *testing.T) {
363 | dbConn, mock, _ := sqlmock.New()
364 | dia := postgres.New(postgres.Config{
365 | DriverName: "postgres",
366 | Conn: dbConn,
367 | })
368 |
369 | db, _ := gorm.Open(dia)
370 |
371 | type args struct {
372 | id int64
373 | }
374 | tests := []struct {
375 | name string
376 | a *articleRepository
377 | mock func()
378 | args args
379 | wantErr bool
380 | }{
381 | {
382 | name: "Success",
383 | a: &articleRepository{
384 | db: db,
385 | },
386 | mock: func() {
387 | mock.ExpectBegin()
388 | mock.ExpectExec(regexp.QuoteMeta("DELETE")).
389 | WithArgs(1).
390 | WillReturnResult(sqlmock.NewResult(1, 1))
391 | mock.ExpectCommit()
392 | },
393 | args: args{
394 | id: 1,
395 | },
396 | wantErr: false,
397 | },
398 | {
399 | name: "Error DB Delete",
400 | a: &articleRepository{
401 | db: db,
402 | },
403 | mock: func() {
404 | mock.ExpectBegin()
405 | mock.ExpectExec(regexp.QuoteMeta("DELETE")).
406 | WithArgs(1).
407 | WillReturnError(errors.New("something error"))
408 | mock.ExpectCommit()
409 | },
410 | args: args{
411 | id: 1,
412 | },
413 | wantErr: true,
414 | },
415 | {
416 | name: "Rows Affected Zero",
417 | a: &articleRepository{
418 | db: db,
419 | },
420 | mock: func() {
421 | mock.ExpectBegin()
422 | mock.ExpectExec(regexp.QuoteMeta("DELETE")).
423 | WithArgs(1).
424 | WillReturnResult(sqlmock.NewResult(0, 0))
425 | mock.ExpectCommit()
426 | },
427 | args: args{
428 | id: 1,
429 | },
430 | wantErr: true,
431 | },
432 | }
433 | for _, tt := range tests {
434 | t.Run(tt.name, func(t *testing.T) {
435 | tt.mock()
436 |
437 | if err := tt.a.Delete(tt.args.id); (err != nil) != tt.wantErr {
438 | t.Errorf("articleRepository.Delete() error = %v, wantErr %v", err, tt.wantErr)
439 | }
440 | })
441 | }
442 | }
443 |
--------------------------------------------------------------------------------
/internal/repositories/user_repository.go:
--------------------------------------------------------------------------------
1 | package repositories
2 |
3 | import (
4 | "gorm.io/gorm"
5 |
6 | "github.com/geshtng/go-base-backend/internal/models"
7 | )
8 |
9 | type UserRepository interface {
10 | GetByID(id int64) (*models.User, error)
11 | GetByUsername(username string) (*models.User, error)
12 | Insert(user models.User) (*models.User, error)
13 | }
14 |
15 | type userRepository struct {
16 | db *gorm.DB
17 | }
18 |
19 | type UserRepoConfig struct {
20 | DB *gorm.DB
21 | }
22 |
23 | func NewUserRepository(u *UserRepoConfig) UserRepository {
24 | return &userRepository{
25 | db: u.DB,
26 | }
27 | }
28 |
29 | func (u *userRepository) GetByID(id int64) (*models.User, error) {
30 | var user *models.User
31 |
32 | result := u.db.
33 | Where("id = ?", id).
34 | First(&user)
35 |
36 | if result.Error != nil {
37 | return nil, result.Error
38 | }
39 |
40 | return user, nil
41 | }
42 |
43 | func (u *userRepository) GetByUsername(username string) (*models.User, error) {
44 | var user *models.User
45 |
46 | result := u.db.
47 | Where("username = ?", username).
48 | First(&user)
49 |
50 | if result.Error != nil {
51 | return nil, result.Error
52 | }
53 |
54 | return user, nil
55 | }
56 |
57 | func (u *userRepository) Insert(user models.User) (*models.User, error) {
58 | result := u.db.Create(&user)
59 | if result.Error != nil {
60 | return nil, result.Error
61 | }
62 |
63 | return &user, nil
64 | }
65 |
--------------------------------------------------------------------------------
/internal/repositories/user_repository_test.go:
--------------------------------------------------------------------------------
1 | package repositories
2 |
3 | import (
4 | "errors"
5 | "reflect"
6 | "testing"
7 |
8 | "github.com/DATA-DOG/go-sqlmock"
9 | "github.com/stretchr/testify/assert"
10 | "gorm.io/driver/postgres"
11 | "gorm.io/gorm"
12 |
13 | "github.com/geshtng/go-base-backend/internal/models"
14 | )
15 |
16 | func TestNewUserRepository(t *testing.T) {
17 | dbConn, _, _ := sqlmock.New()
18 | dia := postgres.New(postgres.Config{
19 | DriverName: "postgres",
20 | Conn: dbConn,
21 | })
22 |
23 | db, _ := gorm.Open(dia)
24 | userRepo := NewUserRepository(&UserRepoConfig{DB: db})
25 |
26 | assert.NotNil(t, userRepo)
27 | }
28 |
29 | func Test_userRepository_GetByID(t *testing.T) {
30 | dbConn, mock, _ := sqlmock.New()
31 | dia := postgres.New(postgres.Config{
32 | DriverName: "postgres",
33 | Conn: dbConn,
34 | })
35 |
36 | db, _ := gorm.Open(dia)
37 |
38 | type args struct {
39 | id int64
40 | }
41 | tests := []struct {
42 | name string
43 | u *userRepository
44 | mock func()
45 | args args
46 | want *models.User
47 | wantErr bool
48 | }{
49 | {
50 | name: "Success",
51 | u: &userRepository{
52 | db: db,
53 | },
54 | mock: func() {
55 | mock.ExpectQuery("SELECT (.+)").
56 | WithArgs(1).
57 | WillReturnRows(sqlmock.NewRows([]string{"id", "username"}).
58 | AddRow(1, "username"))
59 | },
60 | args: args{
61 | id: 1,
62 | },
63 | want: &models.User{
64 | ID: 1,
65 | Username: "username",
66 | },
67 | wantErr: false,
68 | },
69 | {
70 | name: "Error DB First",
71 | u: &userRepository{
72 | db: db,
73 | },
74 | mock: func() {
75 | mock.ExpectQuery("SELECT (.+)").
76 | WithArgs(1).
77 | WillReturnError(errors.New("something error"))
78 | },
79 | args: args{
80 | id: 1,
81 | },
82 | want: nil,
83 | wantErr: true,
84 | },
85 | }
86 | for _, tt := range tests {
87 | t.Run(tt.name, func(t *testing.T) {
88 | tt.mock()
89 |
90 | got, err := tt.u.GetByID(tt.args.id)
91 | if (err != nil) != tt.wantErr {
92 | t.Errorf("userRepository.GetByID() error = %v, wantErr %v", err, tt.wantErr)
93 | return
94 | }
95 | if !reflect.DeepEqual(got, tt.want) {
96 | t.Errorf("userRepository.GetByID() = %v, want %v", got, tt.want)
97 | }
98 | })
99 | }
100 | }
101 |
102 | func Test_userRepository_GetByUsername(t *testing.T) {
103 | dbConn, mock, _ := sqlmock.New()
104 | dia := postgres.New(postgres.Config{
105 | DriverName: "postgres",
106 | Conn: dbConn,
107 | })
108 |
109 | db, _ := gorm.Open(dia)
110 |
111 | type args struct {
112 | username string
113 | }
114 | tests := []struct {
115 | name string
116 | u *userRepository
117 | mock func()
118 | args args
119 | want *models.User
120 | wantErr bool
121 | }{
122 | {
123 | name: "Success",
124 | u: &userRepository{
125 | db: db,
126 | },
127 | mock: func() {
128 | mock.ExpectQuery("SELECT (.+)").
129 | WithArgs("username").
130 | WillReturnRows(sqlmock.NewRows([]string{"id", "username"}).
131 | AddRow(1, "username"))
132 | },
133 | args: args{
134 | username: "username",
135 | },
136 | want: &models.User{
137 | ID: 1,
138 | Username: "username",
139 | },
140 | wantErr: false,
141 | },
142 | {
143 | name: "Error DB First",
144 | u: &userRepository{
145 | db: db,
146 | },
147 | mock: func() {
148 | mock.ExpectQuery("SELECT (.+)").
149 | WithArgs("username").
150 | WillReturnError(errors.New("something error"))
151 | },
152 | args: args{
153 | username: "username",
154 | },
155 | want: nil,
156 | wantErr: true,
157 | },
158 | }
159 | for _, tt := range tests {
160 | t.Run(tt.name, func(t *testing.T) {
161 | tt.mock()
162 |
163 | got, err := tt.u.GetByUsername(tt.args.username)
164 | if (err != nil) != tt.wantErr {
165 | t.Errorf("userRepository.GetByUsername() error = %v, wantErr %v", err, tt.wantErr)
166 | return
167 | }
168 | if !reflect.DeepEqual(got, tt.want) {
169 | t.Errorf("userRepository.GetByUsername() = %v, want %v", got, tt.want)
170 | }
171 | })
172 | }
173 | }
174 |
175 | func Test_userRepository_Insert(t *testing.T) {
176 | dbConn, mock, _ := sqlmock.New()
177 | dia := postgres.New(postgres.Config{
178 | DriverName: "postgres",
179 | Conn: dbConn,
180 | })
181 |
182 | db, _ := gorm.Open(dia)
183 |
184 | type args struct {
185 | user models.User
186 | }
187 | tests := []struct {
188 | name string
189 | u *userRepository
190 | mock func()
191 | args args
192 | want *models.User
193 | wantErr bool
194 | }{
195 | {
196 | name: "Success",
197 | u: &userRepository{
198 | db: db,
199 | },
200 | mock: func() {
201 | mock.ExpectBegin()
202 | mock.ExpectQuery(`INSERT INTO "users" (.+) VALUES (.+)`).
203 | WithArgs("username", "password").
204 | WillReturnRows(sqlmock.NewRows([]string{"id", "username", "password"}).
205 | AddRow(1, "username", "password"))
206 | mock.ExpectCommit()
207 | },
208 | args: args{
209 | user: models.User{
210 | Username: "username",
211 | Password: "password",
212 | },
213 | },
214 | want: &models.User{
215 | ID: 1,
216 | Username: "username",
217 | Password: "password",
218 | },
219 | wantErr: false,
220 | },
221 | {
222 | name: "Error DB Create",
223 | u: &userRepository{
224 | db: db,
225 | },
226 | mock: func() {
227 | mock.ExpectBegin()
228 | mock.ExpectQuery(`INSERT INTO "users" (.+) VALUES (.+)`).
229 | WithArgs("username", "password").
230 | WillReturnError(errors.New("something error"))
231 | mock.ExpectCommit()
232 | },
233 | args: args{
234 | user: models.User{
235 | Username: "username",
236 | Password: "password",
237 | },
238 | },
239 | want: nil,
240 | wantErr: true,
241 | },
242 | }
243 | for _, tt := range tests {
244 | t.Run(tt.name, func(t *testing.T) {
245 | tt.mock()
246 |
247 | got, err := tt.u.Insert(tt.args.user)
248 | if (err != nil) != tt.wantErr {
249 | t.Errorf("userRepository.Insert() error = %v, wantErr %v", err, tt.wantErr)
250 | return
251 | }
252 | if !reflect.DeepEqual(got, tt.want) {
253 | t.Errorf("userRepository.Insert() = %v, want %v", got, tt.want)
254 | }
255 | })
256 | }
257 | }
258 |
--------------------------------------------------------------------------------
/internal/routes/routes.go:
--------------------------------------------------------------------------------
1 | package routes
2 |
3 | import (
4 | "github.com/gin-gonic/gin"
5 |
6 | "github.com/geshtng/go-base-backend/internal/handlers"
7 | "github.com/geshtng/go-base-backend/internal/middlewares"
8 | )
9 |
10 | func InitAllRoutes(r *gin.Engine, h *handlers.Handler) {
11 | // Articles
12 | r.GET("/articles", h.GetAllArticlesHandler)
13 | r.GET("/articles/:id", h.GetArticleByIDHandler)
14 | r.POST("/articles", h.CreateArticleHandler)
15 | r.PUT("/articles/:id", h.UpdateArticleHandler)
16 | r.DELETE("/articles/:id", h.DeleteArticleHandler)
17 |
18 | // Auth
19 | r.POST("/login", h.LoginHandler)
20 | r.POST("/register", h.RegisterHandler)
21 |
22 | // API endpoints below will use authorization
23 | r.Use(middlewares.AuthorizeJWT)
24 |
25 | r.GET("/profiles", h.ShowProfileHandler)
26 | }
27 |
--------------------------------------------------------------------------------
/internal/services/article_service.go:
--------------------------------------------------------------------------------
1 | package services
2 |
3 | import (
4 | "github.com/geshtng/go-base-backend/internal/models"
5 | r "github.com/geshtng/go-base-backend/internal/repositories"
6 | )
7 |
8 | type ArticleService interface {
9 | GetAll() ([]*models.Article, error)
10 | GetByID(id int64) (*models.Article, error)
11 | Create(article models.Article) (*models.Article, error)
12 | Update(id int64, article models.Article) (*models.Article, error)
13 | Delete(id int64) error
14 | }
15 |
16 | type articleService struct {
17 | articleRepository r.ArticleRepository
18 | }
19 |
20 | type ArticleServiceConfig struct {
21 | ArticleRepository r.ArticleRepository
22 | }
23 |
24 | func NewArticleService(a *ArticleServiceConfig) ArticleService {
25 | return &articleService{
26 | articleRepository: a.ArticleRepository,
27 | }
28 | }
29 |
30 | func (a *articleService) GetAll() ([]*models.Article, error) {
31 | result, err := a.articleRepository.GetAll()
32 | if err != nil {
33 | return nil, err
34 | }
35 |
36 | return result, nil
37 | }
38 |
39 | func (a *articleService) GetByID(id int64) (*models.Article, error) {
40 | result, err := a.articleRepository.GetByID(id)
41 | if err != nil {
42 | return nil, err
43 | }
44 |
45 | return result, nil
46 | }
47 |
48 | func (a *articleService) Create(article models.Article) (*models.Article, error) {
49 | result, err := a.articleRepository.Insert(article)
50 | if err != nil {
51 | return nil, err
52 | }
53 |
54 | return result, nil
55 | }
56 |
57 | func (a *articleService) Update(id int64, article models.Article) (*models.Article, error) {
58 | result, err := a.articleRepository.Update(id, article)
59 | if err != nil {
60 | return nil, err
61 | }
62 |
63 | return result, nil
64 | }
65 |
66 | func (a *articleService) Delete(id int64) error {
67 | err := a.articleRepository.Delete(id)
68 | if err != nil {
69 | return err
70 | }
71 |
72 | return nil
73 | }
74 |
--------------------------------------------------------------------------------
/internal/services/article_service_test.go:
--------------------------------------------------------------------------------
1 | package services
2 |
3 | import (
4 | "errors"
5 | "reflect"
6 | "testing"
7 |
8 | "github.com/stretchr/testify/assert"
9 | "github.com/stretchr/testify/mock"
10 |
11 | rmock "github.com/geshtng/go-base-backend/internal/mocks/repositories"
12 | "github.com/geshtng/go-base-backend/internal/models"
13 | )
14 |
15 | func TestNewArticleService(t *testing.T) {
16 | articleRepo := rmock.NewArticleRepository(t)
17 | articleService := NewArticleService(&ArticleServiceConfig{
18 | ArticleRepository: articleRepo,
19 | })
20 |
21 | assert.NotNil(t, articleService)
22 | }
23 |
24 | func Test_articleService_GetAll(t *testing.T) {
25 | t.Run("Success", func(t *testing.T) {
26 | tt := struct {
27 | want []*models.Article
28 | wantErr bool
29 | }{
30 | want: []*models.Article{
31 | {
32 | ID: 1,
33 | Title: "title",
34 | Description: "description",
35 | },
36 | },
37 | wantErr: false,
38 | }
39 |
40 | articleRepo := rmock.NewArticleRepository(t)
41 | s := &articleService{
42 | articleRepository: articleRepo,
43 | }
44 |
45 | result := []*models.Article{
46 | {
47 | ID: 1,
48 | Title: "title",
49 | Description: "description",
50 | },
51 | }
52 |
53 | articleRepo.On("GetAll").Return(result, nil)
54 |
55 | got, err := s.GetAll()
56 | if (err != nil) != tt.wantErr {
57 | t.Errorf("articleService.GetAll() error = %v, wantErr %v", err, tt.wantErr)
58 | return
59 | }
60 |
61 | if !reflect.DeepEqual(got, tt.want) {
62 | t.Errorf("articleService.GetAll() = %v, want %v", got, tt.want)
63 | }
64 | })
65 |
66 | t.Run("Error Repository.GetAll", func(t *testing.T) {
67 | tt := struct {
68 | want []*models.Article
69 | wantErr bool
70 | }{
71 | want: nil,
72 | wantErr: true,
73 | }
74 |
75 | articleRepo := rmock.NewArticleRepository(t)
76 | s := &articleService{
77 | articleRepository: articleRepo,
78 | }
79 |
80 | articleRepo.On("GetAll").Return(nil, errors.New("something error"))
81 |
82 | got, err := s.GetAll()
83 | if (err != nil) != tt.wantErr {
84 | t.Errorf("articleService.GetAll() error = %v, wantErr %v", err, tt.wantErr)
85 | return
86 | }
87 |
88 | if !reflect.DeepEqual(got, tt.want) {
89 | t.Errorf("articleService.GetAll() = %v, want %v", got, tt.want)
90 | }
91 | })
92 | }
93 |
94 | func Test_articleService_GetByID(t *testing.T) {
95 | t.Run("Success", func(t *testing.T) {
96 | type args struct {
97 | id int64
98 | }
99 | tt := struct {
100 | args args
101 | want *models.Article
102 | wantErr bool
103 | }{
104 | args: args{
105 | id: 1,
106 | },
107 | want: &models.Article{
108 | ID: 1,
109 | Title: "title",
110 | Description: "description",
111 | },
112 | wantErr: false,
113 | }
114 |
115 | articleRepo := rmock.NewArticleRepository(t)
116 | s := &articleService{
117 | articleRepository: articleRepo,
118 | }
119 |
120 | result := &models.Article{
121 | ID: 1,
122 | Title: "title",
123 | Description: "description",
124 | }
125 |
126 | articleRepo.On("GetByID", mock.Anything).Return(result, nil)
127 |
128 | got, err := s.GetByID(tt.args.id)
129 | if (err != nil) != tt.wantErr {
130 | t.Errorf("articleService.GetAll() error = %v, wantErr %v", err, tt.wantErr)
131 | return
132 | }
133 |
134 | if !reflect.DeepEqual(got, tt.want) {
135 | t.Errorf("articleService.GetAll() = %v, want %v", got, tt.want)
136 | }
137 | })
138 |
139 | t.Run("Error Repository.GetByID", func(t *testing.T) {
140 | type args struct {
141 | id int64
142 | }
143 | tt := struct {
144 | args args
145 | want *models.Article
146 | wantErr bool
147 | }{
148 | args: args{
149 | id: 1,
150 | },
151 | want: nil,
152 | wantErr: true,
153 | }
154 |
155 | articleRepo := rmock.NewArticleRepository(t)
156 | s := &articleService{
157 | articleRepository: articleRepo,
158 | }
159 |
160 | articleRepo.On("GetByID", mock.Anything).Return(nil, errors.New("something error"))
161 |
162 | got, err := s.GetByID(tt.args.id)
163 | if (err != nil) != tt.wantErr {
164 | t.Errorf("articleService.GetAll() error = %v, wantErr %v", err, tt.wantErr)
165 | return
166 | }
167 |
168 | if !reflect.DeepEqual(got, tt.want) {
169 | t.Errorf("articleService.GetAll() = %v, want %v", got, tt.want)
170 | }
171 | })
172 | }
173 |
174 | func Test_articleService_Create(t *testing.T) {
175 | t.Run("Success", func(t *testing.T) {
176 | type args struct {
177 | menu models.Article
178 | }
179 | tt := struct {
180 | args args
181 | want *models.Article
182 | wantErr bool
183 | }{
184 | args: args{
185 | menu: models.Article{
186 | Title: "title",
187 | Description: "description",
188 | },
189 | },
190 | want: &models.Article{
191 | ID: 1,
192 | Title: "title",
193 | Description: "description",
194 | },
195 | wantErr: false,
196 | }
197 |
198 | articleRepo := rmock.NewArticleRepository(t)
199 | s := &articleService{
200 | articleRepository: articleRepo,
201 | }
202 |
203 | result := &models.Article{
204 | ID: 1,
205 | Title: "title",
206 | Description: "description",
207 | }
208 |
209 | articleRepo.On("Insert", mock.Anything).Return(result, nil)
210 |
211 | got, err := s.Create(tt.args.menu)
212 | if (err != nil) != tt.wantErr {
213 | t.Errorf("articleService.GetAll() error = %v, wantErr %v", err, tt.wantErr)
214 | return
215 | }
216 |
217 | if !reflect.DeepEqual(got, tt.want) {
218 | t.Errorf("articleService.GetAll() = %v, want %v", got, tt.want)
219 | }
220 | })
221 |
222 | t.Run("Error Repository.Insert", func(t *testing.T) {
223 | type args struct {
224 | menu models.Article
225 | }
226 | tt := struct {
227 | args args
228 | want *models.Article
229 | wantErr bool
230 | }{
231 | args: args{
232 | menu: models.Article{
233 | Title: "title",
234 | Description: "description",
235 | },
236 | },
237 | want: nil,
238 | wantErr: true,
239 | }
240 |
241 | articleRepo := rmock.NewArticleRepository(t)
242 | s := &articleService{
243 | articleRepository: articleRepo,
244 | }
245 |
246 | articleRepo.On("Insert", mock.Anything).Return(nil, errors.New("something error"))
247 |
248 | got, err := s.Create(tt.args.menu)
249 | if (err != nil) != tt.wantErr {
250 | t.Errorf("articleService.GetAll() error = %v, wantErr %v", err, tt.wantErr)
251 | return
252 | }
253 |
254 | if !reflect.DeepEqual(got, tt.want) {
255 | t.Errorf("articleService.GetAll() = %v, want %v", got, tt.want)
256 | }
257 | })
258 | }
259 |
260 | func Test_articleService_Update(t *testing.T) {
261 | t.Run("Success", func(t *testing.T) {
262 | type args struct {
263 | id int64
264 | menu models.Article
265 | }
266 | tt := struct {
267 | args args
268 | want *models.Article
269 | wantErr bool
270 | }{
271 | args: args{
272 | id: 1,
273 | menu: models.Article{
274 | Title: "title update",
275 | Description: "description update",
276 | },
277 | },
278 | want: &models.Article{
279 | ID: 1,
280 | Title: "title update",
281 | Description: "description update",
282 | },
283 | wantErr: false,
284 | }
285 |
286 | articleRepo := rmock.NewArticleRepository(t)
287 | s := &articleService{
288 | articleRepository: articleRepo,
289 | }
290 |
291 | result := &models.Article{
292 | ID: 1,
293 | Title: "title update",
294 | Description: "description update",
295 | }
296 |
297 | articleRepo.On("Update", mock.Anything, mock.Anything).Return(result, nil)
298 |
299 | got, err := s.Update(tt.args.id, tt.args.menu)
300 | if (err != nil) != tt.wantErr {
301 | t.Errorf("articleService.GetAll() error = %v, wantErr %v", err, tt.wantErr)
302 | return
303 | }
304 |
305 | if !reflect.DeepEqual(got, tt.want) {
306 | t.Errorf("articleService.GetAll() = %v, want %v", got, tt.want)
307 | }
308 | })
309 |
310 | t.Run("Error Repository.Update", func(t *testing.T) {
311 | type args struct {
312 | id int64
313 | menu models.Article
314 | }
315 | tt := struct {
316 | args args
317 | want *models.Article
318 | wantErr bool
319 | }{
320 | args: args{
321 | id: 1,
322 | menu: models.Article{
323 | Title: "title update",
324 | Description: "description update",
325 | },
326 | },
327 | want: nil,
328 | wantErr: true,
329 | }
330 |
331 | articleRepo := rmock.NewArticleRepository(t)
332 | s := &articleService{
333 | articleRepository: articleRepo,
334 | }
335 |
336 | articleRepo.On("Update", mock.Anything, mock.Anything).Return(nil, errors.New("something error"))
337 |
338 | got, err := s.Update(tt.args.id, tt.args.menu)
339 | if (err != nil) != tt.wantErr {
340 | t.Errorf("articleService.GetAll() error = %v, wantErr %v", err, tt.wantErr)
341 | return
342 | }
343 |
344 | if !reflect.DeepEqual(got, tt.want) {
345 | t.Errorf("articleService.GetAll() = %v, want %v", got, tt.want)
346 | }
347 | })
348 | }
349 |
350 | func Test_articleService_Delete(t *testing.T) {
351 | t.Run("Success", func(t *testing.T) {
352 | type args struct {
353 | id int64
354 | }
355 | tt := struct {
356 | args args
357 | wantErr bool
358 | }{
359 | args: args{
360 | id: 1,
361 | },
362 | wantErr: false,
363 | }
364 |
365 | articleRepo := rmock.NewArticleRepository(t)
366 | s := &articleService{
367 | articleRepository: articleRepo,
368 | }
369 |
370 | articleRepo.On("Delete", mock.Anything).Return(nil)
371 |
372 | if err := s.Delete(tt.args.id); (err != nil) != tt.wantErr {
373 | t.Errorf("articleService.GetAll() error = %v, wantErr %v", err, tt.wantErr)
374 | return
375 | }
376 | })
377 |
378 | t.Run("Error Repository.Delete", func(t *testing.T) {
379 | type args struct {
380 | id int64
381 | }
382 | tt := struct {
383 | args args
384 | wantErr bool
385 | }{
386 | args: args{
387 | id: 1,
388 | },
389 | wantErr: true,
390 | }
391 |
392 | articleRepo := rmock.NewArticleRepository(t)
393 | s := &articleService{
394 | articleRepository: articleRepo,
395 | }
396 |
397 | articleRepo.On("Delete", mock.Anything).Return(errors.New("something error"))
398 |
399 | if err := s.Delete(tt.args.id); (err != nil) != tt.wantErr {
400 | t.Errorf("articleService.GetAll() error = %v, wantErr %v", err, tt.wantErr)
401 | return
402 | }
403 | })
404 | }
405 |
--------------------------------------------------------------------------------
/internal/services/auth_service.go:
--------------------------------------------------------------------------------
1 | package services
2 |
3 | import (
4 | "gorm.io/gorm"
5 |
6 | "github.com/geshtng/go-base-backend/internal/helpers"
7 | "github.com/geshtng/go-base-backend/internal/models"
8 | r "github.com/geshtng/go-base-backend/internal/repositories"
9 | )
10 |
11 | type AuthService interface {
12 | Register(auth models.User) (*models.User, *string, error)
13 | Login(username, password string) (*models.User, *string, error)
14 | }
15 |
16 | type authService struct {
17 | userRepository r.UserRepository
18 | }
19 |
20 | type AuthServiceConfig struct {
21 | UserRepository r.UserRepository
22 | }
23 |
24 | func NewAuthService(a *AuthServiceConfig) AuthService {
25 | return &authService{
26 | userRepository: a.UserRepository,
27 | }
28 | }
29 |
30 | func (a *authService) Register(auth models.User) (*models.User, *string, error) {
31 | encryptedPassword, err := helpers.Encrypt(auth.Password)
32 | if err != nil {
33 | return nil, nil, err
34 | }
35 |
36 | auth.Password = encryptedPassword
37 |
38 | newAuth, err := a.userRepository.Insert(auth)
39 | if err != nil {
40 | return nil, nil, err
41 | }
42 |
43 | dataToJwt := models.User{
44 | ID: newAuth.ID,
45 | Username: newAuth.Username,
46 | }
47 |
48 | token, err := helpers.GenerateJwtToken(dataToJwt)
49 | if err != nil {
50 | return nil, nil, err
51 | }
52 |
53 | return newAuth, token, nil
54 | }
55 |
56 | func (a *authService) Login(username, password string) (*models.User, *string, error) {
57 | auth, err := a.userRepository.GetByUsername(username)
58 | if err != nil {
59 | return nil, nil, err
60 | }
61 |
62 | if !helpers.CompareEncrypt(password, auth.Password) {
63 | return nil, nil, gorm.ErrRecordNotFound
64 | }
65 |
66 | dataToJwt := models.User{
67 | ID: auth.ID,
68 | Username: auth.Username,
69 | }
70 |
71 | token, err := helpers.GenerateJwtToken(dataToJwt)
72 | if err != nil {
73 | return nil, nil, err
74 | }
75 |
76 | return auth, token, nil
77 | }
78 |
--------------------------------------------------------------------------------
/internal/services/auth_service_test.go:
--------------------------------------------------------------------------------
1 | package services
2 |
3 | import (
4 | "errors"
5 | "os"
6 | "path"
7 | "reflect"
8 | "runtime"
9 | "testing"
10 |
11 | "github.com/stretchr/testify/assert"
12 | "github.com/stretchr/testify/mock"
13 |
14 | "github.com/geshtng/go-base-backend/internal/helpers"
15 | rmock "github.com/geshtng/go-base-backend/internal/mocks/repositories"
16 | "github.com/geshtng/go-base-backend/internal/models"
17 | )
18 |
19 | func TestNewAuthService(t *testing.T) {
20 | userRepo := rmock.NewUserRepository(t)
21 | authService := NewAuthService(&AuthServiceConfig{
22 | UserRepository: userRepo,
23 | })
24 |
25 | assert.NotNil(t, authService)
26 | }
27 |
28 | func Test_authService_Register(t *testing.T) {
29 | t.Run("Success", func(t *testing.T) {
30 | _, filename, _, _ := runtime.Caller(0)
31 | dir := path.Join(path.Dir(filename), "../../")
32 | err := os.Chdir(dir)
33 | if err != nil {
34 | panic(err)
35 | }
36 |
37 | encryptedPassword, _ := helpers.Encrypt("password")
38 |
39 | type args struct {
40 | auth models.User
41 | }
42 | test := struct {
43 | args args
44 | want *models.User
45 | wantErr bool
46 | }{
47 | args: args{
48 | auth: models.User{
49 | Username: "username",
50 | Password: "password",
51 | },
52 | },
53 | want: &models.User{
54 | Username: "username",
55 | Password: encryptedPassword,
56 | },
57 | wantErr: false,
58 | }
59 |
60 | userRepo := rmock.NewUserRepository(t)
61 | s := &authService{
62 | userRepository: userRepo,
63 | }
64 |
65 | expectedUser := models.User{
66 | Username: "username",
67 | Password: encryptedPassword,
68 | }
69 |
70 | userRepo.On("Insert", mock.Anything).Return(&expectedUser, nil)
71 |
72 | got, _, err := s.Register(test.args.auth)
73 | if (err != nil) != test.wantErr {
74 | t.Errorf("authService.Register() error = %v, wantErr %v", err, test.wantErr)
75 | return
76 | }
77 |
78 | if !reflect.DeepEqual(got, test.want) {
79 | t.Errorf("authService.Register() = %v, want %v", got, test.want)
80 | }
81 | })
82 |
83 | t.Run("Error Repository.Insert", func(t *testing.T) {
84 | _, filename, _, _ := runtime.Caller(0)
85 | dir := path.Join(path.Dir(filename), "../../")
86 | err := os.Chdir(dir)
87 | if err != nil {
88 | panic(err)
89 | }
90 |
91 | type args struct {
92 | auth models.User
93 | }
94 | test := struct {
95 | args args
96 | want *models.User
97 | wantErr bool
98 | }{
99 | args: args{
100 | auth: models.User{
101 | Username: "username",
102 | Password: "password",
103 | },
104 | },
105 | want: nil,
106 | wantErr: true,
107 | }
108 |
109 | userRepo := rmock.NewUserRepository(t)
110 | s := &authService{
111 | userRepository: userRepo,
112 | }
113 |
114 | userRepo.On("Insert", mock.Anything).Return(nil, errors.New("something error"))
115 |
116 | got, _, err := s.Register(test.args.auth)
117 | if (err != nil) != test.wantErr {
118 | t.Errorf("authService.Register() error = %v, wantErr %v", err, test.wantErr)
119 | return
120 | }
121 |
122 | if !reflect.DeepEqual(got, test.want) {
123 | t.Errorf("authService.Register() = %v, want %v", got, test.want)
124 | }
125 | })
126 | }
127 |
128 | func Test_authService_Login(t *testing.T) {
129 | t.Run("Success", func(t *testing.T) {
130 | _, filename, _, _ := runtime.Caller(0)
131 | dir := path.Join(path.Dir(filename), "../../")
132 | err := os.Chdir(dir)
133 | if err != nil {
134 | panic(err)
135 | }
136 |
137 | encryptedPassword, _ := helpers.Encrypt("password")
138 |
139 | type args struct {
140 | username string
141 | password string
142 | }
143 | test := struct {
144 | args args
145 | want *models.User
146 | wantErr bool
147 | }{
148 | args: args{
149 | username: "username",
150 | password: "password",
151 | },
152 | want: &models.User{
153 | Username: "username",
154 | Password: encryptedPassword,
155 | },
156 | wantErr: false,
157 | }
158 |
159 | userRepo := rmock.NewUserRepository(t)
160 | s := &authService{
161 | userRepository: userRepo,
162 | }
163 |
164 | expectedUser := models.User{
165 | Username: "username",
166 | Password: encryptedPassword,
167 | }
168 |
169 | userRepo.On("GetByUsername", mock.Anything).Return(&expectedUser, nil)
170 |
171 | got, _, err := s.Login(test.args.username, test.args.password)
172 | if (err != nil) != test.wantErr {
173 | t.Errorf("authService.Register() error = %v, wantErr %v", err, test.wantErr)
174 | return
175 | }
176 |
177 | if !reflect.DeepEqual(got, test.want) {
178 | t.Errorf("authService.Register() = %v, want %v", got, test.want)
179 | }
180 | })
181 |
182 | t.Run("Error Repository.GetByUsername", func(t *testing.T) {
183 | _, filename, _, _ := runtime.Caller(0)
184 | dir := path.Join(path.Dir(filename), "../../")
185 | err := os.Chdir(dir)
186 | if err != nil {
187 | panic(err)
188 | }
189 |
190 | type args struct {
191 | username string
192 | password string
193 | }
194 | test := struct {
195 | args args
196 | want *models.User
197 | wantErr bool
198 | }{
199 | args: args{
200 | username: "username",
201 | password: "password",
202 | },
203 | want: nil,
204 | wantErr: true,
205 | }
206 |
207 | userRepo := rmock.NewUserRepository(t)
208 | s := &authService{
209 | userRepository: userRepo,
210 | }
211 |
212 | userRepo.On("GetByUsername", mock.Anything).Return(nil, errors.New("something error"))
213 |
214 | got, _, err := s.Login(test.args.username, test.args.password)
215 | if (err != nil) != test.wantErr {
216 | t.Errorf("authService.Register() error = %v, wantErr %v", err, test.wantErr)
217 | return
218 | }
219 |
220 | if !reflect.DeepEqual(got, test.want) {
221 | t.Errorf("authService.Register() = %v, want %v", got, test.want)
222 | }
223 | })
224 |
225 | t.Run("Error CompareEncrypt", func(t *testing.T) {
226 | _, filename, _, _ := runtime.Caller(0)
227 | dir := path.Join(path.Dir(filename), "../../")
228 | err := os.Chdir(dir)
229 | if err != nil {
230 | panic(err)
231 | }
232 |
233 | encryptedPassword, _ := helpers.Encrypt("password")
234 |
235 | type args struct {
236 | username string
237 | password string
238 | }
239 | test := struct {
240 | args args
241 | want *models.User
242 | wantErr bool
243 | }{
244 | args: args{
245 | username: "username",
246 | password: "wrongpassword",
247 | },
248 | want: nil,
249 | wantErr: true,
250 | }
251 |
252 | userRepo := rmock.NewUserRepository(t)
253 | s := &authService{
254 | userRepository: userRepo,
255 | }
256 |
257 | expectedUser := models.User{
258 | Username: "username",
259 | Password: encryptedPassword,
260 | }
261 |
262 | userRepo.On("GetByUsername", mock.Anything).Return(&expectedUser, nil)
263 |
264 | got, _, err := s.Login(test.args.username, test.args.password)
265 | if (err != nil) != test.wantErr {
266 | t.Errorf("authService.Register() error = %v, wantErr %v", err, test.wantErr)
267 | return
268 | }
269 |
270 | if !reflect.DeepEqual(got, test.want) {
271 | t.Errorf("authService.Register() = %v, want %v", got, test.want)
272 | }
273 | })
274 | }
275 |
--------------------------------------------------------------------------------
/internal/services/user_service.go:
--------------------------------------------------------------------------------
1 | package services
2 |
3 | import (
4 | "github.com/geshtng/go-base-backend/internal/models"
5 | r "github.com/geshtng/go-base-backend/internal/repositories"
6 | )
7 |
8 | type UserService interface {
9 | GetByID(id int64) (*models.User, error)
10 | }
11 |
12 | type userService struct {
13 | userRepository r.UserRepository
14 | }
15 |
16 | type UserServiceConfig struct {
17 | UserRepository r.UserRepository
18 | }
19 |
20 | func NewUserService(u *UserServiceConfig) UserService {
21 | return &userService{
22 | userRepository: u.UserRepository,
23 | }
24 | }
25 |
26 | func (u *userService) GetByID(id int64) (*models.User, error) {
27 | result, err := u.userRepository.GetByID(id)
28 | if err != nil {
29 | return nil, err
30 | }
31 |
32 | return result, nil
33 | }
34 |
--------------------------------------------------------------------------------
/internal/services/user_service_test.go:
--------------------------------------------------------------------------------
1 | package services
2 |
3 | import (
4 | "errors"
5 | "reflect"
6 | "testing"
7 |
8 | "github.com/stretchr/testify/assert"
9 | "github.com/stretchr/testify/mock"
10 |
11 | rmock "github.com/geshtng/go-base-backend/internal/mocks/repositories"
12 | "github.com/geshtng/go-base-backend/internal/models"
13 | )
14 |
15 | func TestNewUserService(t *testing.T) {
16 | userRepo := rmock.NewUserRepository(t)
17 | userService := NewUserService(&UserServiceConfig{
18 | UserRepository: userRepo,
19 | })
20 |
21 | assert.NotNil(t, userService)
22 | }
23 |
24 | func Test_userService_GetByID(t *testing.T) {
25 | t.Run("Success", func(t *testing.T) {
26 | type args struct {
27 | id int64
28 | }
29 | tt := struct {
30 | args args
31 | want *models.User
32 | wantErr bool
33 | }{
34 | args: args{
35 | id: 1,
36 | },
37 | want: &models.User{
38 | ID: 1,
39 | Username: "username",
40 | },
41 | wantErr: false,
42 | }
43 |
44 | userRepo := rmock.NewUserRepository(t)
45 | s := &userService{
46 | userRepository: userRepo,
47 | }
48 |
49 | result := &models.User{
50 | ID: 1,
51 | Username: "username",
52 | }
53 |
54 | userRepo.On("GetByID", mock.Anything).Return(result, nil)
55 |
56 | got, err := s.GetByID(tt.args.id)
57 | if (err != nil) != tt.wantErr {
58 | t.Errorf("articleService.GetAll() error = %v, wantErr %v", err, tt.wantErr)
59 | return
60 | }
61 |
62 | if !reflect.DeepEqual(got, tt.want) {
63 | t.Errorf("articleService.GetAll() = %v, want %v", got, tt.want)
64 | }
65 | })
66 |
67 | t.Run("Error Repository.GetByID", func(t *testing.T) {
68 | type args struct {
69 | id int64
70 | }
71 | tt := struct {
72 | args args
73 | want *models.User
74 | wantErr bool
75 | }{
76 | args: args{
77 | id: 1,
78 | },
79 | want: nil,
80 | wantErr: true,
81 | }
82 |
83 | userRepo := rmock.NewUserRepository(t)
84 | s := &userService{
85 | userRepository: userRepo,
86 | }
87 |
88 | userRepo.On("GetByID", mock.Anything).Return(nil, errors.New("something error"))
89 |
90 | got, err := s.GetByID(tt.args.id)
91 | if (err != nil) != tt.wantErr {
92 | t.Errorf("articleService.GetAll() error = %v, wantErr %v", err, tt.wantErr)
93 | return
94 | }
95 |
96 | if !reflect.DeepEqual(got, tt.want) {
97 | t.Errorf("articleService.GetAll() = %v, want %v", got, tt.want)
98 | }
99 | })
100 | }
101 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/gin-gonic/gin"
5 |
6 | "github.com/geshtng/go-base-backend/config"
7 | "github.com/geshtng/go-base-backend/db"
8 | "github.com/geshtng/go-base-backend/internal/handlers"
9 | "github.com/geshtng/go-base-backend/internal/routes"
10 | )
11 |
12 | func main() {
13 | port := config.InitConfigPort()
14 |
15 | r := gin.Default()
16 |
17 | err := db.Connect()
18 | if err != nil {
19 | panic(err)
20 | }
21 |
22 | h := handlers.InitAllHandlers()
23 |
24 | routes.InitAllRoutes(r, h)
25 |
26 | err = r.Run(port)
27 | if err != nil {
28 | panic(err)
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/makefile:
--------------------------------------------------------------------------------
1 | xrun:
2 | nodemon --exec go run main.go --signal SIGTERM
3 |
4 | run:
5 | go run .
--------------------------------------------------------------------------------