From 2310cfd05b1d36852f0b726a24811c794271d9ed Mon Sep 17 00:00:00 2001 From: ravonzz Date: Mon, 10 Jun 2024 15:59:20 +0500 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB=20?= =?UTF-8?q?=D0=BC=D0=B8=D0=B4=D0=B4=D0=BB=D0=B2=D0=B5=D0=B9=D1=80=20=D0=B4?= =?UTF-8?q?=D0=BB=D1=8F=20=D0=B0=D0=B2=D1=82=D0=BE=D1=80=D0=B8=D0=B7=D0=B0?= =?UTF-8?q?=D1=86=D0=B8=D0=B8.=20=D0=98=20=D0=B4=D0=BE=D0=BF=D0=B8=D1=81?= =?UTF-8?q?=D0=B0=D0=BB=20readme.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Manufacturer/Create Manufacturer.bru | 0 .../Manufacturer/Delete Manufacturer.bru | 0 .../Manufacturer/EDIT Manufacture.bru | 0 .../Manufacturer/Get All Manufacturers.bru | 0 .../golang-test}/Product/Buy Product.bru | 0 .../golang-test}/Product/Create Product.bru | 0 .../golang-test}/Product/Delete Product.bru | 0 .../golang-test}/Product/Get Product Info.bru | 0 .../golang-test}/Product/Get Products.bru | 0 .../golang-test}/User/Add Money.bru | 0 .../golang-test}/User/Edit User Name.bru | 0 .../golang-test}/User/Get Buyed Products.bru | 0 .../golang-test}/User/Get User Info.bru | 0 .../golang-test}/User/Login.bru | 0 .../golang-test}/User/Register.bru | 0 .../golang-test}/bruno.json | 0 .../golang-test}/environments/golang-test.bru | 2 +- cli/migration.go | 2 +- controllers/userController.go | 46 ++++--- golang-test/Test.bru | 11 -- libs/libs.go | 9 ++ main.go | 105 +-------------- middlewares/authMiddleware.go | 24 ++++ readme.md | 18 +++ routes/routes.go | 124 ++++++++++++++++++ 25 files changed, 205 insertions(+), 136 deletions(-) rename {golang-test => brunoTest/golang-test}/Manufacturer/Create Manufacturer.bru (100%) rename {golang-test => brunoTest/golang-test}/Manufacturer/Delete Manufacturer.bru (100%) rename {golang-test => brunoTest/golang-test}/Manufacturer/EDIT Manufacture.bru (100%) rename {golang-test => brunoTest/golang-test}/Manufacturer/Get All Manufacturers.bru (100%) rename {golang-test => brunoTest/golang-test}/Product/Buy Product.bru (100%) rename {golang-test => brunoTest/golang-test}/Product/Create Product.bru (100%) rename {golang-test => brunoTest/golang-test}/Product/Delete Product.bru (100%) rename {golang-test => brunoTest/golang-test}/Product/Get Product Info.bru (100%) rename {golang-test => brunoTest/golang-test}/Product/Get Products.bru (100%) rename {golang-test => brunoTest/golang-test}/User/Add Money.bru (100%) rename {golang-test => brunoTest/golang-test}/User/Edit User Name.bru (100%) rename {golang-test => brunoTest/golang-test}/User/Get Buyed Products.bru (100%) rename {golang-test => brunoTest/golang-test}/User/Get User Info.bru (100%) rename {golang-test => brunoTest/golang-test}/User/Login.bru (100%) rename {golang-test => brunoTest/golang-test}/User/Register.bru (100%) rename {golang-test => brunoTest/golang-test}/bruno.json (100%) rename {golang-test => brunoTest/golang-test}/environments/golang-test.bru (61%) delete mode 100644 golang-test/Test.bru create mode 100644 middlewares/authMiddleware.go create mode 100644 routes/routes.go diff --git a/golang-test/Manufacturer/Create Manufacturer.bru b/brunoTest/golang-test/Manufacturer/Create Manufacturer.bru similarity index 100% rename from golang-test/Manufacturer/Create Manufacturer.bru rename to brunoTest/golang-test/Manufacturer/Create Manufacturer.bru diff --git a/golang-test/Manufacturer/Delete Manufacturer.bru b/brunoTest/golang-test/Manufacturer/Delete Manufacturer.bru similarity index 100% rename from golang-test/Manufacturer/Delete Manufacturer.bru rename to brunoTest/golang-test/Manufacturer/Delete Manufacturer.bru diff --git a/golang-test/Manufacturer/EDIT Manufacture.bru b/brunoTest/golang-test/Manufacturer/EDIT Manufacture.bru similarity index 100% rename from golang-test/Manufacturer/EDIT Manufacture.bru rename to brunoTest/golang-test/Manufacturer/EDIT Manufacture.bru diff --git a/golang-test/Manufacturer/Get All Manufacturers.bru b/brunoTest/golang-test/Manufacturer/Get All Manufacturers.bru similarity index 100% rename from golang-test/Manufacturer/Get All Manufacturers.bru rename to brunoTest/golang-test/Manufacturer/Get All Manufacturers.bru diff --git a/golang-test/Product/Buy Product.bru b/brunoTest/golang-test/Product/Buy Product.bru similarity index 100% rename from golang-test/Product/Buy Product.bru rename to brunoTest/golang-test/Product/Buy Product.bru diff --git a/golang-test/Product/Create Product.bru b/brunoTest/golang-test/Product/Create Product.bru similarity index 100% rename from golang-test/Product/Create Product.bru rename to brunoTest/golang-test/Product/Create Product.bru diff --git a/golang-test/Product/Delete Product.bru b/brunoTest/golang-test/Product/Delete Product.bru similarity index 100% rename from golang-test/Product/Delete Product.bru rename to brunoTest/golang-test/Product/Delete Product.bru diff --git a/golang-test/Product/Get Product Info.bru b/brunoTest/golang-test/Product/Get Product Info.bru similarity index 100% rename from golang-test/Product/Get Product Info.bru rename to brunoTest/golang-test/Product/Get Product Info.bru diff --git a/golang-test/Product/Get Products.bru b/brunoTest/golang-test/Product/Get Products.bru similarity index 100% rename from golang-test/Product/Get Products.bru rename to brunoTest/golang-test/Product/Get Products.bru diff --git a/golang-test/User/Add Money.bru b/brunoTest/golang-test/User/Add Money.bru similarity index 100% rename from golang-test/User/Add Money.bru rename to brunoTest/golang-test/User/Add Money.bru diff --git a/golang-test/User/Edit User Name.bru b/brunoTest/golang-test/User/Edit User Name.bru similarity index 100% rename from golang-test/User/Edit User Name.bru rename to brunoTest/golang-test/User/Edit User Name.bru diff --git a/golang-test/User/Get Buyed Products.bru b/brunoTest/golang-test/User/Get Buyed Products.bru similarity index 100% rename from golang-test/User/Get Buyed Products.bru rename to brunoTest/golang-test/User/Get Buyed Products.bru diff --git a/golang-test/User/Get User Info.bru b/brunoTest/golang-test/User/Get User Info.bru similarity index 100% rename from golang-test/User/Get User Info.bru rename to brunoTest/golang-test/User/Get User Info.bru diff --git a/golang-test/User/Login.bru b/brunoTest/golang-test/User/Login.bru similarity index 100% rename from golang-test/User/Login.bru rename to brunoTest/golang-test/User/Login.bru diff --git a/golang-test/User/Register.bru b/brunoTest/golang-test/User/Register.bru similarity index 100% rename from golang-test/User/Register.bru rename to brunoTest/golang-test/User/Register.bru diff --git a/golang-test/bruno.json b/brunoTest/golang-test/bruno.json similarity index 100% rename from golang-test/bruno.json rename to brunoTest/golang-test/bruno.json diff --git a/golang-test/environments/golang-test.bru b/brunoTest/golang-test/environments/golang-test.bru similarity index 61% rename from golang-test/environments/golang-test.bru rename to brunoTest/golang-test/environments/golang-test.bru index 24fa341..407e06e 100644 --- a/golang-test/environments/golang-test.bru +++ b/brunoTest/golang-test/environments/golang-test.bru @@ -2,5 +2,5 @@ vars { host: http://localhost:8080 manufacturerID: 1 productID: 6 - token: npG8pIdPaFSBHdM119QpZtm773KKCoVIGw== + token: UFY7FwOZpuac11IXiogxtpfVYt0Ib2Il3w== } diff --git a/cli/migration.go b/cli/migration.go index 4d1f9aa..659bdd5 100644 --- a/cli/migration.go +++ b/cli/migration.go @@ -24,7 +24,7 @@ func main() { panic("failed to connect database") } - err = db.AutoMigrate(&database.Manufacturer{}, &database.Product{}, &database.User{}) + err = db.AutoMigrate(&database.Manufacturer{}, &database.Product{}, &database.User{}, &database.Token{}) if err != nil { return } diff --git a/controllers/userController.go b/controllers/userController.go index f34c53a..3563598 100644 --- a/controllers/userController.go +++ b/controllers/userController.go @@ -1,8 +1,6 @@ package controllers import ( - "crypto/rand" - "encoding/base64" "golang-test/database" "golang-test/libs" "golang-test/types" @@ -55,7 +53,7 @@ func RegisterUser(c *gin.Context, register types.RegisterRequest) { } // Генерация токена для пользователя - token.Token = generateToken() + token.Token = libs.GenerateToken() token.UserID = user.ID user.Tokens = append(user.Tokens, token) @@ -98,7 +96,7 @@ func LoginUser(c *gin.Context, login types.LoginRequest) { return } - token.Token = generateToken() + token.Token = libs.GenerateToken() token.UserID = user.ID user.Tokens = append(user.Tokens, token) @@ -108,27 +106,25 @@ func LoginUser(c *gin.Context, login types.LoginRequest) { c.JSON(http.StatusOK, types.TokenResponse{Token: token.Token}) } -func generateToken() string { - b := make([]byte, 25) - if _, err := rand.Read(b); err != nil { - return "" - } - return base64.StdEncoding.EncodeToString(b) -} - func GetUser(c *gin.Context) { var products []types.ProductResponse - user, err := libs.GetUserFromHeaders(c) - if err != nil { - c.JSON(http.StatusUnauthorized, types.ErrorResponse{Message: err.Error()}) + userFromMiddleware, ok := c.Get("user") + if !ok { + c.JSON(http.StatusUnauthorized, types.ErrorResponse{Message: "Authorization Error. Please login"}) return } + user := userFromMiddleware.(database.User) for _, product := range user.Products { products = append(products, types.ProductResponse{ - ID: product.ID, - Name: product.Name, - Price: product.Price, + ID: product.ID, + Name: product.Name, + Price: product.Price, + ManufacturerID: product.ManufacturerID, + Manufacturer: types.ManufacturerResponse{ + Name: product.Manufacturer.Name, + ID: product.Manufacturer.ID, + }, }) } @@ -152,11 +148,12 @@ func EditUserName(c *gin.Context, userName types.EditUserNameRequest) { return } - user, err := libs.GetUserFromHeaders(c) - if err != nil { - c.JSON(http.StatusUnauthorized, types.ErrorResponse{Message: err.Error()}) + userFromMiddleware, ok := c.Get("user") + if !ok { + c.JSON(http.StatusUnauthorized, types.ErrorResponse{Message: "Authorization Error. Please login"}) return } + user := userFromMiddleware.(database.User) user.Name = userName.Name @@ -201,11 +198,12 @@ func BuyProduct(c *gin.Context) { return } - user, err := libs.GetUserFromHeaders(c) - if err != nil { - c.JSON(http.StatusUnauthorized, types.ErrorResponse{Message: err.Error()}) + userFromMiddleware, ok := c.Get("user") + if !ok { + c.JSON(http.StatusUnauthorized, types.ErrorResponse{Message: "Authorization Error. Please login"}) return } + user := userFromMiddleware.(database.User) if user.Money-product.Price < 0 { c.JSON(http.StatusOK, types.MessageResponse{Message: "Not enough money"}) diff --git a/golang-test/Test.bru b/golang-test/Test.bru deleted file mode 100644 index 2bb1c66..0000000 --- a/golang-test/Test.bru +++ /dev/null @@ -1,11 +0,0 @@ -meta { - name: Test - type: http - seq: 2 -} - -get { - url: {{host}}/test - body: none - auth: none -} diff --git a/libs/libs.go b/libs/libs.go index 29d78cc..b4b75db 100644 --- a/libs/libs.go +++ b/libs/libs.go @@ -1,6 +1,7 @@ package libs import ( + "encoding/base64" "errors" "golang-test/database" "strings" @@ -65,3 +66,11 @@ func GetUserFromHeaders(c *gin.Context) (database.User, error) { return user, nil } + +func GenerateToken() string { + b := make([]byte, 25) + if _, err := rand.Read(b); err != nil { + return "" + } + return base64.StdEncoding.EncodeToString(b) +} diff --git a/main.go b/main.go index ae88fab..c5b9cc6 100644 --- a/main.go +++ b/main.go @@ -1,12 +1,10 @@ package main import ( - "golang-test/controllers" - "golang-test/database" - "golang-test/types" - "log" - "net/http" "github.com/gin-gonic/gin" + "golang-test/database" + "golang-test/routes" + "log" ) func main() { @@ -14,106 +12,15 @@ func main() { // Создание сервера r := gin.Default() - r.POST("/product", func(c *gin.Context) { - var product database.Product - if err := c.ShouldBind(&product); err != nil { - c.JSON(http.StatusBadRequest, types.ErrorResponse{Message: err.Error()}) - return - } - controllers.CreateProduct(c, product) - }) - - r.DELETE("/product/:id", func(c *gin.Context) { - controllers.DeleteProduct(c) - }) - - r.GET("/product", func(c *gin.Context) { - controllers.GetProducts(c) - }) - - r.GET("/product/:id", func(c *gin.Context) { - controllers.GetProductInfo(c) - }) - - r.GET("/product/:id/buy", func(c *gin.Context) { - controllers.BuyProduct(c) - }) - - r.POST("/manufacturer", func(c *gin.Context) { - var manufacturer database.Manufacturer - if err := c.ShouldBind(&manufacturer); err != nil { - c.JSON(http.StatusBadRequest, types.ErrorResponse{Message: err.Error()}) - return - } - controllers.CreateManufacturer(c, manufacturer) - }) - - r.DELETE("/manufacturer/:id", func(c *gin.Context) { - controllers.DeleteManufacturer(c) - }) - - r.GET("/manufacturer", func(c *gin.Context) { - controllers.GetManufacturers(c) - }) - - r.PATCH("/manufacturer/:id", func(c *gin.Context) { - var manufacturer types.ManufacturerPatchRequest - if err := c.ShouldBind(&manufacturer); err != nil { - c.JSON(http.StatusBadRequest, types.ErrorResponse{Message: err.Error()}) - return - } - controllers.EditManufacture(c, manufacturer) - }) - - r.POST("/registration", func(c *gin.Context) { - var form types.RegisterRequest - if err := c.ShouldBind(&form); err != nil { - c.JSON(http.StatusBadRequest, types.ErrorResponse{Message: err.Error()}) - return - } - controllers.RegisterUser(c, form) - }) - - r.POST("/login", func(c *gin.Context) { - var register types.LoginRequest - if err := c.ShouldBind(®ister); err != nil { - c.JSON(http.StatusBadRequest, types.ErrorResponse{Message: err.Error()}) - return - } - controllers.LoginUser(c, register) - }) - - r.GET("/user", func(c *gin.Context) { - controllers.GetUser(c) - }) - - r.PATCH("/user", func(c *gin.Context) { - var user types.EditUserNameRequest - if err := c.ShouldBind(&user); err != nil { - c.JSON(http.StatusBadRequest, types.ErrorResponse{Message: err.Error()}) - return - } - controllers.EditUserName(c, user) - }) - - r.POST("/user/addmoney", func(c *gin.Context) { - var moneyRequest types.AddMoneyRequest - if err := c.ShouldBind(&moneyRequest); err != nil { - c.JSON(http.StatusBadRequest, types.ErrorResponse{Message: err.Error()}) - return - } - controllers.AddMoneyToUser(c, moneyRequest) - }) - - r.GET("/user/products", func(c *gin.Context) { - controllers.GetBuyedProducts(c) - }) + //Создание маршрутов + routes.ResolveRoutes(r) err := r.Run() if err != nil { return } + //Отключение от БД при выключении приложения. defer func() { db := database.Connector() sqlDB, err := db.DB() diff --git a/middlewares/authMiddleware.go b/middlewares/authMiddleware.go new file mode 100644 index 0000000..8341855 --- /dev/null +++ b/middlewares/authMiddleware.go @@ -0,0 +1,24 @@ +package middlewares + +import ( + "github.com/gin-gonic/gin" + "golang-test/libs" + "golang-test/types" + "net/http" +) + +// Middleware для авторизации +func AuthMiddleware() gin.HandlerFunc { + return func(c *gin.Context) { + user, err := libs.GetUserFromHeaders(c) + if err != nil { + c.JSON(http.StatusUnauthorized, types.ErrorResponse{Message: err.Error()}) + c.Abort() // Прерываем обработку запроса + return + } + + // Если пользователь успешно извлечен, продолжаем обработку запроса + c.Set("user", user) + c.Next() + } +} diff --git a/readme.md b/readme.md index 40bcf75..6a93f36 100644 --- a/readme.md +++ b/readme.md @@ -3,6 +3,24 @@ ## Обзор Этот проект представляет собой RESTful API для управления товарами, производителями и пользователями. В качестве ORM используется GORM. +## Структура проекта + +- **cli/migration.go** - Применение автомиграции в БД. +- **contollers/** - Вся бизнес логика, обращения к БД. +- **database/connect.go** - Подключение к БД. +- **database/models.go** - Модели Gorm. +- **brunoTest/** - Коллекция Bruno для тестирования API. +- **libs/** - Небольшие функции используемые в разных контроллерах. +- **middlewares/authMiddleware.go** - Прослойка для авторизации. +- **routes.go** - Описание всех маршрутов. +- **types/** - Типы для запросов и ответов на сервер. +- **validators/** - Кастомные валидаторы. +- **docker-compose.yml** - Файл docker-compose для запуска в контейнере вместе в БД. +- **Dockerfile** - Файл для сборки докер контейнера. +- **entrypoint.sh** - Входная точка для докер контейнера. Нужна чтобы запустить миграцию в бд перед запуском сервера. +- **main.go** - Основной файл. Используется для запуска сервера. + + ## Endpoints ### Товары diff --git a/routes/routes.go b/routes/routes.go new file mode 100644 index 0000000..f82b4bb --- /dev/null +++ b/routes/routes.go @@ -0,0 +1,124 @@ +package routes + +import ( + "github.com/gin-gonic/gin" + "golang-test/controllers" + "golang-test/database" + "golang-test/middlewares" + "golang-test/types" + "net/http" +) + +// ResolveRoutes настраивает конечные точки API +func ResolveRoutes(r *gin.Engine) { + + // Создать новый продукт + r.POST("/product", func(c *gin.Context) { + var product database.Product + if err := c.ShouldBind(&product); err != nil { + c.JSON(http.StatusBadRequest, types.ErrorResponse{Message: err.Error()}) + return + } + controllers.CreateProduct(c, product) + }) + + // Удалить продукт по ID + r.DELETE("/product/:id", func(c *gin.Context) { + controllers.DeleteProduct(c) + }) + + // Получить все продукты + r.GET("/product", func(c *gin.Context) { + controllers.GetProducts(c) + }) + + // Получить информацию о продукте по ID + r.GET("/product/:id", func(c *gin.Context) { + controllers.GetProductInfo(c) + }) + + // Купить продукт по ID + r.GET("/product/:id/buy", middlewares.AuthMiddleware(), func(c *gin.Context) { + controllers.BuyProduct(c) + }) + + // Создать нового производителя + r.POST("/manufacturer", func(c *gin.Context) { + var manufacturer database.Manufacturer + if err := c.ShouldBind(&manufacturer); err != nil { + c.JSON(http.StatusBadRequest, types.ErrorResponse{Message: err.Error()}) + return + } + controllers.CreateManufacturer(c, manufacturer) + }) + + // Удалить производителя по ID + r.DELETE("/manufacturer/:id", func(c *gin.Context) { + controllers.DeleteManufacturer(c) + }) + + // Получить всех производителей + r.GET("/manufacturer", func(c *gin.Context) { + controllers.GetManufacturers(c) + }) + + // Редактировать производителя по ID + r.PATCH("/manufacturer/:id", func(c *gin.Context) { + var manufacturer types.ManufacturerPatchRequest + if err := c.ShouldBind(&manufacturer); err != nil { + c.JSON(http.StatusBadRequest, types.ErrorResponse{Message: err.Error()}) + return + } + controllers.EditManufacture(c, manufacturer) + }) + + // Зарегистрировать нового пользователя + r.POST("/registration", func(c *gin.Context) { + var form types.RegisterRequest + if err := c.ShouldBind(&form); err != nil { + c.JSON(http.StatusBadRequest, types.ErrorResponse{Message: err.Error()}) + return + } + controllers.RegisterUser(c, form) + }) + + // Войти в систему + r.POST("/login", func(c *gin.Context) { + var register types.LoginRequest + if err := c.ShouldBind(®ister); err != nil { + c.JSON(http.StatusBadRequest, types.ErrorResponse{Message: err.Error()}) + return + } + controllers.LoginUser(c, register) + }) + + // Получить информацию о пользователе + r.GET("/user", middlewares.AuthMiddleware(), func(c *gin.Context) { + controllers.GetUser(c) + }) + + // Редактировать имя пользователя + r.PATCH("/user", middlewares.AuthMiddleware(), func(c *gin.Context) { + var user types.EditUserNameRequest + if err := c.ShouldBind(&user); err != nil { + c.JSON(http.StatusBadRequest, types.ErrorResponse{Message: err.Error()}) + return + } + controllers.EditUserName(c, user) + }) + + // Добавить деньги на счет пользователя + r.POST("/user/addmoney", func(c *gin.Context) { + var moneyRequest types.AddMoneyRequest + if err := c.ShouldBind(&moneyRequest); err != nil { + c.JSON(http.StatusBadRequest, types.ErrorResponse{Message: err.Error()}) + return + } + controllers.AddMoneyToUser(c, moneyRequest) + }) + + // Получить купленные продукты пользователя + r.GET("/user/products", middlewares.AuthMiddleware(), func(c *gin.Context) { + controllers.GetBuyedProducts(c) + }) +}