├── .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 | ![banner](./assets/banner.png) 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 | --------------------------------------------------------------------------------