From 2728051fdeed24f957dae3e96a1bc0192be022b6 Mon Sep 17 00:00:00 2001 From: nquidox Date: Sun, 2 Nov 2025 23:27:23 +0300 Subject: [PATCH 1/2] fixes --- internal/api/merch/controller.go | 2 +- internal/api/merch/dto.go | 4 ++- internal/api/merch/repository.go | 44 ++++++++++++++++++++++---------- internal/api/merch/service.go | 25 ++++++++++++++++-- 4 files changed, 58 insertions(+), 17 deletions(-) diff --git a/internal/api/merch/controller.go b/internal/api/merch/controller.go index 63b28f2..33c194f 100644 --- a/internal/api/merch/controller.go +++ b/internal/api/merch/controller.go @@ -731,7 +731,7 @@ func (co *controller) deleteZeroPrices(c *gin.Context) { return } - var payload DeleteZeroPrices + var payload []DeleteZeroPrices if err = c.ShouldBindJSON(&payload); err != nil { c.JSON(http.StatusBadRequest, responses.ErrorResponse400{Error: err.Error()}) log.WithError(err).Error(logMsg) diff --git a/internal/api/merch/dto.go b/internal/api/merch/dto.go index b10aa6b..7ee7281 100644 --- a/internal/api/merch/dto.go +++ b/internal/api/merch/dto.go @@ -82,6 +82,7 @@ type LabelLink struct { } type ZeroPrice struct { + Id int `json:"id"` CreatedAt time.Time `json:"created_at"` MerchUuid string `json:"merch_uuid"` Name string `json:"name"` @@ -89,5 +90,6 @@ type ZeroPrice struct { } type DeleteZeroPrices struct { - MerchUuids []string `json:"merch_uuids"` + Id uint `json:"id"` + MerchUuid string `json:"merch_uuid"` } diff --git a/internal/api/merch/repository.go b/internal/api/merch/repository.go index 1af8df0..711b731 100644 --- a/internal/api/merch/repository.go +++ b/internal/api/merch/repository.go @@ -3,6 +3,7 @@ package merch import ( "database/sql" "errors" + "fmt" "gorm.io/gorm" "gorm.io/gorm/clause" "time" @@ -22,6 +23,7 @@ type repository interface { addMerch(bundle merchBundle) error merchRecordExists(userUuid, merchUuid string) (bool, error) + userOwnsMerchUuids(userUuid string, merchUuids []string) (bool, error) getSingleMerch(userUuid, merchUuid string) (merchBundle, error) getAllMerch(userUuid string) ([]ListResponse, error) @@ -40,7 +42,7 @@ type prices interface { getDistinctPrices(userUuid, merchUuid string, period time.Time) (prices []Price, err error) getZeroPrices(userUuid string) ([]ZeroPrice, error) - deleteZeroPrices(userUuid string, list []string) error + deleteZeroPrices(list []DeleteZeroPrices) error } type labels interface { @@ -84,6 +86,22 @@ func (r *Repo) merchRecordExists(userUuid, merchUuid string) (bool, error) { return exists, err } +func (r *Repo) userOwnsMerchUuids(userUuid string, merchUuids []string) (bool, error) { + var count int64 + + err := r.db.Model(&Merch{}). + Where("user_uuid = ?", userUuid). + Where("merch_uuid IN ?", merchUuids). + Where("deleted_at IS NULL"). + Count(&count).Error + fmt.Println("!!!!!!", count) + if err != nil { + return false, err + } + fmt.Println("!!!!!!", len(merchUuids)) + return count == int64(len(merchUuids)), nil +} + func (r *Repo) getSingleMerch(userUuid, merchUuid string) (merchBundle, error) { var merch Merch if err := r.db. @@ -320,16 +338,17 @@ func (r *Repo) getZeroPrices(userUuid string) ([]ZeroPrice, error) { if err := r.db.Raw(` WITH price_with_neighbors AS ( SELECT - p.created_at, p.merch_uuid, p.price, p.origin, m.name, + p.id, p.created_at, p.merch_uuid, p.price, p.origin, m.name, LAG(price) OVER (PARTITION BY p.merch_uuid ORDER BY p.created_at, p.id) AS prev_price, LEAD(price) OVER (PARTITION BY p.merch_uuid ORDER BY p.created_at, p.id) AS next_price FROM prices AS p JOIN merch as m ON m.merch_uuid = p.merch_uuid WHERE p.deleted_at IS NULL + AND m.deleted_at IS NULL AND m.user_uuid = ?) SELECT - created_at, merch_uuid, origin, name + id, created_at, merch_uuid, origin, name FROM price_with_neighbors WHERE price = 0 @@ -343,14 +362,13 @@ func (r *Repo) getZeroPrices(userUuid string) ([]ZeroPrice, error) { return priceList, nil } -func (r *Repo) deleteZeroPrices(userUuid string, list []string) error { - subQuery := r.db.Table("merch"). - Select("merch_uuid"). - Where("user_uuid = ?", userUuid) - - return r.db.Model(&Price{}). - Where("merch_uuid IN ?", list). - Where("merch_uuid IN (?)", subQuery). - Update("deleted_at", time.Now().UTC()). - Error +func (r *Repo) deleteZeroPrices(list []DeleteZeroPrices) error { + for _, item := range list { + if err := r.db.Model(&Price{}). + Where("id = ? AND merch_uuid = ?", item.Id, item.MerchUuid). + Update("deleted_at", time.Now().UTC()).Error; err != nil { + return err + } + } + return nil } diff --git a/internal/api/merch/service.go b/internal/api/merch/service.go index 259563d..4d06a37 100644 --- a/internal/api/merch/service.go +++ b/internal/api/merch/service.go @@ -16,6 +16,7 @@ import ( is "merch-parser-api/proto/imageStorage" "mime/multipart" "path/filepath" + "slices" "strings" "time" ) @@ -617,6 +618,26 @@ func (s *service) getZeroPrices(userUuid string) ([]ZeroPrice, error) { return s.repo.getZeroPrices(userUuid) } -func (s *service) deleteZeroPrices(userUuid string, list DeleteZeroPrices) error { - return s.repo.deleteZeroPrices(userUuid, list.MerchUuids) +func (s *service) deleteZeroPrices(userUuid string, list []DeleteZeroPrices) error { + if len(list) == 0 { + return nil + } + + ids := make([]string, 0, len(list)) + for _, item := range list { + ids = append(ids, item.MerchUuid) + fmt.Println(item.MerchUuid, ids) + } + slices.Compact(ids) + + owns, err := s.repo.userOwnsMerchUuids(userUuid, ids) + if err != nil { + return err + } + + if !owns { + return errors.New("wrong ids") + } + + return s.repo.deleteZeroPrices(list) } From 7fa79d770a20293799485e76c55fb66f4ca73f6f Mon Sep 17 00:00:00 2001 From: nquidox Date: Sun, 2 Nov 2025 23:39:25 +0300 Subject: [PATCH 2/2] swagger docs update --- docs/docs.go | 31 ++++++++++-- docs/swagger.json | 31 ++++++++++-- docs/swagger.yaml | 22 +++++++-- internal/api/merch/controller.go | 84 +++++++++++++++++--------------- 4 files changed, 114 insertions(+), 54 deletions(-) diff --git a/docs/docs.go b/docs/docs.go index b668308..b97688b 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -106,6 +106,9 @@ const docTemplate = `{ } ], "description": "Обновить информацию про мерч по его uuid в json-е", + "consumes": [ + "application/json" + ], "tags": [ "Merch" ], @@ -331,6 +334,9 @@ const docTemplate = `{ } ], "description": "Создать новую метку для товара", + "consumes": [ + "application/json" + ], "tags": [ "Merch labels" ], @@ -373,6 +379,9 @@ const docTemplate = `{ } ], "description": "Прикрепить метку к товару", + "consumes": [ + "application/json" + ], "tags": [ "Merch labels" ], @@ -415,6 +424,9 @@ const docTemplate = `{ } ], "description": "Удалить привязку метки к товару", + "consumes": [ + "application/json" + ], "tags": [ "Merch labels" ], @@ -498,6 +510,9 @@ const docTemplate = `{ } ], "description": "Изменить метку", + "consumes": [ + "application/json" + ], "tags": [ "Merch labels" ], @@ -623,6 +638,9 @@ const docTemplate = `{ } ], "description": "Пометить нулевые цены как удаленные", + "consumes": [ + "application/json" + ], "tags": [ "Merch zero prices" ], @@ -1160,11 +1178,11 @@ const docTemplate = `{ "merch.DeleteZeroPrices": { "type": "object", "properties": { - "merch_uuids": { - "type": "array", - "items": { - "type": "string" - } + "id": { + "type": "integer" + }, + "merch_uuid": { + "type": "string" } } }, @@ -1342,6 +1360,9 @@ const docTemplate = `{ "created_at": { "type": "string" }, + "id": { + "type": "integer" + }, "merch_uuid": { "type": "string" }, diff --git a/docs/swagger.json b/docs/swagger.json index 0df1704..f746266 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -98,6 +98,9 @@ } ], "description": "Обновить информацию про мерч по его uuid в json-е", + "consumes": [ + "application/json" + ], "tags": [ "Merch" ], @@ -323,6 +326,9 @@ } ], "description": "Создать новую метку для товара", + "consumes": [ + "application/json" + ], "tags": [ "Merch labels" ], @@ -365,6 +371,9 @@ } ], "description": "Прикрепить метку к товару", + "consumes": [ + "application/json" + ], "tags": [ "Merch labels" ], @@ -407,6 +416,9 @@ } ], "description": "Удалить привязку метки к товару", + "consumes": [ + "application/json" + ], "tags": [ "Merch labels" ], @@ -490,6 +502,9 @@ } ], "description": "Изменить метку", + "consumes": [ + "application/json" + ], "tags": [ "Merch labels" ], @@ -615,6 +630,9 @@ } ], "description": "Пометить нулевые цены как удаленные", + "consumes": [ + "application/json" + ], "tags": [ "Merch zero prices" ], @@ -1152,11 +1170,11 @@ "merch.DeleteZeroPrices": { "type": "object", "properties": { - "merch_uuids": { - "type": "array", - "items": { - "type": "string" - } + "id": { + "type": "integer" + }, + "merch_uuid": { + "type": "string" } } }, @@ -1334,6 +1352,9 @@ "created_at": { "type": "string" }, + "id": { + "type": "integer" + }, "merch_uuid": { "type": "string" }, diff --git a/docs/swagger.yaml b/docs/swagger.yaml index fd4e41b..883b177 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -9,10 +9,10 @@ definitions: type: object merch.DeleteZeroPrices: properties: - merch_uuids: - items: - type: string - type: array + id: + type: integer + merch_uuid: + type: string type: object merch.ImageLink: properties: @@ -126,6 +126,8 @@ definitions: properties: created_at: type: string + id: + type: integer merch_uuid: type: string name: @@ -254,6 +256,8 @@ paths: tags: - Merch put: + consumes: + - application/json description: Обновить информацию про мерч по его uuid в json-е parameters: - description: merch_uuid @@ -453,6 +457,8 @@ paths: tags: - Merch labels post: + consumes: + - application/json description: Создать новую метку для товара parameters: - description: payload @@ -529,6 +535,8 @@ paths: tags: - Merch labels put: + consumes: + - application/json description: Изменить метку parameters: - description: label uuid @@ -560,6 +568,8 @@ paths: - Merch labels /merch/labels/attach: post: + consumes: + - application/json description: Прикрепить метку к товару parameters: - description: payload @@ -586,6 +596,8 @@ paths: - Merch labels /merch/labels/detach: post: + consumes: + - application/json description: Удалить привязку метки к товару parameters: - description: payload @@ -612,6 +624,8 @@ paths: - Merch labels /merch/zeroprices: delete: + consumes: + - application/json description: Пометить нулевые цены как удаленные parameters: - description: payload diff --git a/internal/api/merch/controller.go b/internal/api/merch/controller.go index 33c194f..7ea9636 100644 --- a/internal/api/merch/controller.go +++ b/internal/api/merch/controller.go @@ -95,7 +95,7 @@ func (co *controller) addMerch(c *gin.Context) { // @Description Получить всю информацию про мерч по его uuid // @Tags Merch // @Security BearerAuth -// @Produce json +// @Produce json // @Param uuid path string true "merch_uuid" // @Success 200 {object} MerchDTO // @Failure 400 {object} responses.ErrorResponse400 @@ -129,7 +129,7 @@ func (co *controller) getSingleMerch(c *gin.Context) { // @Description Получить все записи мерча // @Tags Merch // @Security BearerAuth -// @Produce json +// @Produce json // @Success 200 {array} ListResponse // @Failure 400 {object} responses.ErrorResponse400 // @Failure 500 {object} responses.ErrorResponse500 @@ -156,7 +156,7 @@ func (co *controller) getAllMerch(c *gin.Context) { // @Description Обновить информацию про мерч по его uuid в json-е // @Tags Merch // @Security BearerAuth -// Accept json +// @Accept json // @Param body body UpdateMerchDTO true "merch_uuid" // @Success 200 // @Failure 400 {object} responses.ErrorResponse400 @@ -182,6 +182,7 @@ func (co *controller) updateMerch(c *gin.Context) { log.WithError(err).Error("Merch | Failed to get single merch") return } + c.Status(http.StatusOK) } // @Summary Пометить мерч как удаленный @@ -192,7 +193,8 @@ func (co *controller) updateMerch(c *gin.Context) { // @Success 200 {object} MerchDTO // @Failure 400 {object} responses.ErrorResponse400 // @Failure 500 {object} responses.ErrorResponse500 -// @Router /merch/{uuid} [delete] +// +// @Router /merch/{uuid} [delete] func (co *controller) deleteMerch(c *gin.Context) { merchUuid := c.Param("uuid") if merchUuid == "" { @@ -220,12 +222,16 @@ func (co *controller) deleteMerch(c *gin.Context) { // @Description Получить цены мерча за период // @Tags Merch // @Security BearerAuth -// @Produce json +// @Produce json // @Param days query string false "period in days" // @Success 200 {array} PricesResponse // @Failure 400 {object} responses.ErrorResponse400 // @Failure 500 {object} responses.ErrorResponse500 // @Router /prices [get] +// +// @Failure 400 {object} responses.ErrorResponse400 +// @Failure 500 {object} responses.ErrorResponse500 +// @Router /prices [get] func (co *controller) getChartsPrices(c *gin.Context) { daysQuery := strings.ToLower(c.DefaultQuery("days", "")) @@ -250,7 +256,7 @@ func (co *controller) getChartsPrices(c *gin.Context) { // @Description Получить перепады цен мерча за период по его merch_uuid // @Tags Merch // @Security BearerAuth -// @Produce json +// @Produce json // @Param uuid path string true "merch_uuid" // @Param days query string false "period in days" // @Success 200 {object} PricesResponse @@ -338,7 +344,6 @@ func (co *controller) uploadMerchImage(c *gin.Context) { return } - //c.Status(http.StatusOK) c.JSON(http.StatusOK, response) } @@ -346,7 +351,7 @@ func (co *controller) uploadMerchImage(c *gin.Context) { // @Description Получить картинки по merch_uuid и query параметрам // @Tags Merch images // @Security BearerAuth -// @Produce json +// @Produce json // @Param uuid path string true "merch_uuid" // @Param type query string true "image type" // @Success 200 {object} ImageLink @@ -393,7 +398,6 @@ func (co *controller) getMerchImage(c *gin.Context) { //} // //c.JSON(http.StatusOK, link) - c.JSON(http.StatusNotImplemented, gin.H{"msg": "Method deprecated. Request images from image storage."}) } // @Summary Удалить (безвозвратно) картинки по merch_uuid @@ -438,7 +442,7 @@ func (co *controller) deleteMerchImage(c *gin.Context) { // @Description Создать новую метку для товара // @Tags Merch labels // @Security BearerAuth -// Accept json +// @Accept json // @Param payload body LabelDTO true "payload" // @Success 200 // @Failure 400 {object} responses.ErrorResponse400 @@ -474,7 +478,7 @@ func (co *controller) createLabel(c *gin.Context) { // @Description Получить все метки товаров // @Tags Merch labels // @Security BearerAuth -// @Produce json +// @Produce json // @Success 200 {array} LabelsList // @Failure 400 {object} responses.ErrorResponse400 // @Failure 500 {object} responses.ErrorResponse500 @@ -503,7 +507,7 @@ func (co *controller) getLabels(c *gin.Context) { // @Description Изменить метку // @Tags Merch labels // @Security BearerAuth -// Accept json +// @Accept json // @Param uuid path string true "label uuid" // @Param payload body LabelDTO true "payload" // @Success 200 @@ -576,16 +580,16 @@ func (co *controller) deleteLabel(c *gin.Context) { c.Status(http.StatusOK) } -// @Summary Прикрепить метку к товару -// @Description Прикрепить метку к товару -// @Tags Merch labels -// @Security BearerAuth -// Accept json -// @Param payload body LabelLink true "payload" -// @Success 200 -// @Failure 400 {object} responses.ErrorResponse400 -// @Failure 500 {object} responses.ErrorResponse500 -// @Router /merch/labels/attach [post] +// @Summary Прикрепить метку к товару +// @Description Прикрепить метку к товару +// @Tags Merch labels +// @Security BearerAuth +// @Accept json +// @Param payload body LabelLink true "payload" +// @Success 200 +// @Failure 400 {object} responses.ErrorResponse400 +// @Failure 500 {object} responses.ErrorResponse500 +// @Router /merch/labels/attach [post] func (co *controller) attachLabel(c *gin.Context) { const logMsg = "Merch | Attach label" @@ -611,16 +615,16 @@ func (co *controller) attachLabel(c *gin.Context) { c.Status(http.StatusOK) } -// @Summary Удалить привязку метки к товару -// @Description Удалить привязку метки к товару -// @Tags Merch labels -// @Security BearerAuth -// Accept json -// @Param payload body LabelLink true "payload" -// @Success 200 -// @Failure 400 {object} responses.ErrorResponse400 -// @Failure 500 {object} responses.ErrorResponse500 -// @Router /merch/labels/detach [post] +// @Summary Удалить привязку метки к товару +// @Description Удалить привязку метки к товару +// @Tags Merch labels +// @Security BearerAuth +// @Accept json +// @Param payload body LabelLink true "payload" +// @Success 200 +// @Failure 400 {object} responses.ErrorResponse400 +// @Failure 500 {object} responses.ErrorResponse500 +// @Router /merch/labels/detach [post] func (co *controller) detachLabel(c *gin.Context) { const logMsg = "Merch | Detach label" @@ -650,7 +654,7 @@ func (co *controller) detachLabel(c *gin.Context) { // @Description Получить метки товара по его uuid // @Tags Merch labels // @Security BearerAuth -// @Produce json +// @Produce json // @Param uuid path string true "label uuid" // @Success 200 // @Failure 400 {object} responses.ErrorResponse400 @@ -686,10 +690,10 @@ func (co *controller) getMerchLabels(c *gin.Context) { // @Description Получить нулевые цены // @Tags Merch zero prices // @Security BearerAuth -// @Produce json -// @Success 200 {array} ZeroPrice -// @Failure 400 {object} responses.ErrorResponse400 -// @Failure 500 {object} responses.ErrorResponse500 +// @Produce json +// @Success 200 {array} ZeroPrice +// @Failure 400 {object} responses.ErrorResponse400 +// @Failure 500 {object} responses.ErrorResponse500 // @Router /merch/zeroprices [get] func (co *controller) getZeroPrices(c *gin.Context) { const logMsg = "Merch | Get zero prices" @@ -715,11 +719,11 @@ func (co *controller) getZeroPrices(c *gin.Context) { // @Description Пометить нулевые цены как удаленные // @Tags Merch zero prices // @Security BearerAuth -// Accept json +// @Accept json // @Param payload body DeleteZeroPrices true "payload" // @Success 200 -// @Failure 400 {object} responses.ErrorResponse400 -// @Failure 500 {object} responses.ErrorResponse500 +// @Failure 400 {object} responses.ErrorResponse400 +// @Failure 500 {object} responses.ErrorResponse500 // @Router /merch/zeroprices [delete] func (co *controller) deleteZeroPrices(c *gin.Context) { const logMsg = "Merch | Delete zero prices"