├── .gitignore
├── LICENSE
├── Makefile
├── README.md
├── assets
├── banner.png
├── github_star_button.png
└── use_template_button.png
├── cmd
└── gin-restapi-template
│ └── main.go
├── go.mod
├── go.sum
├── internal
├── bootstrap
│ ├── bootstrap.go
│ └── routes.go
└── middlewares
│ ├── auth_middleware.go
│ ├── cors_middleware.go
│ ├── errors_middleware.go
│ └── middlewares.go
├── pkg
├── auth
│ ├── auth.go
│ ├── auth_controller.go
│ ├── auth_controller_test.go
│ ├── auth_routes.go
│ └── auth_service.go
├── common
│ ├── hasher.go
│ ├── hasher_test.go
│ ├── validation.go
│ └── validation_test.go
├── interfaces
│ └── auth_service_interface.go
├── lib
│ ├── database.go
│ ├── lib.go
│ ├── logger.go
│ └── router.go
└── users
│ ├── users.go
│ ├── users_controller.go
│ ├── users_model.go
│ ├── users_repository.go
│ ├── users_routes.go
│ └── users_service.go
├── sql
├── create_query_functions.sql
└── create_users_table.sql
└── test
├── README.md
├── integration
└── signup_test.go
└── mocks
├── auth_service_mock.go
├── logger_mock.go
└── users_service_mock.go
/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by https://www.toptal.com/developers/gitignore/api/go,macos,dotenv
2 | # Edit at https://www.toptal.com/developers/gitignore?templates=go,macos,dotenv
3 |
4 | bin/
5 |
6 | ### Dotenv ###
7 | configs/
8 |
9 | ### Go ###
10 | # If you prefer the allow list template instead of the deny list, see community template:
11 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
12 | #
13 | # Binaries for programs and plugins
14 | *.exe
15 | *.exe~
16 | *.dll
17 | *.so
18 | *.dylib
19 |
20 | # Test binary, built with `go test -c`
21 | *.test
22 |
23 | # Output of the go coverage tool, specifically when used with LiteIDE
24 | *.out
25 |
26 | # Dependency directories (remove the comment below to include it)
27 | # vendor/
28 |
29 | # Go workspace file
30 | go.work
31 |
32 | ### macOS ###
33 | # General
34 | .DS_Store
35 | .AppleDouble
36 | .LSOverride
37 |
38 | # Icon must end with two \r
39 | Icon
40 |
41 |
42 | # Thumbnails
43 | ._*
44 |
45 | # Files that might appear in the root of a volume
46 | .DocumentRevisions-V100
47 | .fseventsd
48 | .Spotlight-V100
49 | .TemporaryItems
50 | .Trashes
51 | .VolumeIcon.icns
52 | .com.apple.timemachine.donotpresent
53 |
54 | # Directories potentially created on remote AFP share
55 | .AppleDB
56 | .AppleDesktop
57 | Network Trash Folder
58 | Temporary Items
59 | .apdisk
60 |
61 | ### macOS Patch ###
62 | # iCloud generated files
63 | *.icloud
64 |
65 | # End of https://www.toptal.com/developers/gitignore/api/go,macos,dotenv
66 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Alejandro Modroño Vara
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | build:
2 | go build -o bin/api cmd/gin-restapi-template/main.go
3 |
4 | run:
5 | go run cmd/gin-restapi-template/main.go
6 |
7 | .PHONY: test
8 | test:
9 | go test $(if $(VERBOSE),-v,) ./pkg/...
10 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | 
4 |
5 |

