diff --git a/.env.example b/.env.example deleted file mode 100644 index 9e6bef4..0000000 --- a/.env.example +++ /dev/null @@ -1,13 +0,0 @@ -APP_ENV=development -SERVER_ADDRESS=:8080 -PORT=8080 -CONTEXT_TIMEOUT=2 -DB_HOST=mongodb -DB_PORT=27017 -DB_USER= -DB_PASS= -DB_NAME=go-backend-clean-architecture-db -ACCESS_TOKEN_EXPIRY_HOUR = 2 -REFRESH_TOKEN_EXPIRY_HOUR = 168 -ACCESS_TOKEN_SECRET=access_token_secret -REFRESH_TOKEN_SECRET=refresh_token_secret \ No newline at end of file diff --git a/.gitignore b/.gitignore index 620b1b8..e29ddfc 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,6 @@ bin/ /.idea .idea .env -.vscode \ No newline at end of file +.vscode +go.mod +go.sum \ No newline at end of file diff --git a/README.md b/README.md deleted file mode 100644 index eebfa61..0000000 --- a/README.md +++ /dev/null @@ -1,343 +0,0 @@ -# Go Backend Clean Architecture - -A Go (Golang) Backend Clean Architecture project with Gin, MongoDB, JWT Authentication Middleware, Test, and Docker. - -![Go Backend Clean Architecture](https://github.com/amitshekhariitbhu/go-backend-clean-architecture/blob/main/assets/go-backend-clean-architecture.png?raw=true) - -**You can use this project as a template to build your Backend project in the Go language on top of this project.** - -Before creating this project, I have gone through more than 20 projects related to the Go(Golang) Clean Architecture on GitHub. - -Thanks to all those projects, I learned a lot from all of those. As I keep saying: - -> The best way to learn to code is to code. But, to write good code, you will also have to read good code. Make a habit of reading good code. You can find many open-source projects on GitHub and start reading. - -Then for the implementation part, I combined all of my ideas, experiences, and learnings from those projects to create this project. - -And as always I would love to get feedback on my project. This helps everyone and most importantly me. - -Learn about this project architecture in detail from the blogs mentioned below: - -- [Go Backend Clean Architecture](https://amitshekhar.me/blog/go-backend-clean-architecture) -- [Go JWT Authentication Middleware](https://amitshekhar.me/blog/go-jwt-authentication-middleware) -- [Configuration with Viper in Go](https://amitshekhar.me/blog/configuration-with-viper-in-go) -- [Test with Testify and Mockery in Go](https://amitshekhar.me/blog/test-with-testify-and-mockery-in-go) - -## Architecture Layers of the project - -- Router -- Controller -- Usecase -- Repository -- Domain - -![Go Backend Clean Architecture Diagram](https://github.com/amitshekhariitbhu/go-backend-clean-architecture/blob/main/assets/go-backend-arch-diagram.png?raw=true) - -## About me - -Hi, I am [**Amit Shekhar**](https://amitshekhar.me), I have mentored many developers, and their efforts landed them high-paying tech jobs, helped many tech companies in solving their unique problems, and created many open-source libraries being used by top companies. I am passionate about sharing knowledge through open-source, blogs, and videos. - -You can connect with me on: - -- [Twitter](https://twitter.com/amitiitbhu) -- [YouTube](https://www.youtube.com/@amitshekhar) -- [LinkedIn](https://www.linkedin.com/in/amit-shekhar-iitbhu) -- [GitHub](https://github.com/amitshekhariitbhu) - -## System Design Playlist on YouTube - -- [What is System Design?](https://www.youtube.com/watch?v=i4YWRY3hsdA) -- [Twitter Timeline Design with Fanout Approach - System Design](https://www.youtube.com/watch?v=_7qHGfwgPz0) -- [HTTP Request vs HTTP Long-Polling vs WebSocket vs Server-Sent Events](https://www.youtube.com/watch?v=8ksWRX4xV-s) -- [YouTube Video Upload Service - System Design](https://www.youtube.com/watch?v=N0vvJTkokZc) -- [What is Consistent Hashing?](https://www.youtube.com/watch?v=dV5cIm9T3ss) -- [Capacity Estimation: Back-of-the-envelope calculation - Twitter](https://www.youtube.com/watch?v=yrbKxzXm6_Q) - -## Major Packages used in this project - -- **gin**: Gin is an HTTP web framework written in Go (Golang). It features a Martini-like API with much better performance -- up to 40 times faster. If you need a smashing performance, get yourself some Gin. -- **mongo go driver**: The Official Golang driver for MongoDB. -- **jwt**: JSON Web Tokens are an open, industry-standard RFC 7519 method for representing claims securely between two parties. Used for Access Token and Refresh Token. -- **viper**: For loading configuration from the `.env` file. Go configuration with fangs. Find, load, and unmarshal a configuration file in JSON, TOML, YAML, HCL, INI, envfile, or Java properties formats. -- **bcrypt**: Package bcrypt implements Provos and Mazières's bcrypt adaptive hashing algorithm. -- **testify**: A toolkit with common assertions and mocks that plays nicely with the standard library. -- **mockery**: A mock code autogenerator for Golang used in testing. -- Check more packages in `go.mod`. - -### Public API Request Flow without JWT Authentication Middleware - -![Public API Request Flow](https://github.com/amitshekhariitbhu/go-backend-clean-architecture/blob/main/assets/go-arch-public-api-request-flow.png?raw=true) - -### Private API Request Flow with JWT Authentication Middleware - -> JWT Authentication Middleware for Access Token Validation. - -![Private API Request Flow](https://github.com/amitshekhariitbhu/go-backend-clean-architecture/blob/main/assets/go-arch-private-api-request-flow.png?raw=true) - -### How to run this project? - -We can run this Go Backend Clean Architecture project with or without Docker. Here, I am providing both ways to run this project. - -- Clone this project - -```bash -# Move to your workspace -cd your-workspace - -# Clone this project into your workspace -git clone https://github.com/amitshekhariitbhu/go-backend-clean-architecture.git - -# Move to the project root directory -cd go-backend-clean-architecture -``` - -#### Run without Docker - -- Create a file `.env` similar to `.env.example` at the root directory with your configuration. -- Install `go` if not installed on your machine. -- Install `MongoDB` if not installed on your machine. -- Important: Change the `DB_HOST` to `localhost` (`DB_HOST=localhost`) in `.env` configuration file. `DB_HOST=mongodb` is needed only when you run with Docker. -- Run `go run cmd/main.go`. -- Access API using `http://localhost:8080` - -#### Run with Docker - -- Create a file `.env` similar to `.env.example` at the root directory with your configuration. -- Install Docker and Docker Compose. -- Run `docker-compose up -d`. -- Access API using `http://localhost:8080` - -### How to run the test? - -```bash -# Run all tests -go test ./... -``` - -### How to generate the mock code? - -In this project, to test, we need to generate mock code for the use-case, repository, and database. - -```bash -# Generate mock code for the usecase and repository -mockery --dir=domain --output=domain/mocks --outpkg=mocks --all - -# Generate mock code for the database -mockery --dir=mongo --output=mongo/mocks --outpkg=mocks --all -``` - -Whenever you make changes in the interfaces of these use-cases, repositories, or databases, you need to run the corresponding command to regenerate the mock code for testing. - -### The Complete Project Folder Structure - -``` -. -├── Dockerfile -├── api -│ ├── controller -│ │ ├── login_controller.go -│ │ ├── profile_controller.go -│ │ ├── profile_controller_test.go -│ │ ├── refresh_token_controller.go -│ │ ├── signup_controller.go -│ │ └── task_controller.go -│ ├── middleware -│ │ └── jwt_auth_middleware.go -│ └── route -│ ├── login_route.go -│ ├── profile_route.go -│ ├── refresh_token_route.go -│ ├── route.go -│ ├── signup_route.go -│ └── task_route.go -├── bootstrap -│ ├── app.go -│ ├── database.go -│ └── env.go -├── cmd -│ └── main.go -├── docker-compose.yaml -├── domain -│ ├── error_response.go -│ ├── jwt_custom.go -│ ├── login.go -│ ├── profile.go -│ ├── refresh_token.go -│ ├── signup.go -│ ├── success_response.go -│ ├── task.go -│ └── user.go -├── go.mod -├── go.sum -├── internal -│ └── tokenutil -│ └── tokenutil.go -├── mongo -│ └── mongo.go -├── repository -│ ├── task_repository.go -│ ├── user_repository.go -│ └── user_repository_test.go -└── usecase - ├── login_usecase.go - ├── profile_usecase.go - ├── refresh_token_usecase.go - ├── signup_usecase.go - ├── task_usecase.go - └── task_usecase_test.go -``` - -### API documentation of Go Backend Clean Architecture - - - View API Doc Button - - -### Example API Request and Response - -- signup - - - Request - - ``` - curl --location --request POST 'http://localhost:8080/signup' \ - --data-urlencode 'email=test@gmail.com' \ - --data-urlencode 'password=test' \ - --data-urlencode 'name=Test Name' - ``` - - - Response - - ```json - { - "accessToken": "access_token", - "refreshToken": "refresh_token" - } - ``` - -- login - - - Request - - ``` - curl --location --request POST 'http://localhost:8080/login' \ - --data-urlencode 'email=test@gmail.com' \ - --data-urlencode 'password=test' - ``` - - - Response - - ```json - { - "accessToken": "access_token", - "refreshToken": "refresh_token" - } - ``` - -- profile - - - Request - - ``` - curl --location --request GET 'http://localhost:8080/profile' \ - --header 'Authorization: Bearer access_token' - ``` - - - Response - - ```json - { - "name": "Test Name", - "email": "test@gmail.com" - } - ``` - -- task create - - - Request - - ``` - curl --location --request POST 'http://localhost:8080/task' \ - --header 'Authorization: Bearer access_token' \ - --header 'Content-Type: application/x-www-form-urlencoded' \ - --data-urlencode 'title=Test Task' - ``` - - - Response - - ```json - { - "message": "Task created successfully" - } - ``` - -- task fetch - - - Request - - ``` - curl --location --request GET 'http://localhost:8080/task' \ - --header 'Authorization: Bearer access_token' - ``` - - - Response - - ```json - [ - { - "title": "Test Task" - }, - { - "title": "Test Another Task" - } - ] - ``` - -- refresh token - - - Request - - ``` - curl --location --request POST 'http://localhost:8080/refresh' \ - --header 'Content-Type: application/x-www-form-urlencoded' \ - --data-urlencode 'refreshToken=refresh_token' - ``` - - - Response - - ```json - { - "accessToken": "access_token", - "refreshToken": "refresh_token" - } - ``` - -### TODO - -- Improvement based on feedback. -- Add more test cases. -- Always try to update with the latest version of the packages used. - -## If this project helps you in anyway, show your love ❤️ by putting a ⭐ on this project ✌️ - -### License - -``` - Copyright (C) 2023 Amit Shekhar - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -``` - -### Contributing to Go Backend Clean Architecture - -All pull requests are welcome. diff --git a/api/db/mysql.go b/api/db/mysql.go new file mode 100644 index 0000000..e43a820 --- /dev/null +++ b/api/db/mysql.go @@ -0,0 +1,37 @@ +package db + +import ( + "fmt" + "os" + + "gorm.io/driver/mysql" + "gorm.io/gorm" +) + +// Database struct +type Database struct { + DB *gorm.DB +} + +// NewDatabase : intializes and returns mysql db +func NewDatabase() Database { + USER := os.Getenv("DB_MYSQL_USER") + PASS := os.Getenv("DB_MYSQL_PASSWORD") + HOST := os.Getenv("DB_MYSQL_ROOT_PASSWORD") + DBNAME := os.Getenv("DB_MYSQL_DATABASE") + + URL := fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8&parseTime=True&loc=Local", USER, PASS, + HOST, DBNAME) + fmt.Println(URL) + db, err := gorm.Open(mysql.Open(URL)) + + if err != nil { + panic("Failed to connect to database!") + + } + fmt.Println("Database connection established") + return Database{ + DB: db, + } + +} diff --git a/api/route/route.go b/api/route/route.go index 0c764ff..eb16e65 100644 --- a/api/route/route.go +++ b/api/route/route.go @@ -7,9 +7,10 @@ import ( "github.com/amitshekhariitbhu/go-backend-clean-architecture/bootstrap" "github.com/amitshekhariitbhu/go-backend-clean-architecture/mongo" "github.com/gin-gonic/gin" + "gorm.io/gorm" ) -func Setup(env *bootstrap.Env, timeout time.Duration, db mongo.Database, gin *gin.Engine) { +func Setup(env *bootstrap.Env, timeout time.Duration, db mongo.Database, gin *gin.Engine, dbMysql *gorm.DB) { publicRouter := gin.Group("") // All Public APIs NewSignupRouter(env, timeout, db, publicRouter) diff --git a/bootstrap/app.go b/bootstrap/app.go index c62078d..e66be97 100644 --- a/bootstrap/app.go +++ b/bootstrap/app.go @@ -1,16 +1,21 @@ package bootstrap -import "github.com/amitshekhariitbhu/go-backend-clean-architecture/mongo" +import ( + "github.com/amitshekhariitbhu/go-backend-clean-architecture/mongo" + "gorm.io/gorm" +) type Application struct { Env *Env Mongo mongo.Client + Mysql *gorm.DB } func App() Application { app := &Application{} app.Env = NewEnv() app.Mongo = NewMongoDatabase(app.Env) + // app.Mysql = NewMysqlDatabase(app.Env) return *app } diff --git a/bootstrap/database.go b/bootstrap/database.go index cdff5fa..510e253 100644 --- a/bootstrap/database.go +++ b/bootstrap/database.go @@ -6,11 +6,13 @@ import ( "log" "time" + "github.com/amitshekhariitbhu/go-backend-clean-architecture/api/db" "github.com/amitshekhariitbhu/go-backend-clean-architecture/mongo" + "gorm.io/gorm" ) func NewMongoDatabase(env *Env) mongo.Client { - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) defer cancel() dbHost := env.DBHost @@ -31,12 +33,12 @@ func NewMongoDatabase(env *Env) mongo.Client { err = client.Connect(ctx) if err != nil { - log.Fatal(err) + log.Fatal(" err 1 := ", err) } err = client.Ping(ctx) if err != nil { - log.Fatal(err) + log.Fatal(" err 2 := ", err) } return client @@ -54,3 +56,8 @@ func CloseMongoDBConnection(client mongo.Client) { log.Println("Connection to MongoDB closed.") } + +func NewMysqlDatabase(env *Env) *gorm.DB { + + return db.NewDatabase().DB +} diff --git a/bootstrap/env.go b/bootstrap/env.go index c1c0b2e..b1cf7c2 100644 --- a/bootstrap/env.go +++ b/bootstrap/env.go @@ -19,12 +19,15 @@ type Env struct { RefreshTokenExpiryHour int `mapstructure:"REFRESH_TOKEN_EXPIRY_HOUR"` AccessTokenSecret string `mapstructure:"ACCESS_TOKEN_SECRET"` RefreshTokenSecret string `mapstructure:"REFRESH_TOKEN_SECRET"` + DB_MYSQL_USER string `mapstructure:"DB_MYSQL_USER"` + DB_MYSQL_PASSWORD string `mapstructure:"DB_MYSQL_PASSWORD"` + DB_MYSQL_ROOT_PASSWORD string `mapstructure:"DB_MYSQL_ROOT_PASSWORD"` + DB_MYSQL_DATABASE string `mapstructure:"DB_MYSQL_DATABASE"` } func NewEnv() *Env { env := Env{} viper.SetConfigFile(".env") - err := viper.ReadInConfig() if err != nil { log.Fatal("Can't find the file .env : ", err) diff --git a/cmd/main.go b/cmd/main.go index 13b7084..b088284 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -6,6 +6,7 @@ import ( route "github.com/amitshekhariitbhu/go-backend-clean-architecture/api/route" "github.com/amitshekhariitbhu/go-backend-clean-architecture/bootstrap" "github.com/gin-gonic/gin" + cors "github.com/itsjamie/gin-cors" ) func main() { @@ -17,11 +18,22 @@ func main() { db := app.Mongo.Database(env.DBName) defer app.CloseDBConnection() + // db_gorm := app.Mysql + timeout := time.Duration(env.ContextTimeout) * time.Second gin := gin.Default() - route.Setup(env, timeout, db, gin) - + route.Setup(env, timeout, db, gin, nil) + + gin.Use(cors.Middleware(cors.Config{ + Origins: "*", + Methods: "GET, PUT, POST, DELETE", + RequestHeaders: "Origin, Authorization, Content-Type", + ExposedHeaders: "", + MaxAge: 50 * time.Second, + Credentials: false, + ValidateHeaders: false, + })) gin.Run(env.ServerAddress) } diff --git a/docker-compose.yaml b/docker-compose.yaml index b0b8a26..fc27703 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -20,12 +20,25 @@ services: restart: unless-stopped env_file: .env environment: - - MONGO_INITDB_ROOT_USERNAME=$DB_USER - - MONGO_INITDB_ROOT_PASSWORD=$DB_PASS + - MONGO_INITDB_ROOT_USERNAME=${DB_USER} + - MONGO_INITDB_ROOT_PASSWORD=${DB_PASS} ports: - - "$DB_PORT:$DB_PORT" + - "$DB_PORT:${DB_PORT}" volumes: - dbdata:/data/db - + + db: + image: mysql/mysql-server:5.7 + ports: + - "3305:3306" + environment: + - "MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}" + - "MYSQL_USER=${DB_USER}" + - "MYSQL_PASSWORD=${DB_PASSWORD}" + - "MYSQL_DATABASE=${DB_NAME}" + # sync folders. MySQL data is stored outside container so that rebuilding doesn't clear db. + # folder is at workspace root. + volumes: + - ../../../../_local_mysql_data:/var/lib/mysql volumes: dbdata: diff --git a/go.mod b/go.mod index 5eb952c..1d2031f 100644 --- a/go.mod +++ b/go.mod @@ -5,19 +5,28 @@ go 1.19 require ( github.com/gin-gonic/gin v1.8.2 github.com/golang-jwt/jwt/v4 v4.4.3 + github.com/itsjamie/gin-cors v0.0.0-20220228161158-ef28d3d2a0a8 github.com/spf13/viper v1.14.0 github.com/stretchr/testify v1.8.1 go.mongodb.org/mongo-driver v1.11.1 golang.org/x/crypto v0.4.0 + gorm.io/driver/mysql v1.5.2 + gorm.io/gorm v1.25.5 +) + +require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/gin-contrib/sse v0.1.0 // indirect github.com/go-playground/locales v0.14.0 // indirect github.com/go-playground/universal-translator v0.18.0 // indirect github.com/go-playground/validator/v10 v10.11.1 // indirect + github.com/go-sql-driver/mysql v1.7.0 // indirect github.com/goccy/go-json v0.10.0 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/hashicorp/hcl v1.0.0 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/compress v1.15.13 // indirect github.com/leodido/go-urn v1.2.1 // indirect diff --git a/go.sum b/go.sum index f98b14c..214dea3 100644 --- a/go.sum +++ b/go.sum @@ -74,6 +74,8 @@ github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/j github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= github.com/go-playground/validator/v10 v10.11.1 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJWXmqUsHwfTRRkQ= github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= +github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc= +github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= github.com/goccy/go-json v0.10.0 h1:mXKd9Qw4NuzShiRlOXKews24ufknHO7gx30lsDyokKA= github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/golang-jwt/jwt/v4 v4.4.3 h1:Hxl6lhQFj4AnOX6MLrsCb/+7tCj7DxP7VA+2rDIq5AU= @@ -145,6 +147,12 @@ github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/itsjamie/gin-cors v0.0.0-20220228161158-ef28d3d2a0a8 h1:3n0c+dqwjqfvvoV+Q3hWvXT58q/YGnegkFx8w56Kj44= +github.com/itsjamie/gin-cors v0.0.0-20220228161158-ef28d3d2a0a8/go.mod h1:AYdLvrSBFloDBNt7Y8xkQ6gmhCODGl8CPikjyIOnNzA= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= +github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= @@ -567,6 +575,11 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/driver/mysql v1.5.2 h1:QC2HRskSE75wBuOxe0+iCkyJZ+RqpudsQtqkp+IMuXs= +gorm.io/driver/mysql v1.5.2/go.mod h1:pQLhh1Ut/WUAySdTHwBpBv6+JKcj+ua4ZFx1QQTBzb8= +gorm.io/gorm v1.25.2-0.20230530020048-26663ab9bf55/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= +gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls= +gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=