From a338fd03b28378add0d8162217753c5dace9eb56 Mon Sep 17 00:00:00 2001 From: nquidox Date: Sat, 6 Dec 2025 17:32:45 +0300 Subject: [PATCH 01/11] 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 02/11] 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 03/11] 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 04/11] 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 05/11] 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 From e0e5761cd64f489381c3d659e6fcb589ee145261 Mon Sep 17 00:00:00 2001 From: nquidox Date: Mon, 15 Dec 2025 17:34:01 +0300 Subject: [PATCH 06/11] refactor + new origin --- internal/api/merch/controller.go | 4 +- internal/api/merch/dto.go | 37 ++++++--- internal/api/merch/model.go | 37 ++------- internal/api/merch/origins/constructor.go | 55 ++++++++++++++ internal/api/merch/origins/model.go | 34 +++++++++ .../merch/{origins.go => origins/types.go} | 10 ++- internal/api/merch/provider.go | 15 ++-- internal/api/merch/repository.go | 76 ++++++++++--------- internal/api/merch/service.go | 68 +++++++++++------ migrations.sql | 7 ++ 10 files changed, 228 insertions(+), 115 deletions(-) create mode 100644 internal/api/merch/origins/constructor.go create mode 100644 internal/api/merch/origins/model.go rename internal/api/merch/{origins.go => origins/types.go} (83%) diff --git a/internal/api/merch/controller.go b/internal/api/merch/controller.go index a9f922a..9d5e5a9 100644 --- a/internal/api/merch/controller.go +++ b/internal/api/merch/controller.go @@ -757,8 +757,8 @@ func (co *controller) deleteZeroPrices(c *gin.Context) { // @Description Пометить нулевые цены как удаленные за указанный период // @Tags Merch zero prices // @Security BearerAuth -// @Param start query string true "start" -// @Param end query string true "end" +// @Param start query string true "start" +// @Param end query string true "end" // @Success 200 // @Failure 400 {object} responses.ErrorResponse400 // @Failure 500 {object} responses.ErrorResponse500 diff --git a/internal/api/merch/dto.go b/internal/api/merch/dto.go index 5b9180c..17e463b 100644 --- a/internal/api/merch/dto.go +++ b/internal/api/merch/dto.go @@ -1,26 +1,39 @@ package merch -import "time" +import ( + "merch-parser-api/internal/api/merch/origins" + "time" +) type merchBundle struct { Merch *Merch - Surugaya *Surugaya - Mandarake *Mandarake + Surugaya *origins.OriginSurugaya + Mandarake *origins.OriginMandarake + Amiami *origins.OriginAmiami } type MerchDTO struct { MerchUuid string `json:"merch_uuid"` Name string `json:"name"` OriginSurugaya SurugayaDTO `json:"origin_surugaya"` OriginMandarake MandarakeDTO `json:"origin_mandarake"` + OriginAmiami AmiamiDTO `json:"origin_amiami"` Labels []string `json:"labels,omitempty" gorm:"-"` } -type SurugayaDTO struct { +type OriginDTO struct { Link string `json:"link"` } +type SurugayaDTO struct { + OriginDTO +} + type MandarakeDTO struct { - Link string `json:"link"` + OriginDTO +} + +type AmiamiDTO struct { + OriginDTO } type SingleMerchResponse struct { @@ -41,7 +54,7 @@ type PriceEntry struct { } type OriginWithPrices struct { - Origin Origin `json:"origin"` + Origin origins.Origin `json:"origins"` Prices []PriceEntry } @@ -54,7 +67,7 @@ type PricesResponse struct { type UpdateMerchDTO struct { MerchUuid string `json:"merch_uuid"` Name string `json:"name"` - Origin string `json:"origin"` + Origin string `json:"origins"` Link string `json:"link"` } @@ -82,11 +95,11 @@ 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"` - Origin Origin `json:"origin"` + Id int `json:"id"` + CreatedAt time.Time `json:"created_at"` + MerchUuid string `json:"merch_uuid"` + Name string `json:"name"` + Origin origins.Origin `json:"origins"` } type DeleteZeroPrices struct { diff --git a/internal/api/merch/model.go b/internal/api/merch/model.go index 2dd6e0e..d9d43ca 100644 --- a/internal/api/merch/model.go +++ b/internal/api/merch/model.go @@ -2,6 +2,7 @@ package merch import ( "database/sql" + "merch-parser-api/internal/api/merch/origins" "time" ) @@ -19,36 +20,14 @@ func (Merch) TableName() string { return "merch" } -type Surugaya struct { - Id uint `gorm:"primary_key" json:"-"` - DeletedAt sql.NullTime `json:"-"` - MerchUuid string `json:"-"` - Link string `json:"link"` -} - -func (Surugaya) TableName() string { - return "origin_surugaya" -} - -type Mandarake struct { - Id uint `gorm:"primary_key" json:"-"` - DeletedAt sql.NullTime `json:"-"` - MerchUuid string `json:"-"` - Link string `json:"link"` -} - -func (Mandarake) TableName() string { - return "origin_mandarake" -} - type Price struct { - Id uint `json:"id" gorm:"primary_key"` - CreatedAt time.Time `json:"created_at" gorm:"column:created_at"` - UpdatedAt sql.NullTime `json:"updated_at,omitempty" gorm:"column:updated_at"` - DeletedAt sql.NullTime `json:"deleted_at,omitempty" gorm:"column:deleted_at"` - MerchUuid string `json:"merch_uuid" gorm:"column:merch_uuid"` - Price int `json:"price" gorm:"column:price"` - Origin Origin `json:"origin" gorm:"column:origin;type:integer"` + Id uint `json:"id" gorm:"primary_key"` + CreatedAt time.Time `json:"created_at" gorm:"column:created_at"` + UpdatedAt sql.NullTime `json:"updated_at,omitempty" gorm:"column:updated_at"` + DeletedAt sql.NullTime `json:"deleted_at,omitempty" gorm:"column:deleted_at"` + MerchUuid string `json:"merch_uuid" gorm:"column:merch_uuid"` + Price int `json:"price" gorm:"column:price"` + Origin origins.Origin `json:"origins" gorm:"column:origins;type:integer"` } type Label struct { diff --git a/internal/api/merch/origins/constructor.go b/internal/api/merch/origins/constructor.go new file mode 100644 index 0000000..2a51534 --- /dev/null +++ b/internal/api/merch/origins/constructor.go @@ -0,0 +1,55 @@ +package origins + +import "database/sql" + +func AllOriginModels() []interface{} { + return []interface{}{ + &OriginSurugaya{}, + &OriginMandarake{}, + &OriginAmiami{}, + } +} + +func NewSurugaya(merchUuid, link string) *OriginSurugaya { + return &OriginSurugaya{ + BaseOrigin{ + DeletedAt: sql.NullTime{}, + MerchUuid: merchUuid, + Link: link, + }, + } +} + +func NewMandarake(merchUuid, link string) *OriginMandarake { + return &OriginMandarake{ + BaseOrigin{ + DeletedAt: sql.NullTime{}, + MerchUuid: merchUuid, + Link: link, + }, + } +} + +func NewAmiami(merchUuid, link string) *OriginAmiami { + return &OriginAmiami{ + BaseOrigin{ + DeletedAt: sql.NullTime{}, + MerchUuid: merchUuid, + Link: link, + }, + } +} + +type OriginFactory func(merchUuid, link string) interface{} + +var OriginFactories = map[string]OriginFactory{ + "surugaya": func(merchUuid, link string) interface{} { + return &OriginSurugaya{BaseOrigin: BaseOrigin{MerchUuid: merchUuid, Link: link}} + }, + "mandarake": func(merchUuid, link string) interface{} { + return &OriginMandarake{BaseOrigin: BaseOrigin{MerchUuid: merchUuid, Link: link}} + }, + "amiami": func(merchUuid, link string) interface{} { + return &OriginAmiami{BaseOrigin: BaseOrigin{MerchUuid: merchUuid, Link: link}} + }, +} diff --git a/internal/api/merch/origins/model.go b/internal/api/merch/origins/model.go new file mode 100644 index 0000000..d2ee8cf --- /dev/null +++ b/internal/api/merch/origins/model.go @@ -0,0 +1,34 @@ +package origins + +import "database/sql" + +type BaseOrigin struct { + Id uint `gorm:"primary_key" json:"-"` + DeletedAt sql.NullTime `json:"-"` + MerchUuid string `json:"-"` + Link string `json:"link"` +} + +type OriginSurugaya struct { + BaseOrigin +} + +func (OriginSurugaya) TableName() string { + return "origin_surugaya" +} + +type OriginMandarake struct { + BaseOrigin +} + +func (OriginMandarake) TableName() string { + return "origin_mandarake" +} + +type OriginAmiami struct { + BaseOrigin +} + +func (OriginAmiami) TableName() string { + return "origin_amiami" +} diff --git a/internal/api/merch/origins.go b/internal/api/merch/origins/types.go similarity index 83% rename from internal/api/merch/origins.go rename to internal/api/merch/origins/types.go index 22175e5..303176c 100644 --- a/internal/api/merch/origins.go +++ b/internal/api/merch/origins/types.go @@ -1,4 +1,4 @@ -package merch +package origins import ( "database/sql/driver" @@ -9,13 +9,15 @@ import ( type Origin int const ( - surugaya = (iota + 1) * 1000 - mandarake + Surugaya = (iota + 1) * 1000 + Mandarake + Amiami ) var Origins = [...]string{ "surugaya", "mandarake", + "amiami", } func (o Origin) String() string { @@ -30,7 +32,7 @@ func (o Origin) MarshalJSON() ([]byte, error) { return json.Marshal(o.String()) } -func parseOrigin(s string) (Origin, bool) { +func ParseOrigin(s string) (Origin, bool) { for i, name := range Origins { if name == strings.ToLower(s) { return Origin((i + 1) * 1000), true diff --git a/internal/api/merch/provider.go b/internal/api/merch/provider.go index 27ab846..c70da91 100644 --- a/internal/api/merch/provider.go +++ b/internal/api/merch/provider.go @@ -4,13 +4,14 @@ import ( "database/sql" log "github.com/sirupsen/logrus" "gorm.io/gorm" + "merch-parser-api/internal/api/merch/origins" "merch-parser-api/internal/shared" "time" ) type Link struct { - Surugaya []Surugaya - Mandarake []Mandarake + Surugaya []origins.OriginSurugaya + Mandarake []origins.OriginMandarake } type TaskProvider struct { @@ -84,7 +85,7 @@ func (p *TaskProvider) InsertPrices(prices []shared.TaskResult) error { var insertPrices []Price for _, item := range prices { - origin, ok := parseOrigin(item.Origin) + origin, ok := origins.ParseOrigin(item.Origin) if !ok { continue } @@ -107,13 +108,13 @@ func (p *TaskProvider) InsertPrices(prices []shared.TaskResult) error { } func (r *TaskRepo) getLinks() (*Link, error) { - var surugayaList []Surugaya - if err := r.db.Model(&Surugaya{}).Where("deleted_at IS NULL").Find(&surugayaList).Error; err != nil { + var surugayaList []origins.OriginSurugaya + if err := r.db.Model(&origins.OriginSurugaya{}).Where("deleted_at IS NULL").Find(&surugayaList).Error; err != nil { return nil, err } - var mandarakeList []Mandarake - if err := r.db.Model(&Mandarake{}).Where("deleted_at IS NULL").Find(&mandarakeList).Error; err != nil { + var mandarakeList []origins.OriginMandarake + if err := r.db.Model(&origins.OriginMandarake{}).Where("deleted_at IS NULL").Find(&mandarakeList).Error; err != nil { return nil, err } diff --git a/internal/api/merch/repository.go b/internal/api/merch/repository.go index fe5dd7d..bff25b4 100644 --- a/internal/api/merch/repository.go +++ b/internal/api/merch/repository.go @@ -3,8 +3,10 @@ package merch import ( "database/sql" "errors" + "fmt" "gorm.io/gorm" "gorm.io/gorm/clause" + "merch-parser-api/internal/api/merch/origins" "time" ) @@ -61,11 +63,15 @@ func (r *Repo) addMerch(bundle merchBundle) error { return err } - if err := r.db.Model(&Surugaya{}).Create(bundle.Surugaya).Error; err != nil { + if err := r.db.Model(&origins.OriginSurugaya{}).Create(bundle.Surugaya).Error; err != nil { return err } - if err := r.db.Model(&Mandarake{}).Create(bundle.Mandarake).Error; err != nil { + if err := r.db.Model(&origins.OriginMandarake{}).Create(bundle.Mandarake).Error; err != nil { + return err + } + + if err := r.db.Model(&origins.OriginAmiami{}).Create(bundle.Amiami).Error; err != nil { return err } @@ -112,7 +118,7 @@ func (r *Repo) getSingleMerch(userUuid, merchUuid string) (merchBundle, error) { return merchBundle{}, err } - var surugaya Surugaya + var surugaya origins.OriginSurugaya if err := r.db. Where("merch_uuid = ?", merchUuid). First(&surugaya).Error; err != nil { @@ -121,7 +127,7 @@ func (r *Repo) getSingleMerch(userUuid, merchUuid string) (merchBundle, error) { } } - var mandarake Mandarake + var mandarake origins.OriginMandarake if err := r.db. Where("merch_uuid = ?", merchUuid). First(&mandarake).Error; err != nil { @@ -130,10 +136,20 @@ func (r *Repo) getSingleMerch(userUuid, merchUuid string) (merchBundle, error) { } } + var amiami origins.OriginAmiami + if err := r.db. + Where("merch_uuid = ?", merchUuid). + First(&amiami).Error; err != nil { + if !errors.Is(err, gorm.ErrRecordNotFound) { + return merchBundle{}, err + } + } + return merchBundle{ Merch: &merch, Surugaya: &surugaya, Mandarake: &mandarake, + Amiami: &amiami, }, nil } @@ -164,22 +180,13 @@ func (r *Repo) updateMerch(payload UpdateMerchDTO, userUuid string) error { return err } - switch payload.Origin { - case "surugaya": - if err := r.upsertOrigin(&Surugaya{ - MerchUuid: payload.MerchUuid, - Link: payload.Link, - }); err != nil { - return err - } - - case "mandarake": - if err := r.upsertOrigin(&Mandarake{ - MerchUuid: payload.MerchUuid, - Link: payload.Link, - }); err != nil { + if factory, ok := origins.OriginFactories[payload.Origin]; ok { + model := factory(payload.MerchUuid, payload.Link) + if err := r.upsertOrigin(model); err != nil { return err } + } else if payload.Origin != "" { + return fmt.Errorf("unsupported origin: %s", payload.Origin) } return nil @@ -196,18 +203,15 @@ func (r *Repo) deleteMerch(userUuid, merchUuid string) error { return err } - if err := r.db. - Model(&Surugaya{}). - Where("merch_uuid = ?", merchUuid). - Update("deleted_at", now).Error; err != nil { - return err - } + ors := origins.AllOriginModels() - if err := r.db. - Model(&Mandarake{}). - Where("merch_uuid = ?", merchUuid). - Update("deleted_at", now).Error; err != nil { - return err + for _, origin := range ors { + if err := r.db. + Model(origin). + Where("merch_uuid = ?", merchUuid). + Update("deleted_at", now).Error; err != nil { + return err + } } return nil @@ -226,7 +230,7 @@ func (r *Repo) getAllUserMerch(userUuid string) (merchList []Merch, err error) { func (r *Repo) getPricesWithDays(userUuid string, period time.Time) (prices []Price, err error) { err = r.db.Raw(` - SELECT p.created_at, p.merch_uuid, p.price, p.origin + SELECT p.created_at, p.merch_uuid, p.price, p.origins FROM prices AS p JOIN merch AS m ON m.merch_uuid = p.merch_uuid WHERE m.user_uuid = ? @@ -244,9 +248,9 @@ func (r *Repo) getPricesWithDays(userUuid string, period time.Time) (prices []Pr func (r *Repo) getDistinctPrices(userUuid, merchUuid string, period time.Time) (prices []Price, err error) { err = r.db.Raw(` - SELECT price, created_at, origin + SELECT price, created_at, origins FROM ( - SELECT DISTINCT ON (price) price, created_at, origin + SELECT DISTINCT ON (price) price, created_at, origins FROM prices WHERE merch_uuid = ? AND created_at > ? @@ -338,9 +342,9 @@ func (r *Repo) getZeroPrices(userUuid string) ([]ZeroPrice, error) { if err := r.db.Raw(` 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, 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 + p.id, p.created_at, p.merch_uuid, p.price, p.origins, m.name, + LAG(price) OVER (PARTITION BY p.merch_uuid, p.origins ORDER BY p.created_at, p.id) AS prev_price, + LEAD(price) OVER (PARTITION BY p.merch_uuid, p.origins 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 @@ -348,7 +352,7 @@ func (r *Repo) getZeroPrices(userUuid string) ([]ZeroPrice, error) { AND m.user_uuid = ?) SELECT - id, created_at, merch_uuid, origin, name + id, created_at, merch_uuid, origins, name FROM price_with_neighbors WHERE price = 0 diff --git a/internal/api/merch/service.go b/internal/api/merch/service.go index ae6c2f5..11b303e 100644 --- a/internal/api/merch/service.go +++ b/internal/api/merch/service.go @@ -12,6 +12,7 @@ import ( "image" "image/jpeg" "io" + "merch-parser-api/internal/api/merch/origins" "merch-parser-api/internal/interfaces" is "merch-parser-api/proto/imageStorage" "mime/multipart" @@ -58,7 +59,7 @@ func (s *service) addMerch(payload MerchDTO, userUuid string) error { merchUuid := uuid.NewString() bundle := merchBundle{ - &Merch{ + Merch: &Merch{ CreatedAt: time.Time{}, UpdatedAt: sql.NullTime{Valid: false}, DeletedAt: sql.NullTime{Valid: false}, @@ -67,17 +68,9 @@ func (s *service) addMerch(payload MerchDTO, userUuid string) error { Name: payload.Name, }, - &Surugaya{ - DeletedAt: sql.NullTime{}, - MerchUuid: merchUuid, - Link: payload.OriginSurugaya.Link, - }, - - &Mandarake{ - DeletedAt: sql.NullTime{}, - MerchUuid: merchUuid, - Link: payload.OriginMandarake.Link, - }, + Surugaya: origins.NewSurugaya(merchUuid, payload.OriginSurugaya.Link), + Mandarake: origins.NewMandarake(merchUuid, payload.OriginMandarake.Link), + Amiami: origins.NewAmiami(merchUuid, payload.OriginAmiami.Link), } return s.repo.addMerch(bundle) } @@ -92,10 +85,19 @@ func (s *service) getSingleMerch(userUuid, merchUuid string) (MerchDTO, error) { MerchUuid: bundle.Merch.MerchUuid, Name: bundle.Merch.Name, OriginSurugaya: SurugayaDTO{ - Link: bundle.Surugaya.Link, + OriginDTO{ + Link: bundle.Surugaya.Link, + }, }, OriginMandarake: MandarakeDTO{ - Link: bundle.Mandarake.Link, + OriginDTO{ + Link: bundle.Mandarake.Link, + }, + }, + OriginAmiami: AmiamiDTO{ + OriginDTO{ + Link: bundle.Amiami.Link, + }, }, }, nil } @@ -137,7 +139,7 @@ func (s *service) updateMerch(payload UpdateMerchDTO, userUuid string) error { } if payload.Origin == "" { - return errors.New("no origin provided") + return errors.New("no origins provided") } return s.repo.updateMerch(payload, userUuid) @@ -178,10 +180,10 @@ func (s *service) getPrices(userUuid string, days string) ([]PricesResponse, err return nil, err } - pricesMap := make(map[string]map[Origin][]PriceEntry) + pricesMap := make(map[string]map[origins.Origin][]PriceEntry) for _, item := range pricesList { if _, ok := pricesMap[item.MerchUuid]; !ok { - pricesMap[item.MerchUuid] = make(map[Origin][]PriceEntry) + pricesMap[item.MerchUuid] = make(map[origins.Origin][]PriceEntry) } pricesMap[item.MerchUuid][item.Origin] = append(pricesMap[item.MerchUuid][item.Origin], PriceEntry{ @@ -192,16 +194,22 @@ func (s *service) getPrices(userUuid string, days string) ([]PricesResponse, err for i := range response { originSurugaya := OriginWithPrices{ - Origin: surugaya, - Prices: pricesMap[response[i].MerchUuid][surugaya], + Origin: origins.Surugaya, + Prices: pricesMap[response[i].MerchUuid][origins.Surugaya], } response[i].Origins = append(response[i].Origins, originSurugaya) originMandarake := OriginWithPrices{ - Origin: mandarake, - Prices: pricesMap[response[i].MerchUuid][mandarake], + Origin: origins.Mandarake, + Prices: pricesMap[response[i].MerchUuid][origins.Mandarake], } response[i].Origins = append(response[i].Origins, originMandarake) + + originAmiami := OriginWithPrices{ + Origin: origins.Amiami, + Prices: pricesMap[response[i].MerchUuid][origins.Amiami], + } + response[i].Origins = append(response[i].Origins, originAmiami) } return response, nil @@ -218,27 +226,37 @@ func (s *service) getDistinctPrices(userUuid, merchUuid, days string) (PricesRes } originSurugaya := OriginWithPrices{ - Origin: surugaya, + Origin: origins.Surugaya, Prices: []PriceEntry{}, } originMandarake := OriginWithPrices{ - Origin: mandarake, + Origin: origins.Mandarake, + Prices: []PriceEntry{}, + } + + originAmiami := OriginWithPrices{ + Origin: origins.Amiami, Prices: []PriceEntry{}, } for _, item := range result { switch item.Origin { - case surugaya: + case origins.Surugaya: originSurugaya.Prices = append(originSurugaya.Prices, PriceEntry{ CreatedAt: item.CreatedAt.Unix(), Value: item.Price, }) - case mandarake: + case origins.Mandarake: originMandarake.Prices = append(originMandarake.Prices, PriceEntry{ CreatedAt: item.CreatedAt.Unix(), Value: item.Price, }) + case origins.Amiami: + originAmiami.Prices = append(originAmiami.Prices, PriceEntry{ + CreatedAt: item.CreatedAt.Unix(), + Value: item.Price, + }) } } diff --git a/migrations.sql b/migrations.sql index 1ffe7e5..69e7a93 100644 --- a/migrations.sql +++ b/migrations.sql @@ -46,6 +46,13 @@ CREATE TABLE origin_mandarake( link TEXT ); +CREATE TABLE origin_amiami( + id BIGSERIAL PRIMARY KEY, + deleted_at TIMESTAMP WITH TIME ZONE NULL, + merch_uuid VARCHAR(36) NOT NULL UNIQUE, + link TEXT +); + CREATE TABLE prices( id BIGSERIAL PRIMARY KEY, From 3d80aff71f735114b26c209b37e51d5489a1ef12 Mon Sep 17 00:00:00 2001 From: nquidox Date: Mon, 15 Dec 2025 17:34:29 +0300 Subject: [PATCH 07/11] swagger docs update --- docs/docs.go | 6 +++--- docs/swagger.json | 8 ++++---- docs/swagger.yaml | 8 ++++---- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/docs.go b/docs/docs.go index a232b91..2477d6c 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -1337,7 +1337,7 @@ const docTemplate = `{ "merch.OriginWithPrices": { "type": "object", "properties": { - "origin": { + "origins": { "type": "integer" }, "prices": { @@ -1396,7 +1396,7 @@ const docTemplate = `{ "name": { "type": "string" }, - "origin": { + "origins": { "type": "string" } } @@ -1416,7 +1416,7 @@ const docTemplate = `{ "name": { "type": "string" }, - "origin": { + "origins": { "type": "string" } } diff --git a/docs/swagger.json b/docs/swagger.json index 74a0031..50e1f7c 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -1329,7 +1329,7 @@ "merch.OriginWithPrices": { "type": "object", "properties": { - "origin": { + "origins": { "type": "integer" }, "prices": { @@ -1388,7 +1388,7 @@ "name": { "type": "string" }, - "origin": { + "origins": { "type": "string" } } @@ -1408,8 +1408,8 @@ "name": { "type": "string" }, - "origin": { - "type": "string" + "origins": { + "type": "integer" } } }, diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 31eaa0d..6a7ae2c 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -81,7 +81,7 @@ definitions: type: object merch.OriginWithPrices: properties: - origin: + origins: type: integer prices: items: @@ -119,7 +119,7 @@ definitions: type: string name: type: string - origin: + origins: type: string type: object merch.ZeroPrice: @@ -132,8 +132,8 @@ definitions: type: string name: type: string - origin: - type: string + origins: + type: integer type: object responses.ErrorResponse400: properties: From 9eed4c99cc9449b4f3432870f8ae44220e37ba2f Mon Sep 17 00:00:00 2001 From: nquidox Date: Mon, 15 Dec 2025 18:14:54 +0300 Subject: [PATCH 08/11] path fix --- internal/api/merch/controller.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/api/merch/controller.go b/internal/api/merch/controller.go index 9d5e5a9..47d774d 100644 --- a/internal/api/merch/controller.go +++ b/internal/api/merch/controller.go @@ -68,7 +68,7 @@ func (h *Handler) RegisterRoutes(r *gin.RouterGroup, authMW gin.HandlerFunc, ref // @Success 200 // @Failure 400 {object} responses.ErrorResponse400 // @Failure 500 {object} responses.ErrorResponse500 -// @Router /merch [post] +// @Router /merch/ [post] func (co *controller) addMerch(c *gin.Context) { var payload MerchDTO if err := c.ShouldBind(&payload); err != nil { From e9b58d171f56156a3f75ec98b761adf226ad893c Mon Sep 17 00:00:00 2001 From: nquidox Date: Mon, 15 Dec 2025 18:15:16 +0300 Subject: [PATCH 09/11] tags fixes --- internal/api/merch/dto.go | 6 +++--- internal/api/merch/model.go | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/api/merch/dto.go b/internal/api/merch/dto.go index 17e463b..d1bee26 100644 --- a/internal/api/merch/dto.go +++ b/internal/api/merch/dto.go @@ -54,7 +54,7 @@ type PriceEntry struct { } type OriginWithPrices struct { - Origin origins.Origin `json:"origins"` + Origin origins.Origin `json:"origin"` Prices []PriceEntry } @@ -67,7 +67,7 @@ type PricesResponse struct { type UpdateMerchDTO struct { MerchUuid string `json:"merch_uuid"` Name string `json:"name"` - Origin string `json:"origins"` + Origin string `json:"origin"` Link string `json:"link"` } @@ -99,7 +99,7 @@ type ZeroPrice struct { CreatedAt time.Time `json:"created_at"` MerchUuid string `json:"merch_uuid"` Name string `json:"name"` - Origin origins.Origin `json:"origins"` + Origin origins.Origin `json:"origin"` } type DeleteZeroPrices struct { diff --git a/internal/api/merch/model.go b/internal/api/merch/model.go index d9d43ca..b6dc584 100644 --- a/internal/api/merch/model.go +++ b/internal/api/merch/model.go @@ -27,7 +27,7 @@ type Price struct { DeletedAt sql.NullTime `json:"deleted_at,omitempty" gorm:"column:deleted_at"` MerchUuid string `json:"merch_uuid" gorm:"column:merch_uuid"` Price int `json:"price" gorm:"column:price"` - Origin origins.Origin `json:"origins" gorm:"column:origins;type:integer"` + Origin origins.Origin `json:"origin" gorm:"column:origins;type:integer"` } type Label struct { From 895605e6b8d2fa11af5497bfe270def1a0b6562f Mon Sep 17 00:00:00 2001 From: nquidox Date: Mon, 15 Dec 2025 18:15:28 +0300 Subject: [PATCH 10/11] swagger docs update --- docs/docs.go | 101 +++++++++++++++++++++++++--------------------- docs/swagger.json | 99 ++++++++++++++++++++++++--------------------- docs/swagger.yaml | 58 ++++++++++++++------------ 3 files changed, 141 insertions(+), 117 deletions(-) diff --git a/docs/docs.go b/docs/docs.go index 2477d6c..9ec07c9 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -15,51 +15,6 @@ const docTemplate = `{ "host": "{{.Host}}", "basePath": "{{.BasePath}}", "paths": { - "/merch": { - "post": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Добавить новый мерч", - "consumes": [ - "application/json" - ], - "tags": [ - "Merch" - ], - "summary": "Добавить новый мерч", - "parameters": [ - { - "description": "новый мерч", - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/merch.MerchDTO" - } - } - ], - "responses": { - "200": { - "description": "OK" - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/responses.ErrorResponse400" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/responses.ErrorResponse500" - } - } - } - } - }, "/merch/": { "get": { "security": [ @@ -141,6 +96,49 @@ const docTemplate = `{ } } } + }, + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Добавить новый мерч", + "consumes": [ + "application/json" + ], + "tags": [ + "Merch" + ], + "summary": "Добавить новый мерч", + "parameters": [ + { + "description": "новый мерч", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/merch.MerchDTO" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/responses.ErrorResponse400" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/responses.ErrorResponse500" + } + } + } } }, "/merch/images/{uuid}": { @@ -1222,6 +1220,14 @@ const docTemplate = `{ } } }, + "merch.AmiamiDTO": { + "type": "object", + "properties": { + "link": { + "type": "string" + } + } + }, "merch.DeleteZeroPrices": { "type": "object", "properties": { @@ -1326,6 +1332,9 @@ const docTemplate = `{ "name": { "type": "string" }, + "origin_amiami": { + "$ref": "#/definitions/merch.AmiamiDTO" + }, "origin_mandarake": { "$ref": "#/definitions/merch.MandarakeDTO" }, @@ -1417,7 +1426,7 @@ const docTemplate = `{ "type": "string" }, "origins": { - "type": "string" + "type": "integer" } } }, diff --git a/docs/swagger.json b/docs/swagger.json index 50e1f7c..d132129 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -7,51 +7,6 @@ }, "basePath": "/api/v2", "paths": { - "/merch": { - "post": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Добавить новый мерч", - "consumes": [ - "application/json" - ], - "tags": [ - "Merch" - ], - "summary": "Добавить новый мерч", - "parameters": [ - { - "description": "новый мерч", - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/merch.MerchDTO" - } - } - ], - "responses": { - "200": { - "description": "OK" - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/responses.ErrorResponse400" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/responses.ErrorResponse500" - } - } - } - } - }, "/merch/": { "get": { "security": [ @@ -133,6 +88,49 @@ } } } + }, + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Добавить новый мерч", + "consumes": [ + "application/json" + ], + "tags": [ + "Merch" + ], + "summary": "Добавить новый мерч", + "parameters": [ + { + "description": "новый мерч", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/merch.MerchDTO" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/responses.ErrorResponse400" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/responses.ErrorResponse500" + } + } + } } }, "/merch/images/{uuid}": { @@ -1214,6 +1212,14 @@ } } }, + "merch.AmiamiDTO": { + "type": "object", + "properties": { + "link": { + "type": "string" + } + } + }, "merch.DeleteZeroPrices": { "type": "object", "properties": { @@ -1318,6 +1324,9 @@ "name": { "type": "string" }, + "origin_amiami": { + "$ref": "#/definitions/merch.AmiamiDTO" + }, "origin_mandarake": { "$ref": "#/definitions/merch.MandarakeDTO" }, diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 6a7ae2c..ea2f927 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -7,6 +7,11 @@ definitions: thumbnail: type: string type: object + merch.AmiamiDTO: + properties: + link: + type: string + type: object merch.DeleteZeroPrices: properties: id: @@ -74,6 +79,8 @@ definitions: type: string name: type: string + origin_amiami: + $ref: '#/definitions/merch.AmiamiDTO' origin_mandarake: $ref: '#/definitions/merch.MandarakeDTO' origin_surugaya: @@ -202,7 +209,31 @@ info: title: Merch Parser version: 2.0.0-alpha paths: - /merch: + /merch/: + get: + description: Получить все записи мерча + produces: + - application/json + responses: + "200": + description: OK + schema: + items: + $ref: '#/definitions/merch.ListResponse' + type: array + "400": + description: Bad Request + schema: + $ref: '#/definitions/responses.ErrorResponse400' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/responses.ErrorResponse500' + security: + - BearerAuth: [] + summary: Получить все записи мерча + tags: + - Merch post: consumes: - application/json @@ -230,31 +261,6 @@ paths: summary: Добавить новый мерч tags: - Merch - /merch/: - get: - description: Получить все записи мерча - produces: - - application/json - responses: - "200": - description: OK - schema: - items: - $ref: '#/definitions/merch.ListResponse' - type: array - "400": - description: Bad Request - schema: - $ref: '#/definitions/responses.ErrorResponse400' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/responses.ErrorResponse500' - security: - - BearerAuth: [] - summary: Получить все записи мерча - tags: - - Merch put: consumes: - application/json From 1c5802dedf9658c08fb8aafc52f7faab34104453 Mon Sep 17 00:00:00 2001 From: nquidox Date: Mon, 15 Dec 2025 21:02:05 +0300 Subject: [PATCH 11/11] fixes --- internal/api/merch/controller.go | 4 ---- internal/api/merch/dto.go | 9 ++++---- internal/api/merch/model.go | 2 +- internal/api/merch/repository.go | 6 ++--- internal/api/merch/service.go | 39 +++++++++++++++++--------------- 5 files changed, 30 insertions(+), 30 deletions(-) diff --git a/internal/api/merch/controller.go b/internal/api/merch/controller.go index 47d774d..994c677 100644 --- a/internal/api/merch/controller.go +++ b/internal/api/merch/controller.go @@ -231,10 +231,6 @@ func (co *controller) deleteMerch(c *gin.Context) { // @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", "")) diff --git a/internal/api/merch/dto.go b/internal/api/merch/dto.go index d1bee26..c64787e 100644 --- a/internal/api/merch/dto.go +++ b/internal/api/merch/dto.go @@ -21,7 +21,8 @@ type MerchDTO struct { } type OriginDTO struct { - Link string `json:"link"` + Link string `json:"link"` + Name origins.Origin `json:"name,omitempty"` } type SurugayaDTO struct { @@ -37,9 +38,9 @@ type AmiamiDTO struct { } type SingleMerchResponse struct { - MerchUuid string `json:"merch_uuid"` - Name string `json:"name"` - Origins []any `json:"origins"` + MerchUuid string `json:"merch_uuid"` + Name string `json:"name"` + Origins []OriginDTO `json:"origins"` } type ListResponse struct { diff --git a/internal/api/merch/model.go b/internal/api/merch/model.go index b6dc584..702edf0 100644 --- a/internal/api/merch/model.go +++ b/internal/api/merch/model.go @@ -27,7 +27,7 @@ type Price struct { DeletedAt sql.NullTime `json:"deleted_at,omitempty" gorm:"column:deleted_at"` MerchUuid string `json:"merch_uuid" gorm:"column:merch_uuid"` Price int `json:"price" gorm:"column:price"` - Origin origins.Origin `json:"origin" gorm:"column:origins;type:integer"` + Origin origins.Origin `json:"origin" gorm:"column:origin;type:integer"` } type Label struct { diff --git a/internal/api/merch/repository.go b/internal/api/merch/repository.go index bff25b4..7283fa9 100644 --- a/internal/api/merch/repository.go +++ b/internal/api/merch/repository.go @@ -230,7 +230,7 @@ func (r *Repo) getAllUserMerch(userUuid string) (merchList []Merch, err error) { func (r *Repo) getPricesWithDays(userUuid string, period time.Time) (prices []Price, err error) { err = r.db.Raw(` - SELECT p.created_at, p.merch_uuid, p.price, p.origins + SELECT p.created_at, p.merch_uuid, p.price, p.origin FROM prices AS p JOIN merch AS m ON m.merch_uuid = p.merch_uuid WHERE m.user_uuid = ? @@ -248,9 +248,9 @@ func (r *Repo) getPricesWithDays(userUuid string, period time.Time) (prices []Pr func (r *Repo) getDistinctPrices(userUuid, merchUuid string, period time.Time) (prices []Price, err error) { err = r.db.Raw(` - SELECT price, created_at, origins + SELECT price, created_at, origin FROM ( - SELECT DISTINCT ON (price) price, created_at, origins + SELECT DISTINCT ON (price) price, created_at, origin FROM prices WHERE merch_uuid = ? AND created_at > ? diff --git a/internal/api/merch/service.go b/internal/api/merch/service.go index 11b303e..e5ef790 100644 --- a/internal/api/merch/service.go +++ b/internal/api/merch/service.go @@ -75,30 +75,33 @@ func (s *service) addMerch(payload MerchDTO, userUuid string) error { return s.repo.addMerch(bundle) } -func (s *service) getSingleMerch(userUuid, merchUuid string) (MerchDTO, error) { +func (s *service) getSingleMerch(userUuid, merchUuid string) (SingleMerchResponse, error) { bundle, err := s.repo.getSingleMerch(userUuid, merchUuid) if err != nil { - return MerchDTO{}, err + return SingleMerchResponse{}, err } - return MerchDTO{ + var originsSlice []OriginDTO + + originsSlice = append(originsSlice, OriginDTO{ + Link: bundle.Surugaya.Link, + Name: origins.Surugaya, + }) + + originsSlice = append(originsSlice, OriginDTO{ + Link: bundle.Mandarake.Link, + Name: origins.Mandarake, + }) + + originsSlice = append(originsSlice, OriginDTO{ + Link: bundle.Amiami.Link, + Name: origins.Amiami, + }) + + return SingleMerchResponse{ MerchUuid: bundle.Merch.MerchUuid, Name: bundle.Merch.Name, - OriginSurugaya: SurugayaDTO{ - OriginDTO{ - Link: bundle.Surugaya.Link, - }, - }, - OriginMandarake: MandarakeDTO{ - OriginDTO{ - Link: bundle.Mandarake.Link, - }, - }, - OriginAmiami: AmiamiDTO{ - OriginDTO{ - Link: bundle.Amiami.Link, - }, - }, + Origins: originsSlice, }, nil }