From e0e5761cd64f489381c3d659e6fcb589ee145261 Mon Sep 17 00:00:00 2001 From: nquidox Date: Mon, 15 Dec 2025 17:34:01 +0300 Subject: [PATCH] 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,