6 |
7 |
8 |
9 | ## Index
10 |
11 |
12 | [overvw]: #project-overview-
13 | [motiv]: #motivation-
14 | [gs]: #get-started-
15 | [td]: #todo-list-
16 | [sql]: #custom-database-queries
17 |
18 |
19 | - [Project Overview 📋][overvw]
20 | - [Motivation 💪][motiv]
21 | - [Get Started 🏃♂️][gs]
22 | - [TODO list 📝][td]
23 | - [Custom database queries][sql]
24 |
25 | ## Project Overview 📋
26 | `alexmodrono/gin-restapi-template` is a comprehensive and well-structured starting point for developing RESTful APIs using the Gin framework. This template aims to streamline the initial setup and provide a foundation for building robust and scalable APIs with a clean architecture.
27 |
28 | The main key features include, but are not limited to:
29 |
30 | - 🫧 **Clean Architecture**: The template embraces a modular and organized structure based on the principles of Clean Architecture. It leverages the power of `go-fx` for robust dependency injection, enhancing maintainability, scalability, and code reusability.
31 |
32 | - 🧭 **Efficient Routing**: Utilizing the powerful routing capabilities of Gin, the template enables easy management of API endpoints, middleware, and request handling.
33 |
34 | - 👷 **Middleware Integration**: Built-in support for middleware such as authentication, logging, CORS, and more, allowing you to enhance your API's functionality and security with ease.
35 |
36 | - ❗️ **Error Handling**: The template includes a comprehensive error handling mechanism, ensuring consistent and informative error responses for better client interaction and debugging.
37 |
38 | - 💾 **Database Integration**: By leveraging the `pgx` driver, the template enables direct communication with PostgreSQL databases without the additional overhead and abstraction provided by an ORM. This approach provides more control and fine-grained optimization opportunities, allowing you to tailor the database interactions to the specific needs of your application.
39 |
40 | - 🚦 **Request Validation**: Implement request validation and data binding effortlessly by binding to DTOs, ensuring data integrity and security.
41 |
42 | - 🧪 **Testing Support**: The template encourages writing tests by providing a test-friendly structure, making it easier to verify the functionality and stability of your API.
43 |
44 | Because of all the aforementioned reasons, by using this template you can expedite the development process of your next project as it allows you to focus on building your specific business logic while leveraging the proven architecture, routing, middleware, and other essential components provided.
45 |
46 | Whether you're a seasoned developer or just starting with `Gin`, this template provides a solid foundation and best practices for creating clean, scalable, and production-ready REST APIs.
47 |
48 | ## Motivation 💪
49 | Finding the perfect REST API template for `golang` and `Gin` can be a daunting task, as the existing options may not always fulfill all the specific requirements of your project. While several popular templates like [`dipeshdulal/clean-gin`](https://github.com/dipeshdulal/clean-gin/) and [`vsouza/go-gin-boilerplate`](https://github.com/vsouza/go-gin-boilerplate) have garnered attention, they might lack certain essential features you need to have a full-fledged REST API.
50 |
51 | To address these limitations and create a more tailored solution, I decided to develop my own API template. This template not only solves many of the problems developers may find when starting their new project, but also incorporates several crucial functionalities for a clean and efficient API development process.
52 |
53 | And one of the key features of the template is the implementation of dependency injection through `go.uber.org/fx`, which promotes better code organization and modularity. By using this approach, the API becomes more maintainable and easier to extend over time. This template also aims to teach how to use `go-fx` in complex codebases and even provides examples for how to do tests, which is something I struggled to find on the internet when developing this project.
54 |
55 | Furthermore, this template intentionally avoids using an ORM (Object-Relational Mapping) to interact with databases. Instead, it uses `github.com/jackc/pgx`, a low-level, high performance interface that exposes PostgreSQL-specific features to Go. This way, it allows developers to utilize the full potential of native SQL queries, and it should be fairly easy to change the driver with one for your preferred database.
56 |
57 | Additionally, the template includes built-in support for validating both `json` and `form url-encoded` request bodies. By incorporating this validation mechanism, developers can ensure that incoming data is accurately validated and processed, leading to enhanced data integrity and security.
58 |
59 | To ensure a consistent and well-organized project structure, this template adheres to the standard golang folder structure. This practice not only improves the readability of the codebase but also enables other developers to quickly grasp the project's layout.
60 |
61 | In terms of error handling, the template focuses on user-friendly messages and informative responses. By providing clear and meaningful error messages, the API users can easily understand and troubleshoot any issues they might encounter, thereby enhancing the overall user experience.
62 |
63 | Additionally, as I further iterated on the template, I incorporated more features that cater to various use cases and requirements, such as CORS, logging, user authentication and authorization, custom SQL query functions, among others.
64 |
65 | In conclusion, this custom REST API template for `golang` and `Gin` is designed to encompass a comprehensive set of features while maintaining simplicity and flexibility. By combining dependency injection, absence of ORM, data validation, standardized folder structure, user-friendly error handling, and various other functionalities, this template serves as a solid foundation for developing clean, efficient, and tailored APIs to suit your specific needs.
66 |
67 | ## Getting Started 🏃♂️
68 | To get started, click on the `Use template` button on the top of this README, or simply clone this project using the following command:
69 |
70 | #### Using GitHub CLI
71 | ```shell
72 | $ gh repo clone alexmodrono/gin-restapi-template
73 | ```
74 |
75 | #### Using git
76 | ```shell
77 | $ git clone https://github.com/alexmodrono/gin-restapi-template.git
78 | ```
79 |
80 | Once you have cloned the repository, run `go mod tidy` to install the dependencies.
81 |
82 | ```
83 | cd gin-restapi-template
84 | go mod tidy
85 | ```
86 |
87 | ### Running and building the project
88 | Since this template follows the Go folder structure convention, there is no `main.go` file. Instead, the entry point of this app is located at `cmd/gin-restapi-template`. Because of this, it is recommended you build and/or run the project using the Makefile provided as follows:
89 |
90 | ```shell
91 | make build # builds the project
92 | make run # builds and runs the project
93 | make test # runs the tests
94 | ```
95 |
96 | ## TODO List 📝
97 | While this template is very complete and is perfectly ready for anyone to use, there are still some features missing or that would be great to have implemented, among which are:
98 |
99 | - [ ] Add unit tests
100 | - [ ] Add integration tests
101 | - [ ] File upload middleware
102 | - [ ] Add documentation using Vite/Vuepress.
103 | - [x] Add Makefile for automatically running SQL queries.
104 | - [x] Add custom SQL queries.
105 |
106 | ## Custom database queries
107 | In an effort to enhance the code's readability and maintainability, this template employs custom functions like `auth.get_user_by_id`, `auth.get_user_by_email`, and `auth.get_user_by_username` to streamline the length of queries. These functions abstract complex database operations, making the code more concise and organized.
108 |
109 | To incorporate these custom functions into your database, follow these steps:
110 |
111 | 1. First, ensure you have PostgreSQL installed on your system.
112 |
113 | 2. Locate the `create_query_functions.sql` file in the `sql` directory of this template.
114 |
115 | 3. Open a terminal or command prompt and execute the following command, replacing `[DATABASE]` with the name of your PostgreSQL database and `[USER]` with the appropriate database user:
116 |
117 | ```bash
118 | $ psql [DATABASE] -U [USER] -f sql/create_query_functions.sql
119 | ```
120 |
121 | This command will run the SQL script and create the necessary functions in your database.
122 |
123 | Additionally, there is another essential file (`sql/create_users_table.sql`) for setting up the authentication schema and the `users` table.
124 |
125 | Before running any SQL scripts, it is essential to review the contents of the scripts and ensure they align with your specific database requirements. Also, make sure to take appropriate precautions and backups before making any changes to your database.
126 |
127 | By incorporating these custom functions and setting up the authentication schema, you can optimize your database interactions and improve the overall performance and maintainability of your application.
128 |
--------------------------------------------------------------------------------
/assets/banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alexmodrono/gin-restapi-template/896425ab9c80ca8bf59c995edbf87efd0683882c/assets/banner.png
--------------------------------------------------------------------------------
/assets/github_star_button.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alexmodrono/gin-restapi-template/896425ab9c80ca8bf59c995edbf87efd0683882c/assets/github_star_button.png
--------------------------------------------------------------------------------
/assets/use_template_button.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alexmodrono/gin-restapi-template/896425ab9c80ca8bf59c995edbf87efd0683882c/assets/use_template_button.png
--------------------------------------------------------------------------------
/cmd/gin-restapi-template/main.go:
--------------------------------------------------------------------------------
1 | /*
2 | Package Name: main
3 | File Name: main.go
4 | Abstract: The entry point of the project and the source code of the
5 | executable that will be used for initializing the API.
6 |
7 | Author: Alejandro Modroño
8 | Created: 07/08/2023
9 | Last Updated: 07/24/2023
10 |
11 | # MIT License
12 |
13 | # Copyright 2023 Alejandro Modroño Vara
14 |
15 | Permission is hereby granted, free of charge, to any person obtaining a copy
16 | of this software and associated documentation files (the "Software"), to deal
17 | in the Software without restriction, including without limitation the rights
18 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
19 | copies of the Software, and to permit persons to whom the Software is
20 | furnished to do so, subject to the following conditions:
21 |
22 | The above copyright notice and this permission notice shall be included in all
23 | copies or substantial portions of the Software.
24 |
25 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
26 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
27 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
28 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
29 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
30 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
31 | SOFTWARE.
32 | */
33 | package main
34 |
35 | import (
36 | "flag"
37 | "fmt"
38 | "os"
39 |
40 | "github.com/alexmodrono/gin-restapi-template/internal/bootstrap"
41 | "github.com/gin-gonic/gin"
42 | "github.com/joho/godotenv"
43 | "go.uber.org/fx"
44 | )
45 |
46 | // ======== PRIVATE METHODS ========
47 |
48 | // isValidEnvironment checks whether the environment flag is
49 | // a valid environment name.
50 | func isValidEnvironment(environment *string) bool {
51 | switch *environment {
52 | case
53 | "development",
54 | "production",
55 | "test":
56 | return true
57 | }
58 | return false
59 | }
60 |
61 | // ======== ENTRY POINT ========
62 | func main() {
63 |
64 | // ======== CHECK ENVIRONMENT ========
65 | environment := flag.String("e", "development", "")
66 | flag.Usage = func() {
67 | fmt.Println("Usage: server -e {mode}")
68 | os.Exit(1)
69 | }
70 | flag.Parse()
71 |
72 | // The only available environments are "production", "development",
73 | // and "test". If any other environment is provided the api should
74 | // exit.
75 | if !isValidEnvironment(environment) {
76 | fmt.Println("The environment is not valid!")
77 | os.Exit(1)
78 | }
79 |
80 | // Set the 'ENVIRONMENT' value to the flag passed so that
81 | // we can check the state wherever we want.
82 | os.Setenv("ENVIRONMENT", *environment)
83 |
84 | // ======== CONFIG FILES ========
85 | // Load the corresponding environment file
86 | godotenv.Load("configs/.env." + *environment)
87 |
88 | // Load the main file
89 | godotenv.Load("configs/.env")
90 |
91 | // ======== DISCLAIMER ========
92 | fmt.Printf("Welcome to %s %s; Written by %s\n", os.Getenv("APP_NAME"), os.Getenv("APP_VERSION"), os.Getenv("APP_AUTHOR"))
93 |
94 | // ======== DEPENDENCY INJECTION ========
95 | // The api is divided following the next structure:
96 | // - Bootstrap: bundles all the dependency injection under one `fx.Options` variable for cleaner code.
97 |
98 | // If the environment is production, set the gin
99 | // environment to 'release'.
100 | if *environment == "production" {
101 | gin.SetMode(gin.ReleaseMode)
102 | }
103 |
104 | // fx.NopLogger disables the logger
105 | fx.New(
106 | bootstrap.Module,
107 | fx.NopLogger,
108 | ).Run()
109 | }
110 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/alexmodrono/gin-restapi-template
2 |
3 | go 1.20
4 |
5 | require (
6 | github.com/driftprogramming/pgxpoolmock v1.1.0
7 | github.com/gin-contrib/zap v0.1.0
8 | github.com/gin-gonic/gin v1.9.1
9 | github.com/go-playground/validator/v10 v10.14.1
10 | github.com/golang-jwt/jwt/v5 v5.0.0
11 | github.com/jackc/pgx/v5 v5.4.2
12 | github.com/joho/godotenv v1.5.1
13 | github.com/rs/cors/wrapper/gin v0.0.0-20230526135330-e90f16747950
14 | github.com/stretchr/testify v1.8.4
15 | github.com/withmandala/go-log v0.1.0
16 | go.uber.org/fx v1.20.0
17 | go.uber.org/zap v1.24.0
18 | golang.org/x/crypto v0.11.0
19 | )
20 |
21 | require (
22 | github.com/bytedance/sonic v1.9.1 // indirect
23 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
24 | github.com/davecgh/go-spew v1.1.1 // indirect
25 | github.com/gabriel-vasile/mimetype v1.4.2 // indirect
26 | github.com/gin-contrib/sse v0.1.0 // indirect
27 | github.com/go-playground/locales v0.14.1 // indirect
28 | github.com/go-playground/universal-translator v0.18.1 // indirect
29 | github.com/goccy/go-json v0.10.2 // indirect
30 | github.com/golang/mock v1.5.0 // indirect
31 | github.com/jackc/chunkreader/v2 v2.0.1 // indirect
32 | github.com/jackc/pgconn v1.8.1 // indirect
33 | github.com/jackc/pgio v1.0.0 // indirect
34 | github.com/jackc/pgpassfile v1.0.0 // indirect
35 | github.com/jackc/pgproto3/v2 v2.0.6 // indirect
36 | github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
37 | github.com/jackc/pgtype v1.7.0 // indirect
38 | github.com/jackc/pgx/v4 v4.11.0 // indirect
39 | github.com/jackc/puddle/v2 v2.2.0 // indirect
40 | github.com/json-iterator/go v1.1.12 // indirect
41 | github.com/klauspost/cpuid/v2 v2.2.4 // indirect
42 | github.com/leodido/go-urn v1.2.4 // indirect
43 | github.com/mattn/go-isatty v0.0.19 // indirect
44 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
45 | github.com/modern-go/reflect2 v1.0.2 // indirect
46 | github.com/pelletier/go-toml/v2 v2.0.8 // indirect
47 | github.com/pmezard/go-difflib v1.0.0 // indirect
48 | github.com/rs/cors v1.8.1 // indirect
49 | github.com/smartystreets/goconvey v1.8.1 // indirect
50 | github.com/stretchr/objx v0.5.0 // indirect
51 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
52 | github.com/ugorji/go/codec v1.2.11 // indirect
53 | go.opentelemetry.io/otel v1.10.0 // indirect
54 | go.opentelemetry.io/otel/trace v1.10.0 // indirect
55 | go.uber.org/atomic v1.7.0 // indirect
56 | go.uber.org/dig v1.17.0 // indirect
57 | go.uber.org/multierr v1.6.0 // indirect
58 | golang.org/x/arch v0.3.0 // indirect
59 | golang.org/x/net v0.10.0 // indirect
60 | golang.org/x/sync v0.1.0 // indirect
61 | golang.org/x/sys v0.10.0 // indirect
62 | golang.org/x/term v0.10.0 // indirect
63 | golang.org/x/text v0.11.0 // indirect
64 | google.golang.org/protobuf v1.30.0 // indirect
65 | gopkg.in/yaml.v3 v3.0.1 // indirect
66 | )
67 |
--------------------------------------------------------------------------------
/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 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
4 | github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
5 | github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
6 | github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
7 | github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
8 | github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
9 | github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
10 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
11 | github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
12 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
13 | github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
14 | github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
15 | github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
16 | github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
17 | github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
18 | github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
19 | github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
20 | github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
21 | github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
22 | github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
23 | github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
24 | github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A=
25 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
26 | github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
27 | github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
28 | github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
29 | github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
30 | github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
31 | github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
32 | github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
33 | github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
34 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
35 | github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
36 | github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
37 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
38 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
39 | github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE=
40 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
41 | github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
42 | github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
43 | github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
44 | github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
45 | github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
46 | github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
47 | github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
48 | github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
49 | github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
50 | github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
51 | github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
52 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
53 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
54 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
55 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
56 | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
57 | github.com/driftprogramming/pgxpoolmock v1.1.0 h1:gLTxRYerNxz3y1iUQYQlZwIo4lgvgLOGx/qUcjOsG3A=
58 | github.com/driftprogramming/pgxpoolmock v1.1.0/go.mod h1:Uq6x6grXIh5FsovGWHolC33tGBOPV3fUg6lUKEXZ0dQ=
59 | github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
60 | github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
61 | github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
62 | github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
63 | github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
64 | github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g=
65 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
66 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
67 | github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
68 | github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4=
69 | github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
70 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
71 | github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
72 | github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
73 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
74 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
75 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
76 | github.com/gin-contrib/zap v0.1.0 h1:RMSFFJo34XZogV62OgOzvrlaMNmXrNxmJ3bFmMwl6Cc=
77 | github.com/gin-contrib/zap v0.1.0/go.mod h1:hvnZaPs478H1PGvRP8w89ZZbyJUiyip4ddiI/53WG3o=
78 | github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk=
79 | github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
80 | github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
81 | github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
82 | github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
83 | github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o=
84 | github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
85 | github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
86 | github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
87 | github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
88 | github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
89 | github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
90 | github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
91 | github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
92 | github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
93 | github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
94 | github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
95 | github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
96 | github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
97 | github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
98 | github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
99 | github.com/go-playground/validator/v10 v10.14.1 h1:9c50NUPC30zyuKprjL3vNZ0m5oG+jU0zvx4AqHGnv4k=
100 | github.com/go-playground/validator/v10 v10.14.1/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
101 | github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
102 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
103 | github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
104 | github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
105 | github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
106 | github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE=
107 | github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
108 | github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
109 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
110 | github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
111 | github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
112 | github.com/golang-jwt/jwt/v5 v5.0.0 h1:1n1XNM9hk7O9mnQoNBGolZvzebBQ7p93ULHRc28XJUE=
113 | github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
114 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
115 | github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
116 | github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
117 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
118 | github.com/golang/mock v1.5.0 h1:jlYHihg//f7RRwuPfptm04yp4s7O6Kw8EZiVYIGcH0g=
119 | github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
120 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
121 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
122 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
123 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
124 | github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
125 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
126 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
127 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
128 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
129 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
130 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
131 | github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
132 | github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
133 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
134 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
135 | github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
136 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
137 | github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g=
138 | github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
139 | github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
140 | github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
141 | github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
142 | github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
143 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
144 | github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
145 | github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE=
146 | github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
147 | github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
148 | github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
149 | github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
150 | github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
151 | github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
152 | github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
153 | github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
154 | github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
155 | github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
156 | github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
157 | github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
158 | github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
159 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
160 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
161 | github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
162 | github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
163 | github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
164 | github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
165 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
166 | github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg=
167 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
168 | github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
169 | github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
170 | github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
171 | github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8=
172 | github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
173 | github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA=
174 | github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE=
175 | github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s=
176 | github.com/jackc/pgconn v1.4.0/go.mod h1:Y2O3ZDF0q4mMacyWV3AstPJpeHXWGEetiFttmq5lahk=
177 | github.com/jackc/pgconn v1.5.0/go.mod h1:QeD3lBfpTFe8WUnPZWN5KY/mB8FGMIYRdd8P8Jr0fAI=
178 | github.com/jackc/pgconn v1.5.1-0.20200601181101-fa742c524853/go.mod h1:QeD3lBfpTFe8WUnPZWN5KY/mB8FGMIYRdd8P8Jr0fAI=
179 | github.com/jackc/pgconn v1.8.1 h1:ySBX7Q87vOMqKU2bbmKbUvtYhauDFclYbNDYIE1/h6s=
180 | github.com/jackc/pgconn v1.8.1/go.mod h1:JV6m6b6jhjdmzchES0drzCcYcAHS1OPD5xu3OZ/lE2g=
181 | github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE=
182 | github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
183 | github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2 h1:JVX6jT/XfzNqIjye4717ITLaNwV9mWbJx0dLCpcRzdA=
184 | github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=
185 | github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
186 | github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
187 | github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78=
188 | github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA=
189 | github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg=
190 | github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
191 | github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
192 | github.com/jackc/pgproto3/v2 v2.0.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
193 | github.com/jackc/pgproto3/v2 v2.0.6 h1:b1105ZGEMFe7aCvrT1Cca3VoVb4ZFMaFJLJcg/3zD+8=
194 | github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
195 | github.com/jackc/pgservicefile v0.0.0-20200307190119-3430c5407db8/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
196 | github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
197 | github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
198 | github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
199 | github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=
200 | github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
201 | github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
202 | github.com/jackc/pgtype v1.2.0/go.mod h1:5m2OfMh1wTK7x+Fk952IDmI4nw3nPrvtQdM0ZT4WpC0=
203 | github.com/jackc/pgtype v1.3.1-0.20200510190516-8cd94a14c75a/go.mod h1:vaogEUkALtxZMCH411K+tKzNpwzCKU+AnPzBKZ+I+Po=
204 | github.com/jackc/pgtype v1.3.1-0.20200606141011-f6355165a91c/go.mod h1:cvk9Bgu/VzJ9/lxTO5R5sf80p0DiucVtN7ZxvaC4GmQ=
205 | github.com/jackc/pgtype v1.7.0 h1:6f4kVsW01QftE38ufBYxKciO6gyioXSC0ABIRLcZrGs=
206 | github.com/jackc/pgtype v1.7.0/go.mod h1:ZnHF+rMePVqDKaOfJVI4Q8IVvAQMryDlDkZnKOI75BE=
207 | github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
208 | github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
209 | github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
210 | github.com/jackc/pgx/v4 v4.5.0/go.mod h1:EpAKPLdnTorwmPUUsqrPxy5fphV18j9q3wrfRXgo+kA=
211 | github.com/jackc/pgx/v4 v4.6.1-0.20200510190926-94ba730bb1e9/go.mod h1:t3/cdRQl6fOLDxqtlyhe9UWgfIi9R8+8v8GKV5TRA/o=
212 | github.com/jackc/pgx/v4 v4.6.1-0.20200606145419-4e5062306904/go.mod h1:ZDaNWkt9sW1JMiNn0kdYBaLelIhw7Pg4qd+Vk6tw7Hg=
213 | github.com/jackc/pgx/v4 v4.11.0 h1:J86tSWd3Y7nKjwT/43xZBvpi04keQWx8gNC2YkdJhZI=
214 | github.com/jackc/pgx/v4 v4.11.0/go.mod h1:i62xJgdrtVDsnL3U8ekyrQXEwGNTRoG7/8r+CIdYfcc=
215 | github.com/jackc/pgx/v5 v5.4.2 h1:u1gmGDwbdRUZiwisBm/Ky2M14uQyUP65bG8+20nnyrg=
216 | github.com/jackc/pgx/v5 v5.4.2/go.mod h1:q6iHT8uDNXWiFNOlRqJzBTaSH3+2xCXkokxHZC5qWFY=
217 | github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
218 | github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
219 | github.com/jackc/puddle v1.1.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
220 | github.com/jackc/puddle v1.1.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
221 | github.com/jackc/puddle v1.1.3 h1:JnPg/5Q9xVJGfjsO5CPUOjnJps1JaRUm8I9FXVCFK94=
222 | github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
223 | github.com/jackc/puddle/v2 v2.2.0 h1:RdcDk92EJBuBS55nQMMYFXTxwstHug4jkhT5pq8VxPk=
224 | github.com/jackc/puddle/v2 v2.2.0/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
225 | github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
226 | github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
227 | github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
228 | github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
229 | github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
230 | github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
231 | github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
232 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
233 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
234 | github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
235 | github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
236 | github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
237 | github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
238 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
239 | github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
240 | github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
241 | github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
242 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
243 | github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
244 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
245 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
246 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
247 | github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
248 | github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
249 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
250 | github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
251 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
252 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
253 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
254 | github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
255 | github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
256 | github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
257 | github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
258 | github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
259 | github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
260 | github.com/lib/pq v1.3.0 h1:/qkRGz8zljWiDcFvgpwUpwIAPu3r07TDvs3Rws+o/pU=
261 | github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
262 | github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
263 | github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
264 | github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
265 | github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
266 | github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
267 | github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
268 | github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
269 | github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
270 | github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
271 | github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
272 | github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
273 | github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
274 | github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
275 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
276 | github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
277 | github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
278 | github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
279 | github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
280 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
281 | github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
282 | github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
283 | github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
284 | github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
285 | github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
286 | github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
287 | github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
288 | github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
289 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
290 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
291 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
292 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
293 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
294 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
295 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
296 | github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
297 | github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
298 | github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU=
299 | github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k=
300 | github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w=
301 | github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
302 | github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
303 | github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
304 | github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs=
305 | github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
306 | github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
307 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
308 | github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
309 | github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
310 | github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
311 | github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis=
312 | github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
313 | github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
314 | github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
315 | github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA=
316 | github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
317 | github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
318 | github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
319 | github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM=
320 | github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
321 | github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
322 | github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo=
323 | github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
324 | github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
325 | github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac=
326 | github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
327 | github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
328 | github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
329 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
330 | github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
331 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
332 | github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
333 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
334 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
335 | github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
336 | github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
337 | github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
338 | github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
339 | github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og=
340 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
341 | github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
342 | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
343 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
344 | github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
345 | github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
346 | github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
347 | github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA=
348 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
349 | github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
350 | github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
351 | github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
352 | github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
353 | github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
354 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
355 | github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
356 | github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
357 | github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
358 | github.com/rs/cors v1.8.1 h1:OrP+y5H+5Md29ACTA9imbALaKHwOSUZkcizaG0LT5ow=
359 | github.com/rs/cors v1.8.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
360 | github.com/rs/cors/wrapper/gin v0.0.0-20230526135330-e90f16747950 h1:AqLt1PEuscqbMJkmkfOw1xLlDH0VIQzrDEuOGggv0a4=
361 | github.com/rs/cors/wrapper/gin v0.0.0-20230526135330-e90f16747950/go.mod h1:gmu40DuK3SLdKUzGOUofS3UDZwyeOUy6ZjPPuaALatw=
362 | github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
363 | github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
364 | github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
365 | github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
366 | github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
367 | github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
368 | github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
369 | github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
370 | github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
371 | github.com/shopspring/decimal v0.0.0-20200227202807-02e2044944cc h1:jUIKcSPO9MoMJBbEoyE/RJoE8vz7Mb8AjvifMMwSyvY=
372 | github.com/shopspring/decimal v0.0.0-20200227202807-02e2044944cc/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
373 | github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
374 | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
375 | github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
376 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
377 | github.com/smarty/assertions v1.15.0 h1:cR//PqUBUiQRakZWqBiFFQ9wb8emQGDb0HeGdqGByCY=
378 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
379 | github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
380 | github.com/smartystreets/goconvey v1.8.1 h1:qGjIddxOk4grTu9JPOU31tVfq3cNdBlNa5sSznIX1xY=
381 | github.com/smartystreets/goconvey v1.8.1/go.mod h1:+/u4qLyY6x1jReYOp7GOM2FSt8aP9CzCZL03bI28W60=
382 | github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
383 | github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
384 | github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
385 | github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
386 | github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
387 | github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
388 | github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI=
389 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
390 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
391 | github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
392 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
393 | github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
394 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
395 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
396 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
397 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
398 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
399 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
400 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
401 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
402 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
403 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
404 | github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
405 | github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
406 | github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
407 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
408 | github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
409 | github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
410 | github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
411 | github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
412 | github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
413 | github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
414 | github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
415 | github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
416 | github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
417 | github.com/withmandala/go-log v0.1.0 h1:wINmTEe7BQ6zEA8sE7lSsYeaxCLluK6RFjF/IB5tzkA=
418 | github.com/withmandala/go-log v0.1.0/go.mod h1:/V9xQUTW74VjYm3u2Liv/bIUGLWoL9z2GlHwtscp4vg=
419 | github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
420 | github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
421 | github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
422 | go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
423 | go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
424 | go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
425 | go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
426 | go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
427 | go.opentelemetry.io/otel v1.10.0 h1:Y7DTJMR6zs1xkS/upamJYk0SxxN4C9AqRd77jmZnyY4=
428 | go.opentelemetry.io/otel v1.10.0/go.mod h1:NbvWjCthWHKBEUMpf0/v8ZRZlni86PpGFEMA9pnQSnQ=
429 | go.opentelemetry.io/otel/trace v1.10.0 h1:npQMbR8o7mum8uF95yFbOEJffhs1sbCOfDh8zAJiH5E=
430 | go.opentelemetry.io/otel/trace v1.10.0/go.mod h1:Sij3YYczqAdz+EhmGhE6TpTxUO5/F/AzrK+kxfGqySM=
431 | go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
432 | go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
433 | go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
434 | go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
435 | go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
436 | go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
437 | go.uber.org/dig v1.17.0 h1:5Chju+tUvcC+N7N6EV08BJz41UZuO3BmHcN4A287ZLI=
438 | go.uber.org/dig v1.17.0/go.mod h1:rTxpf7l5I0eBTlE6/9RL+lDybC7WFwY2QH55ZSjy1mU=
439 | go.uber.org/fx v1.20.0 h1:ZMC/pnRvhsthOZh9MZjMq5U8Or3mA9zBSPaLnzs3ihQ=
440 | go.uber.org/fx v1.20.0/go.mod h1:qCUj0btiR3/JnanEr1TYEePfSw6o/4qYJscgvzQ5Ub0=
441 | go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
442 | go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
443 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
444 | go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
445 | go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
446 | go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
447 | go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
448 | go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
449 | go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
450 | go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
451 | go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
452 | go.uber.org/zap v1.23.0/go.mod h1:D+nX8jyLsMHMYrln8A0rJjFt/T/9/bGgIhAqxv5URuY=
453 | go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60=
454 | go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg=
455 | golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
456 | golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
457 | golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
458 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
459 | golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
460 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
461 | golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
462 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
463 | golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
464 | golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
465 | golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
466 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
467 | golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
468 | golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
469 | golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
470 | golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA=
471 | golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
472 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
473 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
474 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
475 | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
476 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
477 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
478 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
479 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
480 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
481 | golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
482 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
483 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
484 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
485 | golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
486 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
487 | golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
488 | golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
489 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
490 | golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
491 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
492 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
493 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
494 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
495 | golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
496 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
497 | golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
498 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
499 | golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
500 | golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
501 | golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
502 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
503 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
504 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
505 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
506 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
507 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
508 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
509 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
510 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
511 | golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
512 | golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
513 | golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
514 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
515 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
516 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
517 | golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
518 | golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
519 | golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
520 | golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
521 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
522 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
523 | golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
524 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
525 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
526 | golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
527 | golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
528 | golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
529 | golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
530 | golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
531 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
532 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
533 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
534 | golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
535 | golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
536 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
537 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
538 | golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
539 | golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
540 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
541 | golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
542 | golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
543 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
544 | golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c=
545 | golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o=
546 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
547 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
548 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
549 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
550 | golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4=
551 | golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
552 | golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
553 | golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
554 | golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
555 | golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
556 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
557 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
558 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
559 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
560 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
561 | golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
562 | golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
563 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
564 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
565 | golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
566 | golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
567 | golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
568 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
569 | golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
570 | golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
571 | golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
572 | golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
573 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
574 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
575 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
576 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
577 | google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
578 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
579 | google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
580 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
581 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
582 | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
583 | google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
584 | google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s=
585 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
586 | google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
587 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
588 | google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM=
589 | google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
590 | google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
591 | google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
592 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
593 | google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
594 | google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
595 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
596 | google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
597 | google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
598 | google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
599 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
600 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
601 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
602 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
603 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
604 | gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
605 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
606 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
607 | gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
608 | gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
609 | gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
610 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
611 | gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
612 | gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
613 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
614 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
615 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
616 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
617 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
618 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
619 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
620 | honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
621 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
622 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
623 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
624 | rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
625 | sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
626 | sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=
627 |
--------------------------------------------------------------------------------
/internal/bootstrap/bootstrap.go:
--------------------------------------------------------------------------------
1 | /*
2 | Package Name: bootstrap
3 | File Name: bootstrap.go
4 | Abstract: Wrapper for invoking all the module's dependencies and starting
5 | the API by loading the essential initial components that allow it to run
6 | and perform more complex tasks.
7 |
8 | Author: Alejandro Modroño
9 | Created: 07/08/2023
10 | Last Updated: 07/24/2023
11 |
12 | # MIT License
13 |
14 | # Copyright 2023 Alejandro Modroño Vara
15 |
16 | Permission is hereby granted, free of charge, to any person obtaining a copy
17 | of this software and associated documentation files (the "Software"), to deal
18 | in the Software without restriction, including without limitation the rights
19 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
20 | copies of the Software, and to permit persons to whom the Software is
21 | furnished to do so, subject to the following conditions:
22 |
23 | The above copyright notice and this permission notice shall be included in all
24 | copies or substantial portions of the Software.
25 |
26 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
27 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
28 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
29 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
30 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
31 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
32 | SOFTWARE.
33 | */
34 | package bootstrap
35 |
36 | import (
37 | "context"
38 | "fmt"
39 | "os"
40 |
41 | "github.com/alexmodrono/gin-restapi-template/internal/middlewares"
42 | "github.com/alexmodrono/gin-restapi-template/pkg/auth"
43 | "github.com/alexmodrono/gin-restapi-template/pkg/lib"
44 | "github.com/alexmodrono/gin-restapi-template/pkg/users"
45 | "go.uber.org/fx"
46 | )
47 |
48 | // ======== PRIVATE METHODS ========
49 |
50 | // registerHooks registers a lifecycle hook that starts the API and logs a message
51 | // when the app is stopped.
52 | func registerHooks(
53 | lifecycle fx.Lifecycle,
54 | router *lib.Router,
55 | logger lib.Logger,
56 | routes Routes,
57 | middlewares middlewares.Middlewares,
58 | ) {
59 | lifecycle.Append(
60 | fx.Hook{
61 | OnStart: func(context.Context) error {
62 | // Log the start of the application with the configured host and port
63 | logger.Info(
64 | fmt.Sprintf(
65 | "Starting application in %s:%s",
66 | os.Getenv("APP_HOST"),
67 | os.Getenv("APP_PORT"),
68 | ),
69 | )
70 |
71 | // ======== SET UP COMPONENTS ========
72 | // Perform any necessary setup or initialization tasks for the middlewares
73 | middlewares.Setup()
74 |
75 | // Perform any necessary setup or initialization tasks for the routes
76 | routes.Setup()
77 |
78 | // Start the router by running it in a separate goroutine
79 | go router.Run(fmt.Sprintf("%s:%s", os.Getenv("APP_HOST"), os.Getenv("APP_PORT")))
80 |
81 | return nil
82 | },
83 | OnStop: func(ctx context.Context) error {
84 | // Log the stop of the application and any associated error
85 | logger.Fatal(
86 | fmt.Sprintf(
87 | "Stopping application. Error: %s", ctx.Err(),
88 | ),
89 | )
90 |
91 | return nil
92 | },
93 | },
94 | )
95 | }
96 |
97 | // ======== EXPORTS ========
98 |
99 | // Module exports for fx
100 | var Module = fx.Options(
101 | // Module exports
102 | lib.Module,
103 | middlewares.Module,
104 |
105 | // Context exports
106 | users.Context,
107 | auth.Context,
108 |
109 | // Bootstrap exports
110 | fx.Provide(GetRoutes),
111 |
112 | // Methods
113 | fx.Invoke(registerHooks),
114 | )
115 |
--------------------------------------------------------------------------------
/internal/bootstrap/routes.go:
--------------------------------------------------------------------------------
1 | /*
2 | Package Name: bootstrap
3 | File Name: routes.go
4 | Abstract: The wrapper for setting up all the routes.
5 |
6 | Author: Alejandro Modroño
7 | Created: 07/08/2023
8 | Last Updated: 07/24/2023
9 |
10 | # MIT License
11 |
12 | # Copyright 2023 Alejandro Modroño Vara
13 |
14 | Permission is hereby granted, free of charge, to any person obtaining a copy
15 | of this software and associated documentation files (the "Software"), to deal
16 | in the Software without restriction, including without limitation the rights
17 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
18 | copies of the Software, and to permit persons to whom the Software is
19 | furnished to do so, subject to the following conditions:
20 |
21 | The above copyright notice and this permission notice shall be included in all
22 | copies or substantial portions of the Software.
23 |
24 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
25 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
26 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
27 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
28 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
29 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
30 | SOFTWARE.
31 | */
32 | package bootstrap
33 |
34 | import (
35 | "github.com/alexmodrono/gin-restapi-template/pkg/auth"
36 | "github.com/alexmodrono/gin-restapi-template/pkg/users"
37 | )
38 |
39 | // ======== TYPES ========
40 |
41 | // Route interface
42 | type Route interface {
43 | Setup()
44 | }
45 |
46 | // Routes contains multiple routes
47 | type Routes []Route
48 |
49 | // ======== PUBLIC METHODS ========
50 |
51 | // GetRoutes provides all the routes
52 | func GetRoutes(
53 | userRoutes users.UsersRoutes,
54 | authRoutes auth.AuthRoutes,
55 | ) Routes {
56 | return Routes{
57 | userRoutes,
58 | authRoutes,
59 | }
60 | }
61 |
62 | // Sets up all the routes
63 | func (r Routes) Setup() {
64 | for _, route := range r {
65 | route.Setup()
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/internal/middlewares/auth_middleware.go:
--------------------------------------------------------------------------------
1 | /*
2 | Package Name: middlewares
3 | File Name: auth_middleware.go
4 | Abstract: The middleware for protecting routes.
5 |
6 | Author: Alejandro Modroño
7 | Created: 07/08/2023
8 | Last Updated: 07/24/2023
9 |
10 | # MIT License
11 |
12 | # Copyright 2023 Alejandro Modroño Vara
13 |
14 | Permission is hereby granted, free of charge, to any person obtaining a copy
15 | of this software and associated documentation files (the "Software"), to deal
16 | in the Software without restriction, including without limitation the rights
17 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
18 | copies of the Software, and to permit persons to whom the Software is
19 | furnished to do so, subject to the following conditions:
20 |
21 | The above copyright notice and this permission notice shall be included in all
22 | copies or substantial portions of the Software.
23 |
24 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
25 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
26 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
27 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
28 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
29 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
30 | SOFTWARE.
31 | */
32 | package middlewares
33 |
34 | import (
35 | "errors"
36 | "net/http"
37 | "strings"
38 |
39 | "github.com/alexmodrono/gin-restapi-template/pkg/interfaces"
40 | "github.com/alexmodrono/gin-restapi-template/pkg/lib"
41 | "github.com/gin-gonic/gin"
42 | )
43 |
44 | // ======== TYPES ========
45 |
46 | // AuthMiddleware middleware for authentication
47 | type AuthMiddleware struct {
48 | service interfaces.AuthService
49 | logger lib.Logger
50 | }
51 |
52 | // ======== PUBLIC METHODS ========
53 |
54 | // GetAuthMiddleware returns the auth middleware
55 | func GetAuthMiddleware(
56 | logger lib.Logger,
57 | service interfaces.AuthService,
58 | ) AuthMiddleware {
59 | return AuthMiddleware{
60 | service: service,
61 | logger: logger,
62 | }
63 | }
64 |
65 | // Setup sets up jwt auth middleware
66 | func (middleware AuthMiddleware) Setup() {}
67 |
68 | // Handler handles the middleware's functionality
69 | func (middleware AuthMiddleware) Handler() gin.HandlerFunc {
70 | return func(ctx *gin.Context) {
71 | // Retrieve the Authorization header from the request
72 | authHeader := ctx.Request.Header.Get("Authorization")
73 | authHeaderSplit := strings.Split(authHeader, " ")
74 |
75 | if len(authHeaderSplit) != 2 || strings.ToLower(authHeaderSplit[0]) != "bearer" {
76 | middleware.logger.Info("Tried to access protected route without credentials.")
77 | // If the Authorization header is missing or does not start with "Bearer",
78 | // return an HTTP 401 Unauthorized response or handle the error appropriately.
79 | ctx.AbortWithError(
80 | http.StatusUnauthorized,
81 | errors.New("An access token is required for accessing this data."),
82 | )
83 | return
84 | }
85 |
86 | // Extract the token from the Authorization header
87 | token := authHeaderSplit[1]
88 | // Check the validity of the token using the authentication service
89 | id, err := middleware.service.CheckToken(token)
90 | if err != nil {
91 | // If there is an error in token verification, return an internal server error
92 | ctx.AbortWithError(http.StatusInternalServerError, err)
93 | return
94 | }
95 |
96 | // Set the authenticated user's ID in the context for downstream handlers to access
97 | ctx.Set("id", id)
98 | ctx.Next()
99 | return
100 |
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/internal/middlewares/cors_middleware.go:
--------------------------------------------------------------------------------
1 | /*
2 | Package Name: middlewares
3 | File Name: cors_middleware.go
4 | Abstract: The cors middleware for implementing CORS.
5 |
6 | Author: Alejandro Modroño
7 | Created: 07/12/2023
8 | Last Updated: 07/24/2023
9 |
10 | # MIT License
11 |
12 | # Copyright 2023 Alejandro Modroño Vara
13 |
14 | Permission is hereby granted, free of charge, to any person obtaining a copy
15 | of this software and associated documentation files (the "Software"), to deal
16 | in the Software without restriction, including without limitation the rights
17 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
18 | copies of the Software, and to permit persons to whom the Software is
19 | furnished to do so, subject to the following conditions:
20 |
21 | The above copyright notice and this permission notice shall be included in all
22 | copies or substantial portions of the Software.
23 |
24 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
25 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
26 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
27 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
28 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
29 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
30 | SOFTWARE.
31 | */
32 | package middlewares
33 |
34 | import (
35 | "os"
36 |
37 | "github.com/alexmodrono/gin-restapi-template/pkg/lib"
38 | cors "github.com/rs/cors/wrapper/gin"
39 | )
40 |
41 | // ======== TYPES ========
42 |
43 | // CorsMiddleware middleware for cors
44 | type CorsMiddleware struct {
45 | router *lib.Router
46 | logger lib.Logger
47 | }
48 |
49 | // ======== PUBLIC METHODS ========
50 |
51 | // NewCorsMiddleware creates new cors middleware
52 | func GetCorsMiddleware(router *lib.Router, logger lib.Logger) CorsMiddleware {
53 | return CorsMiddleware{
54 | router: router,
55 | logger: logger,
56 | }
57 | }
58 |
59 | // Setup sets up cors middleware
60 | func (middleware CorsMiddleware) Setup() {
61 | middleware.logger.Info("Setting up [CORS] middleware")
62 |
63 | debug := os.Getenv("ENVIRONMENT") == "development"
64 | middleware.router.Use(cors.New(cors.Options{
65 | AllowCredentials: true,
66 | AllowOriginFunc: func(origin string) bool { return true },
67 | AllowedHeaders: []string{"*"},
68 | AllowedMethods: []string{"GET", "POST", "PUT", "HEAD", "OPTIONS"},
69 | Debug: debug,
70 | }))
71 | }
72 |
--------------------------------------------------------------------------------
/internal/middlewares/errors_middleware.go:
--------------------------------------------------------------------------------
1 | /*
2 | Package Name: middlewares
3 | File Name: errors_middleware.go
4 | Abstract: The error middleware for better error handling.
5 |
6 | Author: Alejandro Modroño
7 | Created: 07/12/2023
8 | Last Updated: 07/24/2023
9 |
10 | # MIT License
11 |
12 | # Copyright 2023 Alejandro Modroño Vara
13 |
14 | Permission is hereby granted, free of charge, to any person obtaining a copy
15 | of this software and associated documentation files (the "Software"), to deal
16 | in the Software without restriction, including without limitation the rights
17 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
18 | copies of the Software, and to permit persons to whom the Software is
19 | furnished to do so, subject to the following conditions:
20 |
21 | The above copyright notice and this permission notice shall be included in all
22 | copies or substantial portions of the Software.
23 |
24 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
25 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
26 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
27 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
28 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
29 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
30 | SOFTWARE.
31 | */
32 | package middlewares
33 |
34 | import (
35 | "github.com/alexmodrono/gin-restapi-template/pkg/lib"
36 | "github.com/gin-gonic/gin"
37 | )
38 |
39 | // ======== TYPES ========
40 |
41 | // AuthMiddleware middleware for authentication
42 | type ErrorsMiddleware struct {
43 | logger lib.Logger
44 | router *lib.Router
45 | }
46 |
47 | // ======== PUBLIC METHODS ========
48 |
49 | // GetAuthMiddleware returns the auth middleware
50 | func GetErrorsMiddleware(
51 | logger lib.Logger,
52 | router *lib.Router,
53 | ) ErrorsMiddleware {
54 | return ErrorsMiddleware{
55 | logger: logger,
56 | router: router,
57 | }
58 | }
59 |
60 | // Setup sets up errors middleware
61 | func (middleware ErrorsMiddleware) Setup() {
62 | middleware.logger.Info("Setting up [ERRORS] middleware")
63 | middleware.router.Use(func(ctx *gin.Context) {
64 | ctx.Next()
65 |
66 | // if any of the routes abort with an error, it
67 | // will be catched here and displayed to the user.
68 | for _, err := range ctx.Errors {
69 | middleware.logger.Error("An error ocurred:", err)
70 | ctx.JSON(-1, err)
71 | }
72 | })
73 | }
74 |
--------------------------------------------------------------------------------
/internal/middlewares/middlewares.go:
--------------------------------------------------------------------------------
1 | /*
2 | Package Name: middlewares
3 | File Name: middlewares.go
4 | Abstract: The wrapper for setting up middlewares.
5 |
6 | Author: Alejandro Modroño
7 | Created: 07/12/2023
8 | Last Updated: 07/24/2023
9 |
10 | # MIT License
11 |
12 | # Copyright 2023 Alejandro Modroño Vara
13 |
14 | Permission is hereby granted, free of charge, to any person obtaining a copy
15 | of this software and associated documentation files (the "Software"), to deal
16 | in the Software without restriction, including without limitation the rights
17 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
18 | copies of the Software, and to permit persons to whom the Software is
19 | furnished to do so, subject to the following conditions:
20 |
21 | The above copyright notice and this permission notice shall be included in all
22 | copies or substantial portions of the Software.
23 |
24 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
25 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
26 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
27 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
28 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
29 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
30 | SOFTWARE.
31 | */
32 | package middlewares
33 |
34 | import (
35 | "go.uber.org/fx"
36 | )
37 |
38 | // ======== TYPES ========
39 |
40 | // The Middleware interface.
41 | type Middleware interface {
42 | Setup()
43 | }
44 |
45 | // Middlewares contains multiple middleware
46 | type Middlewares []Middleware
47 |
48 | // ======== PUBLIC METHODS ========
49 |
50 | // GetMiddlewares creates new middlewares
51 | func GetMiddlewares(
52 | corsMiddleware CorsMiddleware,
53 | errorsMiddleware ErrorsMiddleware,
54 | ) Middlewares {
55 | return Middlewares{
56 | corsMiddleware,
57 | errorsMiddleware,
58 | }
59 | }
60 |
61 | // Setup sets up middlewares
62 | func (m Middlewares) Setup() {
63 | for _, middleware := range m {
64 | middleware.Setup()
65 | }
66 | }
67 |
68 | // ======== EXPORTS ========
69 |
70 | // Module Middleware exported
71 | var Module = fx.Options(
72 | fx.Provide(GetCorsMiddleware),
73 | fx.Provide(GetErrorsMiddleware),
74 | fx.Provide(GetAuthMiddleware),
75 | fx.Provide(GetMiddlewares),
76 | )
77 |
--------------------------------------------------------------------------------
/pkg/auth/auth.go:
--------------------------------------------------------------------------------
1 | /*
2 | Package Name: users
3 | File Name: users.go
4 | Abstract: Wrapper for exposing to fx all the components of the 'users' context.
5 |
6 | Author: Alejandro Modroño
7 | Created: 07/08/2023
8 | Last Updated: 07/24/2023
9 |
10 | # MIT License
11 |
12 | # Copyright 2023 Alejandro Modroño Vara
13 |
14 | Permission is hereby granted, free of charge, to any person obtaining a copy
15 | of this software and associated documentation files (the "Software"), to deal
16 | in the Software without restriction, including without limitation the rights
17 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
18 | copies of the Software, and to permit persons to whom the Software is
19 | furnished to do so, subject to the following conditions:
20 |
21 | The above copyright notice and this permission notice shall be included in all
22 | copies or substantial portions of the Software.
23 |
24 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
25 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
26 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
27 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
28 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
29 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
30 | SOFTWARE.
31 | */
32 | package auth
33 |
34 | import "go.uber.org/fx"
35 |
36 | // ======== EXPORTS ========
37 |
38 | // Module exports services present
39 | var Context = fx.Options(
40 | fx.Provide(GetAuthController),
41 | fx.Provide(GetAuthService),
42 | fx.Provide(SetAuthRoutes),
43 | )
44 |
--------------------------------------------------------------------------------
/pkg/auth/auth_controller.go:
--------------------------------------------------------------------------------
1 | /*
2 | Package Name: auth
3 | File Name: auth_controller.go
4 | Abstract: The controller for everything related with authenticating users.
5 |
6 | Author: Alejandro Modroño
7 | Created: 07/08/2023
8 | Last Updated: 07/24/2023
9 |
10 | # MIT License
11 |
12 | # Copyright 2023 Alejandro Modroño Vara
13 |
14 | Permission is hereby granted, free of charge, to any person obtaining a copy
15 | of this software and associated documentation files (the "Software"), to deal
16 | in the Software without restriction, including without limitation the rights
17 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
18 | copies of the Software, and to permit persons to whom the Software is
19 | furnished to do so, subject to the following conditions:
20 |
21 | The above copyright notice and this permission notice shall be included in all
22 | copies or substantial portions of the Software.
23 |
24 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
25 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
26 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
27 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
28 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
29 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
30 | SOFTWARE.
31 | */
32 | package auth
33 |
34 | import (
35 | "errors"
36 | "net/http"
37 |
38 | "github.com/alexmodrono/gin-restapi-template/pkg/common"
39 | "github.com/alexmodrono/gin-restapi-template/pkg/interfaces"
40 | "github.com/alexmodrono/gin-restapi-template/pkg/lib"
41 | "github.com/alexmodrono/gin-restapi-template/pkg/users"
42 | "github.com/gin-gonic/gin"
43 | )
44 |
45 | // ======== TYPES ========
46 |
47 | // AuthController struct
48 | type AuthController struct {
49 | logger lib.Logger
50 | service interfaces.AuthService
51 | usersService users.UsersRepository
52 | }
53 |
54 | type LoginBody struct {
55 | Email string `json:"email" form:"email" binding:"required,email"`
56 | Password string `json:"password" form:"password" binding:"required"`
57 | }
58 |
59 | type SignupBody struct {
60 | Username string `json:"username" form:"username" binding:"required,alpha"`
61 | Email string `json:"email" form:"email" binding:"required,email"`
62 | Password string `json:"password" form:"password" binding:"required"`
63 | ConfirmPassword string `json:"confirm_password" form:"confirm_password" binding:"required,eqfield=Password"`
64 | }
65 |
66 | // ======== METHODS ========
67 |
68 | // GetAuthController retrieves a new auth controller.
69 | func GetAuthController(
70 | logger lib.Logger,
71 | service interfaces.AuthService,
72 | usersService users.UsersRepository,
73 | ) AuthController {
74 | return AuthController{
75 | logger: logger,
76 | service: service,
77 | usersService: usersService,
78 | }
79 | }
80 |
81 | // SignIn signs in user
82 | func (controller AuthController) Login(ctx *gin.Context) {
83 | controller.logger.Info("[POST] Login route.")
84 |
85 | // ======== VALIDATE PARAMETERS ========
86 | // Initilize an empty DTO that represents the parameters
87 | // this route expects.
88 | body := LoginBody{}
89 |
90 | err := ctx.Request.ParseForm()
91 | if err != nil {
92 | // Handle the error if parsing fails.
93 | ctx.AbortWithError(http.StatusInternalServerError, err)
94 | return
95 | }
96 |
97 | // Validate the body and, if successful, assign the
98 | // contents to the DTO.
99 | if errors := common.Validation.ValidateBody(ctx, &body); errors != nil {
100 | ctx.AbortWithStatusJSON(http.StatusBadRequest, errors)
101 | return
102 | }
103 |
104 | // ======== CHECK CREDENTIALS ========
105 | // Retrieve the user from the database by the email.
106 | user, err := controller.usersService.GetUserByEmail(body.Email)
107 | if err != nil {
108 | ctx.AbortWithError(http.StatusBadRequest, err)
109 | return
110 | }
111 |
112 | // Check whether the password is correct using the hasher's
113 | // compare function.
114 | matches, err := common.Hasher.Compare(body.Password, user.Password)
115 | if err != nil {
116 | ctx.AbortWithError(http.StatusInternalServerError, err)
117 | return
118 | }
119 |
120 | if matches {
121 | // Create a JWT token for the user with the subject.
122 | token, err := controller.service.CreateToken(user.ID)
123 | if err != nil {
124 | ctx.AbortWithError(http.StatusInternalServerError, err)
125 | return
126 | }
127 |
128 | // And, finally, return the token.
129 | ctx.JSON(200, gin.H{
130 | "message": "Logged in successfully.",
131 | "token": token,
132 | })
133 | return
134 | }
135 |
136 | ctx.AbortWithError(http.StatusUnauthorized, errors.New("The password provided is incorrect."))
137 | }
138 |
139 | // Register registers user
140 | func (controller AuthController) Signup(ctx *gin.Context) {
141 | controller.logger.Info("[POST] Signup route.")
142 |
143 | // ======== VALIDATE PARAMETERS ========
144 | // Initilize an empty DTO that represents the parameters
145 | // this route expects.
146 | body := SignupBody{}
147 |
148 | // Validate the body and, if successful, assign the
149 | // contents to the DTO.
150 | if errors := common.Validation.ValidateBody(ctx, &body); errors != nil {
151 | ctx.AbortWithStatusJSON(http.StatusBadRequest, errors)
152 | return
153 | }
154 |
155 | // ======== CREATE USER ========
156 |
157 | // Retrieve the user from the database by the email.
158 | id, err := controller.usersService.CreateUser(body.Email, body.Username, body.Password)
159 | if err != nil {
160 | ctx.AbortWithError(http.StatusBadRequest, err)
161 | return
162 | }
163 |
164 | // Create a JWT token for the user with the subject.
165 | token, err := controller.service.CreateToken(*id)
166 | if err != nil {
167 | ctx.AbortWithError(http.StatusInternalServerError, err)
168 | return
169 | }
170 |
171 | // And, finally, return the token.
172 | ctx.JSON(200, gin.H{
173 | "message": "User signed-up successfully.",
174 | "token": token,
175 | })
176 | }
177 |
--------------------------------------------------------------------------------
/pkg/auth/auth_controller_test.go:
--------------------------------------------------------------------------------
1 | package auth
2 |
3 | import (
4 | "bytes"
5 | "encoding/json"
6 | "net/http"
7 | "net/http/httptest"
8 | "testing"
9 |
10 | "github.com/alexmodrono/gin-restapi-template/internal/middlewares"
11 | "github.com/alexmodrono/gin-restapi-template/test/mocks"
12 | "github.com/gin-gonic/gin"
13 | "github.com/stretchr/testify/assert"
14 | )
15 |
16 | func TestAuthController_Login(t *testing.T) {
17 | gin.SetMode(gin.TestMode)
18 | // Initialize a new gin router for testing
19 | router := gin.Default()
20 |
21 | // Sets the errors middleware. If this step is skipped, if a route fails the
22 | // body will just be empty.
23 | errors_middleware := middlewares.GetErrorsMiddleware(mocks.NewMockLogger(), router)
24 | errors_middleware.Setup()
25 |
26 | // Initialize mock logger, mock users service, and mock auth service
27 | logger := &mocks.MockLogger{}
28 | usersService := &mocks.MockUsersService{}
29 | authService := &mocks.MockAuthService{}
30 |
31 | // Create the auth controller for testing
32 | authController := GetAuthController(logger, authService, usersService)
33 | // Add the route to the router
34 | router.POST("/login", authController.Login)
35 |
36 | t.Run("ValidCredentials", func(t *testing.T) {
37 | // Create a request body with valid login credentials
38 | requestBody := LoginBody{
39 | Email: "user@example.com",
40 | Password: "password123",
41 | }
42 | jsonBody, _ := json.Marshal(requestBody)
43 |
44 | // Create a new HTTP request
45 | req, _ := http.NewRequest("POST", "/login", bytes.NewBuffer(jsonBody))
46 | req.Header.Set("Content-Type", "application/json")
47 |
48 | // Perform the request and record the response
49 | w := httptest.NewRecorder()
50 | router.ServeHTTP(w, req)
51 |
52 | // Assert the response status code and body
53 | assert.Equal(t, http.StatusOK, w.Code)
54 |
55 | var response map[string]interface{}
56 | json.Unmarshal(w.Body.Bytes(), &response)
57 |
58 | assert.Equal(t, "Logged in successfully.", response["message"])
59 | assert.Equal(t, "mock_jwt_token", response["token"])
60 | })
61 |
62 | t.Run("InvalidCredentials", func(t *testing.T) {
63 | // Create a request body with valid login credentials
64 | requestBody := LoginBody{
65 | Email: "user@example.com",
66 | Password: "incorrect_password",
67 | }
68 | jsonBody, _ := json.Marshal(requestBody)
69 |
70 | // Create a new HTTP request
71 | req, _ := http.NewRequest("POST", "/login", bytes.NewBuffer(jsonBody))
72 | req.Header.Set("Content-Type", "application/json")
73 |
74 | // Perform the request and record the response
75 | w := httptest.NewRecorder()
76 | router.ServeHTTP(w, req)
77 |
78 | // Assert the response status code and body
79 | assert.Equal(t, http.StatusUnauthorized, w.Code)
80 |
81 | var response map[string]interface{}
82 | json.Unmarshal(w.Body.Bytes(), &response)
83 |
84 | assert.Equal(t, "The password provided is incorrect.", response["error"])
85 | })
86 | }
87 |
--------------------------------------------------------------------------------
/pkg/auth/auth_routes.go:
--------------------------------------------------------------------------------
1 | /*
2 | Package Name: auth
3 | File Name: auth_routes.go
4 | Abstract: The routes for logging in and signing up.
5 |
6 | Author: Alejandro Modroño
7 | Created: 07/08/2023
8 | Last Updated: 07/24/2023
9 |
10 | # MIT License
11 |
12 | # Copyright 2023 Alejandro Modroño Vara
13 |
14 | Permission is hereby granted, free of charge, to any person obtaining a copy
15 | of this software and associated documentation files (the "Software"), to deal
16 | in the Software without restriction, including without limitation the rights
17 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
18 | copies of the Software, and to permit persons to whom the Software is
19 | furnished to do so, subject to the following conditions:
20 |
21 | The above copyright notice and this permission notice shall be included in all
22 | copies or substantial portions of the Software.
23 |
24 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
25 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
26 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
27 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
28 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
29 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
30 | SOFTWARE.
31 | */
32 | package auth
33 |
34 | import "github.com/alexmodrono/gin-restapi-template/pkg/lib"
35 |
36 | // ======== TYPES ========
37 |
38 | // UserRoutes struct
39 | type AuthRoutes struct {
40 | logger lib.Logger
41 | router *lib.Router
42 | authController AuthController
43 | }
44 |
45 | // ======== PUBLIC METHODS ========
46 |
47 | // Returns an AuthRoutes struct.
48 | func SetAuthRoutes(
49 | logger lib.Logger,
50 | router *lib.Router,
51 | authController AuthController,
52 | ) AuthRoutes {
53 | return AuthRoutes{
54 | router: router,
55 | logger: logger,
56 | authController: authController,
57 | }
58 | }
59 |
60 | // Setup the auth routes
61 | func (route AuthRoutes) Setup() {
62 | route.logger.Info("Setting up [AUTH] routes.")
63 | route.router.POST("/login", route.authController.Login)
64 | route.router.POST("/signup", route.authController.Signup)
65 | }
66 |
--------------------------------------------------------------------------------
/pkg/auth/auth_service.go:
--------------------------------------------------------------------------------
1 | /*
2 | Package Name: auth
3 | File Name: auth_service.go
4 | Abstract: The service for verifying and creating JWTs.
5 |
6 | Author: Alejandro Modroño
7 | Created: 07/08/2023
8 | Last Updated: 07/24/2023
9 |
10 | # MIT License
11 |
12 | # Copyright 2023 Alejandro Modroño Vara
13 |
14 | Permission is hereby granted, free of charge, to any person obtaining a copy
15 | of this software and associated documentation files (the "Software"), to deal
16 | in the Software without restriction, including without limitation the rights
17 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
18 | copies of the Software, and to permit persons to whom the Software is
19 | furnished to do so, subject to the following conditions:
20 |
21 | The above copyright notice and this permission notice shall be included in all
22 | copies or substantial portions of the Software.
23 |
24 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
25 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
26 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
27 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
28 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
29 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
30 | SOFTWARE.
31 | */
32 | package auth
33 |
34 | import (
35 | "os"
36 | "time"
37 |
38 | "github.com/alexmodrono/gin-restapi-template/pkg/interfaces"
39 | "github.com/alexmodrono/gin-restapi-template/pkg/lib"
40 | "github.com/golang-jwt/jwt/v5"
41 | )
42 |
43 | // ======== TYPES ========
44 |
45 | // AuthService service layer
46 | type AuthService struct {
47 | db *lib.Database
48 | }
49 |
50 | // ======== METHODS ========
51 |
52 | // GetUserService returns the user service.
53 | func GetAuthService(db *lib.Database) interfaces.AuthService {
54 | return AuthService{
55 | db: db,
56 | }
57 | }
58 |
59 | // CheckToken checks whether the token is correct and returns the subject, which
60 | // in the case of our API is supposed to be the id of the user.
61 | func (service AuthService) CheckToken(tokenString string) (*int32, error) {
62 | // Parse takes the token string and a function for looking up the key. The latter is especially
63 | // useful if you use multiple keys for your application. The standard is to use 'kid' in the
64 | // head of the token to identify which key to use, but the parsed token (head and claims) is provided
65 | // to the callback, providing flexibility.
66 | token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
67 | return []byte(os.Getenv("SECRET_KEY")), nil
68 | })
69 | if err != nil {
70 | return nil, err
71 | }
72 |
73 | if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
74 | subFloat := claims["sub"].(float64)
75 | sub := int32(subFloat)
76 | return &sub, nil
77 | }
78 |
79 | return nil, err
80 | }
81 |
82 | // CreateToken creates jwt auth token
83 | func (service AuthService) CreateToken(id int32) (*string, error) {
84 | token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
85 | "sub": id,
86 | "iat": time.Now().Unix(),
87 | "exp": time.Now().AddDate(0, 0, 15).Unix(),
88 | })
89 |
90 | tokenString, err := token.SignedString([]byte(os.Getenv("SECRET_KEY")))
91 | if err != nil {
92 | return nil, err
93 | }
94 |
95 | return &tokenString, nil
96 | }
97 |
--------------------------------------------------------------------------------
/pkg/common/hasher.go:
--------------------------------------------------------------------------------
1 | /*
2 | Package Name: common
3 | File Name: hasher.go
4 | Abstract: Hasher provides helper functions for encoding/decoding
5 | strings with the argon2 algorithm.
6 |
7 | Author: Alejandro Modroño
8 | Created: 07/12/2023
9 | Last Updated: 07/24/2023
10 |
11 | # MIT License
12 |
13 | # Copyright 2023 Alejandro Modroño Vara
14 |
15 | Permission is hereby granted, free of charge, to any person obtaining a copy
16 | of this software and associated documentation files (the "Software"), to deal
17 | in the Software without restriction, including without limitation the rights
18 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
19 | copies of the Software, and to permit persons to whom the Software is
20 | furnished to do so, subject to the following conditions:
21 |
22 | The above copyright notice and this permission notice shall be included in all
23 | copies or substantial portions of the Software.
24 |
25 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
26 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
27 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
28 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
29 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
30 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
31 | SOFTWARE.
32 | */
33 | package common
34 |
35 | import (
36 | "crypto/rand"
37 | "crypto/subtle"
38 | "encoding/base64"
39 | "errors"
40 | "fmt"
41 | "strings"
42 |
43 | "golang.org/x/crypto/argon2"
44 | )
45 |
46 | // ======== TYPES ========
47 |
48 | // Extracted from https://www.alexedwards.net/blog/how-to-hash-and-verify-passwords-with-argon2-in-go
49 | //
50 | // The Argon2 algorithm accepts a number of configurable parameters:
51 | // - Memory: The amount of memory used by the algorithm (in kibibytes).
52 | // - Iterations: The number of iterations (or passes) over the memory.
53 | // - Parallelism: The number of threads (or lanes) used by the algorithm.
54 | // - Salt length: Length of the random salt. 16 bytes is recommended for
55 | // password hashing.
56 | // - Key length: Length of the generated key (or password hash).
57 | // 16 bytes or more is recommended.
58 | //
59 | // The memory and iterations parameters control the computational cost of
60 | // hashing the password. The higher these figures are, the greater the cost
61 | // of generating the hash. It also follows that the greater the cost will be
62 | // for any attacker trying to guess the password.
63 | //
64 | // But there's a balance that you need to strike. As you increase the cost,
65 | // the time taken to generate the hash also increases. If you're generating
66 | // the hash in response to a user action (like signing up or logging in to
67 | // a website) then you probably want to keep the runtime to less than 500ms
68 | // to avoid a negative user experience.
69 | //
70 | // If the Argon2 algorithm is running on a machine with multiple cores, then
71 | // one way to decrease the runtime without reducing the cost is to increase
72 | // the parallelism parameter. This controls the number of threads that the
73 | // work is spread across. There's an important thing to note here though:
74 | // changing the value of the parallelism parameter changes the output of the
75 | // algorithm. So — for example — running Argon2 with a parallelism parameter
76 | // of 2 will result in a different password hash to running it with a parallelism
77 | // parameter of 4.
78 | type Parameters struct {
79 | memory uint32
80 | iterations uint32
81 | parallelism uint8
82 | saltLength uint32
83 | keyLength uint32
84 | }
85 |
86 | // ======== NAMESPACES ========
87 |
88 | // hasherT is used for creating a namespace
89 | type hasherT struct{}
90 |
91 | // the Hasher namespace
92 | var Hasher hasherT
93 |
94 | // ======== ERRORS ========
95 | var (
96 | InvalidHashException = errors.New("The encoded hash is not in the correct format.")
97 | IncompatibleVersionException = errors.New("Incompatible version of argon2.")
98 | )
99 |
100 | // ======== PUBLIC METHODS ========
101 |
102 | // Hasher.hash returns a hash from a string.
103 | func (hasherT) Hash(from_string string) (encodedHash string, err error) {
104 | // Set the parameters to be used by the argon2 algorithm.
105 | params := &Parameters{
106 | memory: 64 * 1024,
107 | iterations: 3,
108 | parallelism: 2,
109 | saltLength: 16,
110 | keyLength: 32,
111 | }
112 |
113 | // Generate a random salt to be appended to the hash.
114 | salt, err := generateRandomBytes(params.saltLength)
115 | if err != nil {
116 | return "", err
117 | }
118 |
119 | // Generate the hash
120 | hash := argon2.IDKey(
121 | []byte(from_string),
122 | salt,
123 | params.iterations,
124 | params.memory,
125 | params.parallelism,
126 | params.keyLength,
127 | )
128 |
129 | // Base64 encode the salt and hashed password.
130 | b64Salt := base64.RawStdEncoding.EncodeToString(salt)
131 | b64Hash := base64.RawStdEncoding.EncodeToString(hash)
132 |
133 | // Return a string using the standard encoded hash representation.
134 | encodedHash = fmt.Sprintf(
135 | "$argon2id$v=%d$m=%d,t=%d,p=%d$%s$%s",
136 | argon2.Version,
137 | params.memory,
138 | params.iterations,
139 | params.parallelism,
140 | b64Salt,
141 | b64Hash,
142 | )
143 |
144 | return encodedHash, nil
145 | }
146 |
147 | // Hasher.compare compares a plaintext string with a hash and returns whether
148 | // they match or not.
149 | func (hasherT) Compare(plaintext, encodedHash string) (matches bool, err error) {
150 | // Decodes the salt and hash from base64 and extracts the parameters,
151 | // salt and derived key from the encoded password hash.
152 | params, salt, hash, err := decode(encodedHash)
153 | if err != nil {
154 | return false, err
155 | }
156 |
157 | // Derive the key from the other password using the same parameters.
158 | otherHash := argon2.IDKey(
159 | []byte(plaintext),
160 | salt,
161 | params.iterations,
162 | params.memory,
163 | params.parallelism,
164 | params.keyLength,
165 | )
166 |
167 | // Check that the contents of the hashed passwords are identical. Note
168 | // that we are using the subtle.ConstantTimeCompare() function for this
169 | // to help prevent timing attacks.
170 | if subtle.ConstantTimeCompare(hash, otherHash) == 1 {
171 | return true, nil
172 | }
173 | return false, nil
174 | }
175 |
176 | // ======== PRIVATE METHODS ========
177 |
178 | // GenerateRandomBytes generates a random salt that will be appended
179 | // to the hash.
180 | func generateRandomBytes(n uint32) ([]byte, error) {
181 | b := make([]byte, n)
182 | if _, err := rand.Read(b); err != nil {
183 | return nil, err
184 | }
185 |
186 | return b, nil
187 | }
188 |
189 | // Hasher.decodeHash decodes the salt and hash and extracts the parameters
190 | // from an argon2 hash.
191 | func decode(encodedHash string) (params *Parameters, salt, hash []byte, err error) {
192 | vals := strings.Split(encodedHash, "$")
193 | if len(vals) != 6 {
194 | return nil, nil, nil, InvalidHashException
195 | }
196 |
197 | var version int
198 | if _, err = fmt.Sscanf(vals[2], "v=%d", &version); err != nil {
199 | return nil, nil, nil, err
200 | }
201 | if version != argon2.Version {
202 | return nil, nil, nil, IncompatibleVersionException
203 | }
204 |
205 | params = &Parameters{}
206 | _, err = fmt.Sscanf(vals[3], "m=%d,t=%d,p=%d", ¶ms.memory, ¶ms.iterations, ¶ms.parallelism)
207 | if err != nil {
208 | return nil, nil, nil, err
209 | }
210 |
211 | salt, err = base64.RawStdEncoding.Strict().DecodeString(vals[4])
212 | if err != nil {
213 | return nil, nil, nil, err
214 | }
215 | params.saltLength = uint32(len(salt))
216 |
217 | hash, err = base64.RawStdEncoding.Strict().DecodeString(vals[5])
218 | if err != nil {
219 | return nil, nil, nil, err
220 | }
221 | params.keyLength = uint32(len(hash))
222 |
223 | return params, salt, hash, nil
224 | }
225 |
--------------------------------------------------------------------------------
/pkg/common/hasher_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | Package Name: common
3 | File Name: hasher_test.go
4 | Abstract: Tests for the hasher functions
5 | Author: Alejandro Modroño
6 | Created: 07/26/2023
7 | Last Updated: 07/26/2023
8 |
9 | # MIT License
10 |
11 | # Copyright 2023 Alejandro Modroño Vara
12 |
13 | Permission is hereby granted, free of charge, to any person obtaining a copy
14 | of this software and associated documentation files (the "Software"), to deal
15 | in the Software without restriction, including without limitation the rights
16 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | copies of the Software, and to permit persons to whom the Software is
18 | furnished to do so, subject to the following conditions:
19 |
20 | The above copyright notice and this permission notice shall be included in all
21 | copies or substantial portions of the Software.
22 |
23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 | SOFTWARE.
30 | */
31 | package common
32 |
33 | import (
34 | "encoding/base64"
35 | "testing"
36 |
37 | "github.com/stretchr/testify/assert"
38 | "github.com/stretchr/testify/require"
39 | )
40 |
41 | func TestHasher_HashAndCompare(t *testing.T) {
42 | // Test case 1: Valid hash and comparison
43 | password := "mySecretPassword"
44 |
45 | // Generate the hash for the password
46 | hashedPassword, err := Hasher.Hash(password)
47 | require.NoError(t, err)
48 |
49 | // Test that the password matches the hash
50 | matches, err := Hasher.Compare(password, hashedPassword)
51 | assert.True(t, matches)
52 | assert.NoError(t, err)
53 |
54 | // Test case 2: Invalid comparison
55 | wrongPassword := "wrongPassword"
56 |
57 | // Test that the wrong password does not match the hash
58 | matches, err = Hasher.Compare(wrongPassword, hashedPassword)
59 | assert.False(t, matches)
60 | assert.NoError(t, err)
61 | }
62 |
63 | func TestHasher_Decode(t *testing.T) {
64 | // Test case 1: Valid encoded hash
65 | encodedHash := "$argon2id$v=19$m=65536,t=3,p=2$Zm9v$MTIzNDU2"
66 |
67 | params, salt, hash, err := decode(encodedHash)
68 | require.NoError(t, err)
69 | assert.NotNil(t, params)
70 | assert.Equal(t, uint32(65536), params.memory)
71 | assert.Equal(t, uint32(3), params.iterations)
72 | assert.Equal(t, uint8(2), params.parallelism)
73 | assert.Equal(t, uint32(3), params.saltLength)
74 | assert.Equal(t, uint32(6), params.keyLength)
75 |
76 | expectedSalt, _ := base64.RawStdEncoding.Strict().DecodeString("Zm9v")
77 | expectedHash, _ := base64.RawStdEncoding.Strict().DecodeString("MTIzNDU2")
78 |
79 | assert.Equal(t, expectedSalt, salt)
80 | assert.Equal(t, expectedHash, hash)
81 |
82 | // Test case 2: Invalid encoded hash
83 | invalidHash := "invalidHash"
84 |
85 | _, _, _, err = decode(invalidHash)
86 | assert.Error(t, err)
87 | }
88 |
89 | func TestHasher_Decode_InvalidHash(t *testing.T) {
90 | // Test case 3: Invalid encoded hash with less than 6 parts
91 | encodedHash := "$argon2id$v=19$m=65536,t=3,p=2$Zm9v"
92 |
93 | _, _, _, err := decode(encodedHash)
94 | assert.EqualError(t, err, InvalidHashException.Error())
95 |
96 | // Test case 4: Invalid encoded hash with incompatible version
97 | encodedHash = "$argon2id$v=18$m=65536,t=3,p=2$Zm9v$MTIzNDU2"
98 |
99 | _, _, _, err = decode(encodedHash)
100 | assert.EqualError(t, err, IncompatibleVersionException.Error())
101 | }
102 |
--------------------------------------------------------------------------------
/pkg/common/validation.go:
--------------------------------------------------------------------------------
1 | /*
2 | Package Name: lib
3 | File Name: database.go
4 | Abstract: This file contains functions for validating the body of a request.
5 | Author: Alejandro Modroño
6 | Created: 07/22/2023
7 | Last Updated: 07/24/2023
8 |
9 | # MIT License
10 |
11 | # Copyright 2023 Alejandro Modroño Vara
12 |
13 | Permission is hereby granted, free of charge, to any person obtaining a copy
14 | of this software and associated documentation files (the "Software"), to deal
15 | in the Software without restriction, including without limitation the rights
16 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | copies of the Software, and to permit persons to whom the Software is
18 | furnished to do so, subject to the following conditions:
19 |
20 | The above copyright notice and this permission notice shall be included in all
21 | copies or substantial portions of the Software.
22 |
23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 | SOFTWARE.
30 | */
31 | package common
32 |
33 | import (
34 | "errors"
35 | "reflect"
36 | "strings"
37 |
38 | "github.com/gin-gonic/gin"
39 | "github.com/go-playground/validator/v10"
40 | )
41 |
42 | // ======== NAMESPACES ========
43 |
44 | // validationT is used for creating a namespace
45 | type validationT struct{}
46 |
47 | // the Validation namespace
48 | var Validation validationT
49 |
50 | // ======== TYPES ========
51 |
52 | // ErrorMsg represents an error message returned by the API
53 | // when the validation of the body parameters fails.
54 | type ValidationErrorMessage struct {
55 | Field string `json:"field"`
56 | Message string `json:"message"`
57 | }
58 |
59 | // ======== PUBLIC METHODS ========
60 |
61 | // BodyIsValid binds the body of a gin request to the given struct,
62 | // and aborts the operation with user-friendly error messages if any
63 | // parameters are missing.
64 | func (validationT) ValidateBody(ctx *gin.Context, body interface{}) *gin.H {
65 | // Check the Content-Type of the request
66 | if err := ctx.ShouldBind(body); err != nil {
67 | var ve validator.ValidationErrors
68 | if errors.As(err, &ve) {
69 | out := make([]ValidationErrorMessage, len(ve))
70 | for i, fe := range ve {
71 | out[i] = ValidationErrorMessage{
72 | Field: getJSONFieldName(reflect.TypeOf(body).Elem(), fe.Field()),
73 | Message: getValidationErrorMessage(fe),
74 | }
75 | }
76 | return &gin.H{"errors": out}
77 | }
78 | }
79 |
80 | return nil
81 | }
82 |
83 | // ======== PRIVATE METHODS ========
84 |
85 | // Helper function to get the form field name
86 | func getFormFieldName(field string) string {
87 | // Implement your logic to map the field name as needed for form data.
88 | // This could be based on your specific naming conventions.
89 | return field
90 | }
91 |
92 | // getJSONFieldName returns the json field tag of a field.
93 | func getJSONFieldName(structType reflect.Type, fieldName string) string {
94 | field, found := structType.FieldByName(fieldName)
95 | if !found {
96 | return fieldName
97 | }
98 |
99 | jsonTag := field.Tag.Get("json")
100 | if jsonTag == "" {
101 | return fieldName
102 | }
103 |
104 | parts := strings.Split(jsonTag, ",")
105 | return parts[0]
106 | }
107 |
108 | // GetValidationErrorMessage returns a more user-friendly validation error
109 | func getValidationErrorMessage(error validator.FieldError) string {
110 | switch error.Tag() {
111 | case "required":
112 | return "This field is required."
113 | case "lte":
114 | return "This field should be less than or equal to " + error.Param() + "."
115 | case "gte":
116 | return "This field should be greater than or equal to " + error.Param() + "."
117 | case "email":
118 | return "Please enter a valid email address."
119 | case "eqfield":
120 | return "Must be equal to " + error.Param() + "."
121 | }
122 | return error.Tag()
123 | }
124 |
--------------------------------------------------------------------------------
/pkg/common/validation_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | Package Name: common
3 | File Name: validation_test.go
4 | Abstract: Tests for the body validation functions.
5 | Author: Alejandro Modroño
6 | Created: 07/26/2023
7 | Last Updated: 07/26/2023
8 |
9 | # MIT License
10 |
11 | # Copyright 2023 Alejandro Modroño Vara
12 |
13 | Permission is hereby granted, free of charge, to any person obtaining a copy
14 | of this software and associated documentation files (the "Software"), to deal
15 | in the Software without restriction, including without limitation the rights
16 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | copies of the Software, and to permit persons to whom the Software is
18 | furnished to do so, subject to the following conditions:
19 |
20 | The above copyright notice and this permission notice shall be included in all
21 | copies or substantial portions of the Software.
22 |
23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 | SOFTWARE.
30 | */
31 |
32 | package common
33 |
34 | import (
35 | "bytes"
36 | "encoding/json"
37 | "net/http"
38 | "net/http/httptest"
39 | "testing"
40 |
41 | "github.com/gin-gonic/gin"
42 | "github.com/stretchr/testify/assert"
43 | )
44 |
45 | func TestValidation_ValidateBody_Valid(t *testing.T) {
46 | gin.SetMode(gin.TestMode)
47 | // Initialize a new gin router for testing
48 | router := gin.New()
49 |
50 | // Define a test struct that mimics the request body
51 | type TestBody struct {
52 | Name string `json:"name" binding:"required"`
53 | Email string `json:"email" binding:"required,email"`
54 | }
55 |
56 | // Define a test request body in JSON format
57 | requestBodyJSON := `{"name": "John Doe", "email": "john.doe@example.com"}`
58 |
59 | // Create a new request with the test JSON body
60 | req, err := http.NewRequest("POST", "/test", bytes.NewBufferString(requestBodyJSON))
61 | assert.NoError(t, err)
62 |
63 | // Set the request Content-Type header to application/json
64 | req.Header.Set("Content-Type", "application/json")
65 |
66 | // Create a new response recorder for capturing the response
67 | recorder := httptest.NewRecorder()
68 |
69 | // Define the test handler function that calls ValidateBody
70 | testHandler := func(ctx *gin.Context) {
71 | var body TestBody
72 | errors := Validation.ValidateBody(ctx, &body)
73 | if errors != nil {
74 | ctx.JSON(http.StatusBadRequest, errors)
75 | return
76 | }
77 | ctx.JSON(http.StatusOK, gin.H{"message": "Validation passed successfully"})
78 | }
79 |
80 | // Register the testHandler as the handler for the test route
81 | router.POST("/test", testHandler)
82 |
83 | // Perform the test request
84 | router.ServeHTTP(recorder, req)
85 |
86 | // Assert that the response status code is 200 OK
87 | assert.Equal(t, http.StatusOK, recorder.Code)
88 |
89 | // Parse the response body into a map
90 | var response map[string]string
91 | err = json.Unmarshal(recorder.Body.Bytes(), &response)
92 | assert.NoError(t, err)
93 |
94 | // Assert that the response contains the success message
95 | assert.Equal(t, "Validation passed successfully", response["message"])
96 | }
97 |
98 | func TestValidation_ValidateBody_Invalid(t *testing.T) {
99 | // Initialize a new gin router for testing
100 | router := gin.Default()
101 |
102 | // Define a test struct that mimics the request body
103 | type TestBody struct {
104 | Name string `json:"name" binding:"required"`
105 | Email string `json:"email" binding:"required,email"`
106 | }
107 |
108 | // Define a test request body in JSON format with missing required fields
109 | requestBodyJSON := `{"name": ""}`
110 |
111 | // Create a new request with the test JSON body
112 | req, err := http.NewRequest("POST", "/test", bytes.NewBufferString(requestBodyJSON))
113 | assert.NoError(t, err)
114 |
115 | // Set the request Content-Type header to application/json
116 | req.Header.Set("Content-Type", "application/json")
117 |
118 | // Create a new response recorder for capturing the response
119 | recorder := httptest.NewRecorder()
120 |
121 | // Define the test handler function that calls ValidateBody
122 | testHandler := func(ctx *gin.Context) {
123 | var body TestBody
124 | errors := Validation.ValidateBody(ctx, &body)
125 | if errors != nil {
126 | ctx.JSON(http.StatusBadRequest, errors)
127 | return
128 | }
129 | ctx.JSON(http.StatusOK, gin.H{"message": "Validation passed successfully"})
130 | }
131 |
132 | // Register the testHandler as the handler for the test route
133 | router.POST("/test", testHandler)
134 |
135 | // Perform the test request
136 | router.ServeHTTP(recorder, req)
137 |
138 | // Assert that the response status code is 400 Bad Request
139 | assert.Equal(t, http.StatusBadRequest, recorder.Code)
140 |
141 | // Parse the response body into a slice of ValidationErrorMessage
142 | var responseErrors map[string][]ValidationErrorMessage
143 | err = json.Unmarshal(recorder.Body.Bytes(), &responseErrors)
144 | assert.NoError(t, err)
145 |
146 | // Assert that the response errors contain the expected validation errors
147 | expectedErrors := map[string][]ValidationErrorMessage{
148 | "errors": {
149 | {Field: "name", Message: "This field is required."},
150 | {Field: "email", Message: "This field is required."},
151 | },
152 | }
153 | assert.Equal(t, expectedErrors, responseErrors)
154 | }
155 |
--------------------------------------------------------------------------------
/pkg/interfaces/auth_service_interface.go:
--------------------------------------------------------------------------------
1 | /*
2 | Package Name: interfaces
3 | File Name: auth_service_interface.go
4 | Abstract: Interface for the AuthService used for avoiding import/dependency cycles
5 | and allowing to mock these services in tests.
6 | Author: Alejandro Modroño
7 | Created: 07/22/2023
8 | Last Updated: 07/24/2023
9 |
10 | # MIT License
11 |
12 | # Copyright 2023 Alejandro Modroño Vara
13 |
14 | Permission is hereby granted, free of charge, to any person obtaining a copy
15 | of this software and associated documentation files (the "Software"), to deal
16 | in the Software without restriction, including without limitation the rights
17 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
18 | copies of the Software, and to permit persons to whom the Software is
19 | furnished to do so, subject to the following conditions:
20 |
21 | The above copyright notice and this permission notice shall be included in all
22 | copies or substantial portions of the Software.
23 |
24 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
25 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
26 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
27 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
28 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
29 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
30 | SOFTWARE.
31 | */
32 | package interfaces
33 |
34 | // ======== INTERFACES ========
35 |
36 | // The interface for the AuthService.
37 | type AuthService interface {
38 | // CheckToken checks whether a token is valid and returns the
39 | // subject of the payload.
40 | CheckToken(tokenString string) (*int32, error)
41 |
42 | // CreateToken return a token for a subject.
43 | CreateToken(id int32) (*string, error)
44 | }
45 |
--------------------------------------------------------------------------------
/pkg/lib/database.go:
--------------------------------------------------------------------------------
1 | /*
2 | Package Name: lib
3 | File Name: database.go
4 | Abstract: This file in the 'lib' package contains a method named 'GetDatabase'
5 | that enables asynchronous connection to a PostgreSQL database. Using the 'pgxpool'
6 | library, it establishes a database pool by constructing the connection URL from
7 | environment variables. The method returns the database pool for seamless database interaction
8 | and logs the connection status using a provided logger. It plays a crucial role in
9 | facilitating database connectivity in the 'lib' package.
10 | Author: Alejandro Modroño
11 | Created: 07/08/2023
12 | Last Updated: 07/24/2023
13 |
14 | # MIT License
15 |
16 | # Copyright 2023 Alejandro Modroño Vara
17 |
18 | Permission is hereby granted, free of charge, to any person obtaining a copy
19 | of this software and associated documentation files (the "Software"), to deal
20 | in the Software without restriction, including without limitation the rights
21 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
22 | copies of the Software, and to permit persons to whom the Software is
23 | furnished to do so, subject to the following conditions:
24 |
25 | The above copyright notice and this permission notice shall be included in all
26 | copies or substantial portions of the Software.
27 |
28 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
29 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
30 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
31 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
32 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
33 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
34 | SOFTWARE.
35 | */
36 | package lib
37 |
38 | import (
39 | "context"
40 | "fmt"
41 | "os"
42 |
43 | "github.com/jackc/pgx/v5/pgxpool"
44 | )
45 |
46 | // ======== TYPES ========
47 |
48 | // A type alias for the connection pool
49 | type Database = pgxpool.Pool
50 |
51 | // ======== METHODS ========
52 |
53 | // GetDatabase returns a database pool to connect to the database asynchronously
54 | func GetDatabase(logger Logger) *Database {
55 |
56 | url := fmt.Sprintf(
57 | "postgres://%s:%s@%s:%s/%s",
58 | os.Getenv("DATABASE_USERNAME"),
59 | os.Getenv("DATABASE_PASSWORD"),
60 | os.Getenv("DATABASE_HOST"),
61 | os.Getenv("DATABASE_PORT"),
62 | os.Getenv("DATABASE_NAME"),
63 | )
64 |
65 | // Create a connection pool to the database using pgxpool
66 | dbPool, err := pgxpool.New(context.Background(), url)
67 | if err != nil {
68 | logger.Fatal("Unable to connect to database: ", err)
69 | os.Exit(1)
70 | }
71 |
72 | logger.Info("Connected to the database successfully.")
73 | // Closes the pool once the function goes out of scope.
74 | // defer dbPool.Close()
75 |
76 | return dbPool
77 | }
78 |
--------------------------------------------------------------------------------
/pkg/lib/lib.go:
--------------------------------------------------------------------------------
1 | /*
2 | Package Name: lib
3 | File Name: lib.go
4 | Abstract: The fx provider for allowing dependency injection
5 | for the lib files.
6 |
7 | Author: Alejandro Modroño
8 | Created: 07/08/2023
9 | Last Updated: 07/24/2023
10 |
11 | # MIT License
12 |
13 | # Copyright 2023 Alejandro Modroño Vara
14 |
15 | Permission is hereby granted, free of charge, to any person obtaining a copy
16 | of this software and associated documentation files (the "Software"), to deal
17 | in the Software without restriction, including without limitation the rights
18 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
19 | copies of the Software, and to permit persons to whom the Software is
20 | furnished to do so, subject to the following conditions:
21 |
22 | The above copyright notice and this permission notice shall be included in all
23 | copies or substantial portions of the Software.
24 |
25 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
26 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
27 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
28 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
29 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
30 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
31 | SOFTWARE.
32 | */
33 | package lib
34 |
35 | import "go.uber.org/fx"
36 |
37 | // ======== EXPORTS ========
38 |
39 | // Module exports dependency
40 | var Module = fx.Options(
41 | fx.Provide(
42 | GetLogger,
43 | GetDatabase,
44 | GetRouter,
45 | ),
46 | )
47 |
--------------------------------------------------------------------------------
/pkg/lib/logger.go:
--------------------------------------------------------------------------------
1 | /*
2 | Package Name: lib
3 | File Name: logger.go
4 | Abstract: The logger used for logging data.
5 |
6 | Author: Alejandro Modroño
7 | Created: 07/08/2023
8 | Last Updated: 07/24/2023
9 |
10 | # MIT License
11 |
12 | # Copyright 2023 Alejandro Modroño Vara
13 |
14 | Permission is hereby granted, free of charge, to any person obtaining a copy
15 | of this software and associated documentation files (the "Software"), to deal
16 | in the Software without restriction, including without limitation the rights
17 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
18 | copies of the Software, and to permit persons to whom the Software is
19 | furnished to do so, subject to the following conditions:
20 |
21 | The above copyright notice and this permission notice shall be included in all
22 | copies or substantial portions of the Software.
23 |
24 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
25 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
26 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
27 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
28 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
29 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
30 | SOFTWARE.
31 | */
32 | package lib
33 |
34 | import (
35 | "os"
36 |
37 | "github.com/withmandala/go-log"
38 | )
39 |
40 | // ======== TYPES ========
41 |
42 | // loggerInterface represents the logging functionality used in the code.
43 | // This is done to enable mocking the logger in tests.
44 | type Logger interface {
45 | Info(args ...interface{})
46 | Fatal(args ...interface{})
47 | Error(args ...interface{})
48 | }
49 |
50 | // NewHandler returns a new gin router
51 | func GetLogger() Logger {
52 | logger := log.New(os.Stderr)
53 | return logger
54 | }
55 |
--------------------------------------------------------------------------------
/pkg/lib/router.go:
--------------------------------------------------------------------------------
1 | /*
2 | Package Name: lib
3 | File Name: logger.go
4 | Abstract: The route handler.
5 |
6 | Author: Alejandro Modroño
7 | Created: 07/08/2023
8 | Last Updated: 07/24/2023
9 |
10 | # MIT License
11 |
12 | # Copyright 2023 Alejandro Modroño Vara
13 |
14 | Permission is hereby granted, free of charge, to any person obtaining a copy
15 | of this software and associated documentation files (the "Software"), to deal
16 | in the Software without restriction, including without limitation the rights
17 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
18 | copies of the Software, and to permit persons to whom the Software is
19 | furnished to do so, subject to the following conditions:
20 |
21 | The above copyright notice and this permission notice shall be included in all
22 | copies or substantial portions of the Software.
23 |
24 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
25 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
26 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
27 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
28 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
29 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
30 | SOFTWARE.
31 | */
32 | package lib
33 |
34 | import (
35 | "time"
36 |
37 | ginzap "github.com/gin-contrib/zap"
38 | "github.com/gin-gonic/gin"
39 | "go.uber.org/zap"
40 | )
41 |
42 | // ======== TYPES ========
43 |
44 | // Router type
45 | type Router = gin.Engine
46 |
47 | // ======== METHODS ========
48 |
49 | // GetRouter retrieves the router used by the API.
50 | func GetRouter() *Router {
51 |
52 | // ======== ROUTER ========
53 | router := gin.New()
54 |
55 | // ======== LOGGER ========
56 | logger, _ := zap.NewProduction()
57 |
58 | // Add a ginzap middleware, which:
59 | // - Logs all requests, like a combined access and error log.
60 | // - Logs to stdout.
61 | // - RFC3339 with UTC time format.
62 | router.Use(ginzap.Ginzap(logger, time.RFC3339, true))
63 |
64 | // Logs all panic to error log
65 | // - stack means whether output the stack info.
66 | router.Use(ginzap.RecoveryWithZap(logger, true))
67 |
68 | // ======== ERROR HANDLING ========
69 |
70 | // ======== SETTINGS ========
71 | router.Use(gin.Recovery())
72 | router.SetTrustedProxies(nil)
73 |
74 | return router
75 |
76 | }
77 |
--------------------------------------------------------------------------------
/pkg/users/users.go:
--------------------------------------------------------------------------------
1 | /*
2 | Package Name: users
3 | File Name: users.go
4 | Abstract: Wrapper for exposing to fx all the components of the 'users' context.
5 |
6 | Author: Alejandro Modroño
7 | Created: 07/08/2023
8 | Last Updated: 07/24/2023
9 |
10 | # MIT License
11 |
12 | # Copyright 2023 Alejandro Modroño Vara
13 |
14 | Permission is hereby granted, free of charge, to any person obtaining a copy
15 | of this software and associated documentation files (the "Software"), to deal
16 | in the Software without restriction, including without limitation the rights
17 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
18 | copies of the Software, and to permit persons to whom the Software is
19 | furnished to do so, subject to the following conditions:
20 |
21 | The above copyright notice and this permission notice shall be included in all
22 | copies or substantial portions of the Software.
23 |
24 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
25 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
26 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
27 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
28 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
29 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
30 | SOFTWARE.
31 | */
32 | package users
33 |
34 | import "go.uber.org/fx"
35 |
36 | // ======== EXPORTS ========
37 |
38 | // Module exports services present
39 | var Context = fx.Options(
40 | fx.Provide(GetUsersController),
41 | fx.Provide(GetUsersService),
42 | fx.Provide(SetUsersRoutes),
43 | )
44 |
--------------------------------------------------------------------------------
/pkg/users/users_controller.go:
--------------------------------------------------------------------------------
1 | /*
2 | Package Name: users
3 | File Name: users_controller.go
4 | Abstract: The user controller for performing operations after a route is called.
5 |
6 | Author: Alejandro Modroño
7 | Created: 07/08/2023
8 | Last Updated: 07/24/2023
9 |
10 | # MIT License
11 |
12 | # Copyright 2023 Alejandro Modroño Vara
13 |
14 | Permission is hereby granted, free of charge, to any person obtaining a copy
15 | of this software and associated documentation files (the "Software"), to deal
16 | in the Software without restriction, including without limitation the rights
17 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
18 | copies of the Software, and to permit persons to whom the Software is
19 | furnished to do so, subject to the following conditions:
20 |
21 | The above copyright notice and this permission notice shall be included in all
22 | copies or substantial portions of the Software.
23 |
24 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
25 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
26 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
27 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
28 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
29 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
30 | SOFTWARE.
31 | */
32 | package users
33 |
34 | import (
35 | "errors"
36 | "net/http"
37 | "strconv"
38 |
39 | "github.com/alexmodrono/gin-restapi-template/pkg/lib"
40 | "github.com/gin-gonic/gin"
41 | )
42 |
43 | // ======== TYPES ========
44 |
45 | // UsersController data type
46 | type UsersController struct {
47 | // service domains.UserService
48 | logger lib.Logger
49 | service UsersRepository
50 | }
51 |
52 | // ======== METHODS ========
53 |
54 | // Creates a new user controller and exposes its routes
55 | // to the router.
56 | func GetUsersController(logger lib.Logger, service UsersRepository) UsersController {
57 | return UsersController{
58 | logger: logger,
59 | service: service,
60 | }
61 | }
62 |
63 | func (controller UsersController) Get(ctx *gin.Context) {
64 | // Get the id from the context
65 | idParam := ctx.Param("id")
66 | controller.logger.Info("[GET] Getting user with id", idParam)
67 |
68 | // ======== TYPE CONVERSION ========
69 | // Convert the id from string to int
70 | id, err := strconv.Atoi(idParam)
71 | if err != nil {
72 | ctx.AbortWithError(http.StatusBadRequest, errors.New("The id must be an int."))
73 | return
74 | }
75 |
76 | // ======== RETRIEVE USER ========
77 | internalUser, err := controller.service.GetUserById(id)
78 | if err != nil {
79 | ctx.AbortWithError(http.StatusBadRequest, err)
80 | return
81 | }
82 |
83 | // The controller.service.GetUser(int) function returns a models.InternalUser
84 | // struct, which contains the password. To avoid exposing this data to the
85 | // end user, we must convert the internal user to a public user as follows:
86 | publicUser := internalUser.ToPublic()
87 |
88 | // We can now return the user
89 | ctx.JSON(http.StatusOK, publicUser)
90 | }
91 |
92 | func (controller UsersController) GetAll(ctx *gin.Context) {
93 | controller.logger.Info("[GET] Getting all users.")
94 |
95 | // ======== RETRIEVE USER ========
96 | internalUsers, err := controller.service.GetUsers()
97 | if err != nil {
98 | ctx.AbortWithError(http.StatusBadRequest, err)
99 | return
100 | }
101 |
102 | // The controller.service.GetUser(int) function returns a models.InternalUser
103 | // struct, which contains the password. To avoid exposing this data to the
104 | // end user, we must convert the internal user to a public user as follows:
105 | publicUsers := make([]PublicUser, len(internalUsers))
106 | for user := range internalUsers {
107 | publicUsers[user] = internalUsers[user].ToPublic()
108 | }
109 |
110 | // We can now return the user
111 | ctx.JSON(http.StatusOK, publicUsers)
112 | }
113 |
--------------------------------------------------------------------------------
/pkg/users/users_model.go:
--------------------------------------------------------------------------------
1 | /*
2 | Package Name: users
3 | File Name: users_controller.go
4 | Abstract: A representation of a user in the database.
5 |
6 | Author: Alejandro Modroño
7 | Created: 07/08/2023
8 | Last Updated: 07/24/2023
9 |
10 | # MIT License
11 |
12 | # Copyright 2023 Alejandro Modroño Vara
13 |
14 | Permission is hereby granted, free of charge, to any person obtaining a copy
15 | of this software and associated documentation files (the "Software"), to deal
16 | in the Software without restriction, including without limitation the rights
17 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
18 | copies of the Software, and to permit persons to whom the Software is
19 | furnished to do so, subject to the following conditions:
20 |
21 | The above copyright notice and this permission notice shall be included in all
22 | copies or substantial portions of the Software.
23 |
24 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
25 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
26 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
27 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
28 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
29 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
30 | SOFTWARE.
31 | */
32 | package users
33 |
34 | import (
35 | "time"
36 | )
37 |
38 | // ======== TYPES ========
39 |
40 | // InternalUser is a struct that represents a user, and it contains its password.
41 | // As its own name suggests, this type should only be used internally.
42 | type InternalUser struct {
43 | ID int32
44 | Username string
45 | Email string
46 | Password string
47 | CreatedAt time.Time
48 | }
49 |
50 | // PublicUser is basically a user that will be returned by the api. As its own
51 | // name says, it should be used for returning user data publicly.
52 | type PublicUser struct {
53 | ID int32 `json:"id"`
54 | Username string `json:"username"`
55 | Email string `json:"email"`
56 | CreatedAt time.Time `json:"created_at"`
57 | }
58 |
59 | // ======== PUBLIC METHODS ========
60 |
61 | // Converts an internal user to a public user.
62 | func (self InternalUser) ToPublic() PublicUser {
63 | return PublicUser{
64 | ID: self.ID,
65 | Username: self.Username,
66 | Email: self.Email,
67 | CreatedAt: self.CreatedAt,
68 | }
69 | }
70 |
71 | // Creates a new instance of an internal user from data.
72 | func InternalUserFromData(values []interface{}) InternalUser {
73 | return InternalUser{
74 | ID: values[0].(int32),
75 | Username: values[1].(string),
76 | Email: values[2].(string),
77 | Password: values[3].(string),
78 | CreatedAt: values[4].(time.Time),
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/pkg/users/users_repository.go:
--------------------------------------------------------------------------------
1 | /*
2 | Package Name: users
3 | File Name: users_repository.go
4 | Abstract: Interface for the UsersService used for avoiding import/dependency cycles
5 | and allowing to mock these services in tests.
6 | Author: Alejandro Modroño
7 | Created: 07/26/2023
8 | Last Updated: 07/26/2023
9 |
10 | # MIT License
11 |
12 | # Copyright 2023 Alejandro Modroño Vara
13 |
14 | Permission is hereby granted, free of charge, to any person obtaining a copy
15 | of this software and associated documentation files (the "Software"), to deal
16 | in the Software without restriction, including without limitation the rights
17 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
18 | copies of the Software, and to permit persons to whom the Software is
19 | furnished to do so, subject to the following conditions:
20 |
21 | The above copyright notice and this permission notice shall be included in all
22 | copies or substantial portions of the Software.
23 |
24 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
25 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
26 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
27 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
28 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
29 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
30 | SOFTWARE.
31 | */
32 | package users
33 |
34 | // ======== INTERFACES ========
35 |
36 | // The interface for the AuthService.
37 | type UsersRepository interface {
38 | GetUserById(id int) (*InternalUser, error)
39 |
40 | GetUserByEmail(email string) (*InternalUser, error)
41 |
42 | GetUsers() (users []InternalUser, err error)
43 |
44 | CreateUser(email string, username string, password string) (*int32, error)
45 | }
46 |
--------------------------------------------------------------------------------
/pkg/users/users_routes.go:
--------------------------------------------------------------------------------
1 | /*
2 | Package Name: users
3 | File Name: users_routes.go
4 | Abstract: The file containing all the user routes.
5 |
6 | Author: Alejandro Modroño
7 | Created: 07/08/2023
8 | Last Updated: 07/24/2023
9 |
10 | # MIT License
11 |
12 | # Copyright 2023 Alejandro Modroño Vara
13 |
14 | Permission is hereby granted, free of charge, to any person obtaining a copy
15 | of this software and associated documentation files (the "Software"), to deal
16 | in the Software without restriction, including without limitation the rights
17 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
18 | copies of the Software, and to permit persons to whom the Software is
19 | furnished to do so, subject to the following conditions:
20 |
21 | The above copyright notice and this permission notice shall be included in all
22 | copies or substantial portions of the Software.
23 |
24 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
25 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
26 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
27 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
28 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
29 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
30 | SOFTWARE.
31 | */
32 | package users
33 |
34 | import (
35 | "github.com/alexmodrono/gin-restapi-template/internal/middlewares"
36 | "github.com/alexmodrono/gin-restapi-template/pkg/lib"
37 | )
38 |
39 | // ======== TYPES ========
40 |
41 | // UsersRoutes struct
42 | type UsersRoutes struct {
43 | logger lib.Logger
44 | router *lib.Router
45 | usersController UsersController
46 | authMiddleware middlewares.AuthMiddleware
47 | }
48 |
49 | // ======== PUBLIC METHODS ========
50 |
51 | // Returns a UserRoutes struct.
52 | func SetUsersRoutes(
53 | logger lib.Logger,
54 | router *lib.Router,
55 | usersController UsersController,
56 | authMiddleware middlewares.AuthMiddleware,
57 | ) UsersRoutes {
58 | return UsersRoutes{
59 | logger: logger,
60 | router: router,
61 | usersController: usersController,
62 | authMiddleware: authMiddleware,
63 | }
64 | }
65 |
66 | // Setup the user routes
67 | func (route UsersRoutes) Setup() {
68 | route.logger.Info("Setting up [USERS] routes.")
69 | api := route.router.Group("/users").Use(route.authMiddleware.Handler())
70 | {
71 | api.GET("/", route.usersController.GetAll)
72 | api.GET("/:id", route.usersController.Get)
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/pkg/users/users_service.go:
--------------------------------------------------------------------------------
1 | /*
2 | Package Name: users
3 | File Name: users_service.go
4 | Abstract: The user service for performing operations upon users in the database.
5 |
6 | Author: Alejandro Modroño
7 | Created: 07/08/2023
8 | Last Updated: 07/24/2023
9 |
10 | # MIT License
11 |
12 | # Copyright 2023 Alejandro Modroño Vara
13 |
14 | Permission is hereby granted, free of charge, to any person obtaining a copy
15 | of this software and associated documentation files (the "Software"), to deal
16 | in the Software without restriction, including without limitation the rights
17 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
18 | copies of the Software, and to permit persons to whom the Software is
19 | furnished to do so, subject to the following conditions:
20 |
21 | The above copyright notice and this permission notice shall be included in all
22 | copies or substantial portions of the Software.
23 |
24 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
25 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
26 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
27 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
28 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
29 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
30 | SOFTWARE.
31 | */
32 | package users
33 |
34 | import (
35 | "context"
36 | "errors"
37 | "fmt"
38 | "time"
39 |
40 | "github.com/alexmodrono/gin-restapi-template/pkg/common"
41 | "github.com/alexmodrono/gin-restapi-template/pkg/lib"
42 | "github.com/jackc/pgx/v5/pgconn"
43 | )
44 |
45 | // ======== TYPES ========
46 |
47 | // UsersService service layer
48 | type UsersService struct {
49 | logger lib.Logger
50 | db *lib.Database
51 | }
52 |
53 | // ======== PUBLIC METHODS ========
54 |
55 | // GetUsersService returns the user service.
56 | func GetUsersService(logger lib.Logger, db *lib.Database) UsersRepository {
57 | return UsersService{
58 | logger: logger,
59 | db: db,
60 | }
61 | }
62 |
63 | // GetUserById returns a single user with the specified id.
64 | //
65 | // NOTE: This query returns the user with its hashed password, so make sure to convert its value to
66 | // a models.PublicUser struct which omits the password.
67 | func (service UsersService) GetUserById(id int) (*InternalUser, error) {
68 | service.logger.Info("Retrieving user with id", id)
69 | return service.getUserByQuery("id", id)
70 | }
71 |
72 | // GetUserByEmail returns a single user with the specified email.
73 | //
74 | // NOTE: This query returns the user with its hashed password, so make sure to convert its value to
75 | // a models.PublicUser struct which omits the password.
76 | func (service UsersService) GetUserByEmail(email string) (*InternalUser, error) {
77 | service.logger.Info("Retrieving user with email", email)
78 | return service.getUserByQuery("email", email)
79 | }
80 |
81 | // GetUsers returns all the users
82 | func (service UsersService) GetUsers() (users []InternalUser, err error) {
83 | rows, err := service.db.Query(context.Background(), "SELECT * FROM auth.user;")
84 | service.logger.Info("Retrieving all users.")
85 | if err != nil {
86 | service.logger.Fatal("Error while executing query. Err:", err)
87 | return nil, err
88 | }
89 |
90 | var results []InternalUser
91 |
92 | for rows.Next() {
93 | values, err := rows.Values()
94 | if err != nil {
95 | service.logger.Fatal("Error while iterating dataset. Err:", err)
96 | return nil, err
97 | }
98 |
99 | // Once the values have been obtained, they need to be converted into Go
100 | // types.
101 | results = append(results, InternalUserFromData(values))
102 | }
103 |
104 | return results, nil
105 | }
106 |
107 | // CreateUser inserts a new user in the database
108 | func (service UsersService) CreateUser(email string, username string, password string) (*int32, error) {
109 |
110 | // ======== HASHING THE PASSWORD ========
111 | hashedPassword, err := common.Hasher.Hash(password)
112 | if err != nil {
113 | service.logger.Fatal("An error ocurred while hashing the password:", err)
114 | return nil, err
115 | }
116 |
117 | // ======== QUERIES ========
118 | var id int32
119 | err = service.db.QueryRow(
120 | context.Background(),
121 | `INSERT INTO auth.user VALUES (DEFAULT, $1, $2, $3, $4) RETURNING id;`,
122 | username,
123 | email,
124 | hashedPassword,
125 | time.Now(),
126 | ).Scan(&id)
127 | if err != nil {
128 | return handleError(err, username, email)
129 | }
130 |
131 | // Return the first user in the result set.
132 | return &id, nil
133 | }
134 |
135 | // TODO: add update and delete operations for users.
136 |
137 | // ======== PRIVATE METHODS ========
138 |
139 | // Converts an error to a more user-friendly error.
140 | func handleError(err error, username string, email string) (*int32, error) {
141 | // Check if the error is a PostgreSQL error (*pgconn.PgError)
142 | // and handle unique constraint violations based on the constraint name.
143 | if pgerr, ok := err.(*pgconn.PgError); ok {
144 | if pgerr.ConstraintName == "user_username_unique" {
145 | // The username already exists, return a specific error message.
146 | return nil, fmt.Errorf("Username %s is already taken.", username)
147 | } else if pgerr.ConstraintName == "user_email_unique" {
148 | // The email already exists, return a specific error message.
149 | return nil, fmt.Errorf("User with email %s already exists.", email)
150 | } else {
151 | // Handle other PostgreSQL errors.
152 | return nil, fmt.Errorf("Unexpected error while performing operation on user %s: %v\n", email, pgerr)
153 | }
154 | }
155 | // Handle other types of errors (non-PostgreSQL errors).
156 | return nil, fmt.Errorf("Unexpected error while performing operation on user %s: %v\n", email, err)
157 | }
158 |
159 | // getUserByQuery returns a user from the database based on a specific query.
160 | //
161 | // The auth.get_user_by_id($1) SQL function is a custom function designed to retrieve a user with a specific ID.
162 | // Alternatively, you can directly execute a query like:
163 | //
164 | // SELECT u.id, u.username, u.email, u.password, u.created_at FROM auth.user u WHERE u.id = $1;
165 | //
166 | // Similarly, the auth.get_user_by_email($1) function allows you to retrieve a user based on their email.
167 | // You can use the following query as an alternative:
168 | //
169 | // SELECT u.id, u.username, u.email, u.password, u.created_at FROM auth.user u WHERE u.email = $1;
170 | //
171 | // Likewise, the auth.get_user_by_username($1) function retrieves a user based on their username.
172 | // The equivalent query can be used as an alternative:
173 | //
174 | // SELECT u.id, u.username, u.email, u.password, u.created_at FROM auth.user u WHERE u.username = $1;
175 | func (service UsersService) getUserByQuery(queryType string, args ...interface{}) (*InternalUser, error) {
176 | var query string
177 | switch queryType {
178 | case "id":
179 | query = "SELECT * FROM auth.get_user_by_id($1)"
180 | case "email":
181 | query = "SELECT * FROM auth.get_user_by_email($1)"
182 | case "username":
183 | query = "SELECT * FROM auth.get_user_by_username($1)"
184 | default:
185 | return nil, errors.New("Invalid query type")
186 | }
187 |
188 | rows, err := service.db.Query(context.Background(), query, args...)
189 | if err != nil {
190 | service.logger.Fatal("Error while executing query. Err:", err)
191 | return nil, err
192 | }
193 |
194 | // iterate through the rows
195 | for rows.Next() {
196 | values, err := rows.Values()
197 | if err != nil {
198 | service.logger.Fatal("Error while iterating dataset. Err:", err)
199 | return nil, err
200 | }
201 |
202 | // Once the values have been obtained, they need to be converted into Go types.
203 | user := InternalUserFromData(values)
204 |
205 | return &user, nil
206 | }
207 |
208 | var message string
209 | if val, ok := args[0].(int); ok {
210 | message = fmt.Sprintf(
211 | "The user with the %s '%d' could not be found.",
212 | queryType,
213 | val,
214 | )
215 | } else if val, ok := args[0].(string); ok {
216 | message = fmt.Sprintf(
217 | "The user with the %s '%s' could not be found.",
218 | queryType,
219 | val,
220 | )
221 | } else {
222 | // Handle the case when args[0] is neither int nor string.
223 | message = "Invalid value for the user query."
224 | }
225 |
226 | return nil, errors.New(message)
227 | }
228 |
--------------------------------------------------------------------------------
/sql/create_query_functions.sql:
--------------------------------------------------------------------------------
1 | /*
2 | File Name: create_query_functions.sql
3 | Abstract: This file contains functions that provide a convenient way
4 | to interact with the database as they encapsulate common operations
5 | and help reduce the complexity and length of the queries when using
6 | the database driver.
7 |
8 | Author: Alejandro Modroño
9 | Created: 07/10/2023
10 | Last Updated: 07/12/2023
11 | */
12 |
13 | -- ======== QUERY FUNCTIONS ========
14 | -- ===== FILTER QUERIES =====
15 | -- This fuction returns username, email, and created_at values
16 | -- for the given input user id
17 | CREATE OR REPLACE FUNCTION auth.get_user_by_id(for_id int)
18 | RETURNS TABLE
19 | (
20 | id integer,
21 | username varchar,
22 | email varchar,
23 | password varchar,
24 | created_at date
25 | )
26 | language plpgsql
27 | AS
28 | $$
29 | BEGIN
30 | RETURN QUERY
31 | SELECT u.id, u.username, u.email, u.password, u.created_at
32 | FROM auth.user u
33 | WHERE u.id = for_id;
34 | END
35 | $$;
36 |
37 | -- This fuction returns username, email, and created_at values
38 | -- for the given input user email
39 | CREATE OR REPLACE FUNCTION auth.get_user_by_email(for_email varchar)
40 | RETURNS TABLE
41 | (
42 | id integer,
43 | username varchar,
44 | email varchar,
45 | password varchar,
46 | created_at date
47 | )
48 | language plpgsql
49 | AS
50 | $$
51 | BEGIN
52 | RETURN QUERY
53 | SELECT u.id, u.username, u.email, u.password, u.created_at
54 | FROM auth.user u
55 | WHERE u.email = for_email;
56 | END
57 | $$;
58 |
59 | -- This fuction returns username, email, and created_at values
60 | -- for the given input username
61 | CREATE OR REPLACE FUNCTION auth.get_user_by_username(for_username varchar)
62 | RETURNS TABLE
63 | (
64 | id integer,
65 | username varchar,
66 | email varchar,
67 | password varchar,
68 | created_at date
69 | )
70 | language plpgsql
71 | AS
72 | $$
73 | BEGIN
74 | RETURN QUERY
75 | SELECT u.id, u.username, u.email, u.password, u.created_at
76 | FROM auth.user u
77 | WHERE u.username = for_username;
78 | END
79 | $$;
--------------------------------------------------------------------------------
/sql/create_users_table.sql:
--------------------------------------------------------------------------------
1 | /*
2 | File Name: create_query_functions.sql
3 | Abstract: This file contains functions that provide a convenient way
4 | to interact with the database as they encapsulate common operations
5 | and help reduce the complexity and length of the queries when using
6 | the database driver.
7 |
8 | Author: Alejandro Modroño
9 | Created: 07/10/2023
10 | Last Updated: 07/12/2023
11 | */
12 |
13 | -- ======== SCHEMAS ========
14 | CREATE SCHEMA auth;
15 |
16 | -- ======== TABLES ========
17 | CREATE TABLE auth.user
18 | (
19 | -- ======== KEYS ========
20 | id SERIAL not null
21 | primary key,
22 | username varchar(100) not null,
23 | email varchar(100) not null,
24 | password varchar(100) not null,
25 | created_at date not null,
26 |
27 | -- ======== CONSTRAINTS ========
28 | CONSTRAINT user_email_unique UNIQUE (email),
29 | CONSTRAINT user_username_unique UNIQUE (username)
30 | );
31 |
32 | ALTER TABLE auth.user
33 | owner to api;
34 |
--------------------------------------------------------------------------------
/test/README.md:
--------------------------------------------------------------------------------
1 | In software development, testing plays a critical role in ensuring the quality and reliability of a codebase. Unit testing and integration testing are two fundamental approaches used to validate software components and their interactions. Organizing test files and test targets in a structured manner is essential for maintaining a clear and maintainable testing suite.
2 |
3 | 1. **Unit Testing:**
4 | Unit tests focus on verifying the correctness of individual units or components of code in isolation. A unit can be a function, method, or class that performs a specific task within the codebase. These tests ensure that each unit behaves as expected, identifying bugs early in the development process and allowing developers to refactor with confidence.
5 |
6 | For the purpose of organization, in this template (and in `Go` projects in general) unit tests are typically placed in the same folder as the corresponding test target. This makes it easier to locate and manage the tests for a particular unit.
7 |
8 | Example file tree for a Go project:
9 |
10 | ```
11 | project/
12 | └── internal/
13 | ├── module_a.go
14 | ├── module_a_test.go # Unit test for module_a.go
15 | ├── module_b.go
16 | └── module_b_test.go # Unit test for module_b.go
17 | ```
18 |
19 | 2. **Integration Testing:**
20 | Integration tests, on the other hand, are designed to validate the interactions between multiple units or packages, ensuring that they work seamlessly together as a whole. Unlike unit tests that focus on isolated functionality, integration tests mimic real-world scenarios and check for the integration points' correctness.
21 |
22 | Since integration tests span throughout multiple packages and are not specific to a single file, they are often organized separately in a designated folder. This promotes a clear separation between unit and integration tests, making it easier to distinguish between different test types.
23 |
24 | Example file tree for a Go project:
25 |
26 | ```
27 | project/
28 | ├── internal/
29 | │ ├── module_a.go
30 | │ ├── module_a_test.go # Unit test module_a.go
31 | │ ├── module_b.go
32 | │ └── module_b_test.go # Unit test module_b.go
33 | └── test/
34 | └── integration_test.go # Integration test for module_a.go & module_b.go
35 | ```
36 |
37 | By adhering to this organized approach, development teams can maintain a well-structured and easily navigable testing suite. This helps to improve code quality, enhance collaboration among team members, and facilitate faster and more efficient bug identification and resolution. Additionally, having clear separation between unit and integration tests allows developers to run them selectively based on their needs and testing objectives.
38 |
--------------------------------------------------------------------------------
/test/integration/signup_test.go:
--------------------------------------------------------------------------------
1 | package integration
2 |
3 | import (
4 | "bytes"
5 | "net/http"
6 | "net/http/httptest"
7 | "testing"
8 |
9 | "github.com/stretchr/testify"
10 | )
11 |
12 | // func TestBodyValidation(t *testing.T) {
13 | // body := {}
14 | // }
15 |
--------------------------------------------------------------------------------
/test/mocks/auth_service_mock.go:
--------------------------------------------------------------------------------
1 | /*
2 | Package Name: mocks
3 | File Name: auth_service_mock.go
4 | Abstract: Interface for mocking the auth service in tests.
5 | Author: Alejandro Modroño
6 | Created: 07/26/2023
7 | Last Updated: 07/26/2023
8 |
9 | # MIT License
10 |
11 | # Copyright 2023 Alejandro Modroño Vara
12 |
13 | Permission is hereby granted, free of charge, to any person obtaining a copy
14 | of this software and associated documentation files (the "Software"), to deal
15 | in the Software without restriction, including without limitation the rights
16 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | copies of the Software, and to permit persons to whom the Software is
18 | furnished to do so, subject to the following conditions:
19 |
20 | The above copyright notice and this permission notice shall be included in all
21 | copies or substantial portions of the Software.
22 |
23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 | SOFTWARE.
30 | */
31 | package mocks
32 |
33 | // Mock AuthService for testing purposes
34 | type MockAuthService struct{}
35 |
36 | func (s *MockAuthService) CreateToken(userID int32) (*string, error) {
37 | // Mock the CreateToken method to return a test JWT token for testing.
38 | // You can replace this with any logic to generate a mock JWT token for testing.
39 | token := "mock_jwt_token"
40 | return &token, nil
41 | }
42 |
43 | func (s *MockAuthService) CheckToken(tokenString string) (*int32, error) {
44 | // Mock the CheckToken method to return the subject of the payload of a JWT
45 | // for testing.
46 | // You can replace this with any logic to generate a random ID for testing.
47 | sub := int32(1)
48 | return &sub, nil
49 | }
50 |
--------------------------------------------------------------------------------
/test/mocks/logger_mock.go:
--------------------------------------------------------------------------------
1 | /*
2 | Package Name: mocks
3 | File Name: logger_mock.go
4 | Abstract: Interface for mocking the logger in tests.
5 | Author: Alejandro Modroño
6 | Created: 07/26/2023
7 | Last Updated: 07/26/2023
8 |
9 | # MIT License
10 |
11 | # Copyright 2023 Alejandro Modroño Vara
12 |
13 | Permission is hereby granted, free of charge, to any person obtaining a copy
14 | of this software and associated documentation files (the "Software"), to deal
15 | in the Software without restriction, including without limitation the rights
16 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | copies of the Software, and to permit persons to whom the Software is
18 | furnished to do so, subject to the following conditions:
19 |
20 | The above copyright notice and this permission notice shall be included in all
21 | copies or substantial portions of the Software.
22 |
23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 | SOFTWARE.
30 | */
31 | package mocks
32 |
33 | // MockLogger is a mock implementation of the Logger interface for testing purposes.
34 | type MockLogger struct {
35 | // Add fields or methods here if needed for capturing log messages or other testing purposes.
36 | }
37 |
38 | // Info is the mocked Info method for testing.
39 | func (m *MockLogger) Info(args ...interface{}) {
40 | // Implement the logic to capture log messages or perform testing actions.
41 | }
42 |
43 | // Fatal is the mocked Fatal method for testing.
44 | func (m *MockLogger) Fatal(args ...interface{}) {
45 | // Implement the logic to capture log messages or perform testing actions.
46 | }
47 |
48 | // Error is the mocked Error method for testing.
49 | func (m *MockLogger) Error(args ...interface{}) {
50 | // Implement the logic to capture log messages or perform testing actions.
51 | }
52 |
53 | // NewMockLogger returns a new instance of the MockLogger.
54 | func NewMockLogger() *MockLogger {
55 | return &MockLogger{}
56 | }
57 |
--------------------------------------------------------------------------------
/test/mocks/users_service_mock.go:
--------------------------------------------------------------------------------
1 | /*
2 | Package Name: mocks
3 | File Name: users_service_mock.go
4 | Abstract: Interface for mocking the users service in tests.
5 | Author: Alejandro Modroño
6 | Created: 07/26/2023
7 | Last Updated: 07/26/2023
8 |
9 | # MIT License
10 |
11 | # Copyright 2023 Alejandro Modroño Vara
12 |
13 | Permission is hereby granted, free of charge, to any person obtaining a copy
14 | of this software and associated documentation files (the "Software"), to deal
15 | in the Software without restriction, including without limitation the rights
16 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | copies of the Software, and to permit persons to whom the Software is
18 | furnished to do so, subject to the following conditions:
19 |
20 | The above copyright notice and this permission notice shall be included in all
21 | copies or substantial portions of the Software.
22 |
23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 | SOFTWARE.
30 | */
31 | package mocks
32 |
33 | import (
34 | "errors"
35 | "time"
36 |
37 | "github.com/alexmodrono/gin-restapi-template/pkg/common"
38 | "github.com/alexmodrono/gin-restapi-template/pkg/users"
39 | )
40 |
41 | // Mock UsersService for testing purposes
42 | type MockUsersService struct{}
43 |
44 | func (s *MockUsersService) GetUserById(id int) (*users.InternalUser, error) {
45 | // Mock the GetUserByEmail method to return a test user with a known password
46 | // for testing the login functionality.
47 | if id == 1 {
48 | password, _ := common.Hasher.Hash("password123")
49 | return &users.InternalUser{
50 | ID: 1,
51 | Username: "user",
52 | Email: "user@example.com",
53 | Password: password,
54 | }, nil
55 | }
56 | return nil, errors.New("user not found")
57 | }
58 |
59 | func (s *MockUsersService) GetUserByEmail(email string) (*users.InternalUser, error) {
60 | // Mock the GetUserByEmail method to return a test user with a known password
61 | // for testing the login functionality.
62 | if email == "user@example.com" {
63 | password, _ := common.Hasher.Hash("password123")
64 | return &users.InternalUser{
65 | ID: 1,
66 | Username: "user",
67 | Email: "user@example.com",
68 | Password: password,
69 | }, nil
70 | }
71 | return nil, errors.New("user not found")
72 | }
73 |
74 | func (s *MockUsersService) GetUsers() ([]users.InternalUser, error) {
75 | users := []users.InternalUser{
76 | {
77 | ID: 1,
78 | Username: "user",
79 | Email: "user@example.com",
80 | Password: "$argon2id$v=18$m=65536,t=3,p=2$Zm9v$MTIzNDU2",
81 | CreatedAt: time.Now(),
82 | },
83 | {
84 | ID: 2,
85 | Username: "user2",
86 | Email: "user2@example.com",
87 | Password: "$argon2id$v=18$m=65536,t=3,p=2$Zm9v$MTIzNDU2",
88 | CreatedAt: time.Now(),
89 | },
90 | }
91 | return users, nil
92 | }
93 |
94 | func (s *MockUsersService) CreateUser(email, username, password string) (*int32, error) {
95 | // Mock the CreateUser method to return a test user ID for the signup functionality.
96 | // You can replace this with any logic to generate a mock user ID for testing.
97 | userID := int32(1)
98 | return &userID, nil
99 | }
100 |
--------------------------------------------------------------------------------