From a338fd03b28378add0d8162217753c5dace9eb56 Mon Sep 17 00:00:00 2001 From: nquidox Date: Sat, 6 Dec 2025 17:32:45 +0300 Subject: [PATCH 1/5] added: time util --- internal/interfaces/utils.go | 6 +++++- pkg/utils/time.go | 11 +++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 pkg/utils/time.go diff --git a/internal/interfaces/utils.go b/internal/interfaces/utils.go index dd6b773..31cd891 100644 --- a/internal/interfaces/utils.go +++ b/internal/interfaces/utils.go @@ -1,6 +1,9 @@ package interfaces -import "github.com/gin-gonic/gin" +import ( + "github.com/gin-gonic/gin" + "time" +) type Utils interface { IsEmail(email string) bool @@ -8,4 +11,5 @@ type Utils interface { GetRefreshUuidFromContext(c *gin.Context) (string, error) HashPassword(password string) (string, error) ComparePasswords(hashedPassword string, plainPassword string) error + ParseTime(t string) (time.Time, error) } diff --git a/pkg/utils/time.go b/pkg/utils/time.go new file mode 100644 index 0000000..01bfaf6 --- /dev/null +++ b/pkg/utils/time.go @@ -0,0 +1,11 @@ +package utils + +import "time" + +func (u *Utils) ParseTime(t string) (time.Time, error) { + timeStr, err := time.Parse(time.RFC3339, t) + if err != nil { + return time.Time{}, err + } + return timeStr, nil +} From d997d8bfa4c0a57b68f12a36e66a1b5b3527b7b1 Mon Sep 17 00:00:00 2001 From: nquidox Date: Sat, 6 Dec 2025 17:33:18 +0300 Subject: [PATCH 2/5] added: delete zero prices in period --- internal/api/merch/controller.go | 101 ++++++++++++++++++++++--------- internal/api/merch/repository.go | 17 ++++++ internal/api/merch/service.go | 4 ++ 3 files changed, 94 insertions(+), 28 deletions(-) diff --git a/internal/api/merch/controller.go b/internal/api/merch/controller.go index 7ea9636..a9f922a 100644 --- a/internal/api/merch/controller.go +++ b/internal/api/merch/controller.go @@ -54,6 +54,9 @@ func (h *Handler) RegisterRoutes(r *gin.RouterGroup, authMW gin.HandlerFunc, ref zeroPricesGroup := merchGroup.Group("/zeroprices", authMW) zeroPricesGroup.GET("", h.controller.getZeroPrices) zeroPricesGroup.DELETE("", h.controller.deleteZeroPrices) + + zeroPricesGroup.DELETE("/period", h.controller.deleteZeroPricesPeriod) + } // @Summary Добавить новый мерч @@ -156,7 +159,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 @@ -194,7 +197,7 @@ func (co *controller) updateMerch(c *gin.Context) { // @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 == "" { @@ -229,9 +232,9 @@ func (co *controller) deleteMerch(c *gin.Context) { // @Failure 500 {object} responses.ErrorResponse500 // @Router /prices [get] // -// @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", "")) @@ -442,7 +445,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 @@ -507,7 +510,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 @@ -580,16 +583,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" @@ -615,16 +618,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" @@ -719,7 +722,7 @@ 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 @@ -749,3 +752,45 @@ func (co *controller) deleteZeroPrices(c *gin.Context) { } c.Status(http.StatusOK) } + +// @Summary Пометить нулевые цены как удаленные за указанный период +// @Description Пометить нулевые цены как удаленные за указанный период +// @Tags Merch zero prices +// @Security BearerAuth +// @Param start query string true "start" +// @Param end query string true "end" +// @Success 200 +// @Failure 400 {object} responses.ErrorResponse400 +// @Failure 500 {object} responses.ErrorResponse500 +// @Router /merch/zeroprices/period [delete] +func (co *controller) deleteZeroPricesPeriod(c *gin.Context) { + const logMsg = "Merch | Delete zero prices period" + + userUuid, err := co.utils.GetUserUuidFromContext(c) + if err != nil { + c.JSON(http.StatusInternalServerError, responses.ErrorResponse500{Error: err.Error()}) + log.WithError(err).Error(logMsg) + return + } + + start, err := co.utils.ParseTime(c.Query("start")) + if err != nil { + c.JSON(http.StatusBadRequest, responses.ErrorResponse400{Error: err.Error()}) + log.WithError(err).Error(logMsg) + return + } + + end, err := co.utils.ParseTime(c.Query("end")) + if err != nil { + c.JSON(http.StatusBadRequest, responses.ErrorResponse400{Error: err.Error()}) + log.WithError(err).Error(logMsg) + return + } + + if err = co.service.deleteZeroPricesPeriod(userUuid, start, end); err != nil { + c.JSON(http.StatusInternalServerError, responses.ErrorResponse500{Error: err.Error()}) + log.WithError(err).Error(logMsg) + return + } + c.Status(http.StatusOK) +} diff --git a/internal/api/merch/repository.go b/internal/api/merch/repository.go index e84f551..4a5f3ce 100644 --- a/internal/api/merch/repository.go +++ b/internal/api/merch/repository.go @@ -42,6 +42,7 @@ type prices interface { getZeroPrices(userUuid string) ([]ZeroPrice, error) deleteZeroPrices(list []DeleteZeroPrices) error + deleteZeroPricesPeriod(userUuid string, start, end time.Time) error } type labels interface { @@ -371,3 +372,19 @@ func (r *Repo) deleteZeroPrices(list []DeleteZeroPrices) error { } return nil } + +func (r *Repo) deleteZeroPricesPeriod(userUuid string, start, end time.Time) error { + if err := r.db.Exec(` + UPDATE prices + SET deleted_at = ? + FROM merch + WHERE prices.merch_uuid = merch.merch_uuid + AND merch.user_uuid = ? + AND prices.price = 0 + AND prices.deleted_at IS NULL + AND prices.created_at BETWEEN ? AND ?; + `, time.Now().UTC(), userUuid, start, end).Error; err != nil { + return err + } + return nil +} diff --git a/internal/api/merch/service.go b/internal/api/merch/service.go index d409b71..ae6c2f5 100644 --- a/internal/api/merch/service.go +++ b/internal/api/merch/service.go @@ -662,3 +662,7 @@ func (s *service) deleteZeroPrices(userUuid string, list []DeleteZeroPrices) err return s.repo.deleteZeroPrices(toDelete) } + +func (s *service) deleteZeroPricesPeriod(userUuid string, start, end time.Time) error { + return s.repo.deleteZeroPricesPeriod(userUuid, start, end) +} From f9eac067be59003f3e0bff1c4012ef732313fe00 Mon Sep 17 00:00:00 2001 From: nquidox Date: Sat, 6 Dec 2025 17:33:29 +0300 Subject: [PATCH 3/5] swagger docs update --- docs/docs.go | 47 +++++++++++++++++++++++++++++++++++++++++++++++ docs/swagger.json | 47 +++++++++++++++++++++++++++++++++++++++++++++++ docs/swagger.yaml | 30 ++++++++++++++++++++++++++++++ 3 files changed, 124 insertions(+) diff --git a/docs/docs.go b/docs/docs.go index b97688b..a232b91 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -675,6 +675,53 @@ const docTemplate = `{ } } }, + "/merch/zeroprices/period": { + "delete": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Пометить нулевые цены как удаленные за указанный период", + "tags": [ + "Merch zero prices" + ], + "summary": "Пометить нулевые цены как удаленные за указанный период", + "parameters": [ + { + "type": "string", + "description": "start", + "name": "start", + "in": "query", + "required": true + }, + { + "type": "string", + "description": "end", + "name": "end", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/responses.ErrorResponse400" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/responses.ErrorResponse500" + } + } + } + } + }, "/merch/{uuid}": { "get": { "security": [ diff --git a/docs/swagger.json b/docs/swagger.json index f746266..74a0031 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -667,6 +667,53 @@ } } }, + "/merch/zeroprices/period": { + "delete": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Пометить нулевые цены как удаленные за указанный период", + "tags": [ + "Merch zero prices" + ], + "summary": "Пометить нулевые цены как удаленные за указанный период", + "parameters": [ + { + "type": "string", + "description": "start", + "name": "start", + "in": "query", + "required": true + }, + { + "type": "string", + "description": "end", + "name": "end", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/responses.ErrorResponse400" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/responses.ErrorResponse500" + } + } + } + } + }, "/merch/{uuid}": { "get": { "security": [ diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 883b177..31eaa0d 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -674,6 +674,36 @@ paths: summary: Получить нулевые цены tags: - Merch zero prices + /merch/zeroprices/period: + delete: + description: Пометить нулевые цены как удаленные за указанный период + parameters: + - description: start + in: query + name: start + required: true + type: string + - description: end + in: query + name: end + required: true + type: string + responses: + "200": + description: OK + "400": + description: Bad Request + schema: + $ref: '#/definitions/responses.ErrorResponse400' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/responses.ErrorResponse500' + security: + - BearerAuth: [] + summary: Пометить нулевые цены как удаленные за указанный период + tags: + - Merch zero prices /prices: get: description: Получить цены мерча за период From 4c59ab3f58f5f1cbe2eedf3ec5543d93cb6e385e Mon Sep 17 00:00:00 2001 From: nquidox Date: Sat, 6 Dec 2025 19:14:21 +0300 Subject: [PATCH 4/5] return origin name instead of code --- internal/api/merch/dto.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/api/merch/dto.go b/internal/api/merch/dto.go index 7ee7281..5b9180c 100644 --- a/internal/api/merch/dto.go +++ b/internal/api/merch/dto.go @@ -86,7 +86,7 @@ type ZeroPrice struct { CreatedAt time.Time `json:"created_at"` MerchUuid string `json:"merch_uuid"` Name string `json:"name"` - Origin string `json:"origin"` + Origin Origin `json:"origin"` } type DeleteZeroPrices struct { From 8f2b0470b19e9dd4b4cece4a76c8b8e22f930077 Mon Sep 17 00:00:00 2001 From: nquidox Date: Sun, 7 Dec 2025 13:37:15 +0300 Subject: [PATCH 5/5] false zero price bugfix --- internal/api/merch/repository.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/api/merch/repository.go b/internal/api/merch/repository.go index 4a5f3ce..fe5dd7d 100644 --- a/internal/api/merch/repository.go +++ b/internal/api/merch/repository.go @@ -339,8 +339,8 @@ func (r *Repo) getZeroPrices(userUuid string) ([]ZeroPrice, error) { WITH price_with_neighbors AS ( SELECT 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 + LAG(price) OVER (PARTITION BY p.merch_uuid, p.origin ORDER BY p.created_at, p.id) AS prev_price, + LEAD(price) OVER (PARTITION BY p.merch_uuid, p.origin 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