package merch import ( "database/sql" "errors" "gorm.io/gorm" "gorm.io/gorm/clause" "time" ) type Repo struct { db *gorm.DB } func NewRepo(db *gorm.DB) *Repo { return &Repo{ db: db, } } type repository interface { addMerch(bundle merchBundle) error merchRecordExists(userUuid, merchUuid string) (bool, error) getSingleMerch(userUuid, merchUuid string) (merchBundle, error) getAllMerch(userUuid string) ([]ListResponse, error) updateMerch(payload UpdateMerchDTO, userUuid string) error deleteMerch(userUuid, merchUuid string) error getAllUserMerch(userUuid string) ([]Merch, error) prices } type prices interface { getPricesWithDays(userUuid string, period time.Time) ([]Price, error) getDistinctPrices(userUuid, merchUuid string, period time.Time) (prices []Price, err error) } func (r *Repo) addMerch(bundle merchBundle) error { if err := r.db.Model(&Merch{}).Create(bundle.Merch).Error; err != nil { return err } if err := r.db.Model(&Surugaya{}).Create(bundle.Surugaya).Error; err != nil { return err } if err := r.db.Model(&Mandarake{}).Create(bundle.Mandarake).Error; err != nil { return err } return nil } func (r *Repo) merchRecordExists(userUuid, merchUuid string) (bool, error) { var exists bool err := r.db.Raw(` SELECT EXISTS ( SELECT 1 FROM merch WHERE user_uuid = ? AND merch_uuid = ? );`, userUuid, merchUuid).Scan(&exists).Error return exists, err } func (r *Repo) getSingleMerch(userUuid, merchUuid string) (merchBundle, error) { var merch Merch if err := r.db. Where("user_uuid = ?", userUuid). Where("merch_uuid = ?", merchUuid). Where("deleted_at IS NULL"). First(&merch).Error; err != nil { return merchBundle{}, err } var surugaya Surugaya if err := r.db. Where("merch_uuid = ?", merchUuid). First(&surugaya).Error; err != nil { if !errors.Is(err, gorm.ErrRecordNotFound) { return merchBundle{}, err } } var mandarake Mandarake if err := r.db. Where("merch_uuid = ?", merchUuid). First(&mandarake).Error; err != nil { if !errors.Is(err, gorm.ErrRecordNotFound) { return merchBundle{}, err } } return merchBundle{ Merch: &merch, Surugaya: &surugaya, Mandarake: &mandarake, }, nil } func (r *Repo) getAllMerch(userUuid string) ([]ListResponse, error) { var list []ListResponse if err := r.db.Model(&Merch{}). Where("user_uuid = ?", userUuid). Where("deleted_at IS NULL"). Find(&list).Error; err != nil { return nil, err } return list, nil } func (r *Repo) updateMerch(payload UpdateMerchDTO, userUuid string) error { m := make(map[string]any) m["name"] = payload.Name m["updated_at"] = sql.NullTime{ Time: time.Now(), Valid: true, } if err := r.db. Model(&Merch{}). Where("user_uuid = ?", userUuid). Where("merch_uuid = ?", payload.MerchUuid). Updates(m).Error; err != nil { 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 { return err } } return nil } func (r *Repo) deleteMerch(userUuid, merchUuid string) error { now := time.Now().UTC() if err := r.db. Model(&Merch{}). Where("user_uuid = ?", userUuid). Where("merch_uuid = ?", merchUuid). Update("deleted_at", now).Error; err != nil { return err } if err := r.db. Model(&Surugaya{}). Where("merch_uuid = ?", merchUuid). Update("deleted_at", now).Error; err != nil { return err } if err := r.db. Model(&Mandarake{}). Where("merch_uuid = ?", merchUuid). Update("deleted_at", now).Error; err != nil { return err } return nil } func (r *Repo) getAllUserMerch(userUuid string) (merchList []Merch, err error) { err = r.db.Model(&Merch{}). Where("user_uuid = ?", userUuid). Where("deleted_at IS NULL"). Find(&merchList).Error if err != nil { return nil, err } return merchList, nil } 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 FROM prices AS p JOIN merch AS m ON m.merch_uuid = p.merch_uuid WHERE m.user_uuid = ? AND p.created_at > ? AND p.deleted_at IS NULL AND m.deleted_at IS NULL ORDER BY p.created_at DESC `, userUuid, period).Scan(&prices).Error if err != nil { return nil, err } return prices, nil } func (r *Repo) getDistinctPrices(userUuid, merchUuid string, period time.Time) (prices []Price, err error) { err = r.db.Raw(` SELECT price, created_at, origin FROM ( SELECT DISTINCT ON (price) price, created_at, origin FROM prices WHERE merch_uuid = ? AND created_at > ? AND deleted_at IS NULL AND EXISTS ( SELECT 1 FROM merch WHERE merch_uuid = ? AND user_uuid = ? ) ) AS up ORDER BY created_at;`, merchUuid, period, merchUuid, userUuid).Scan(&prices).Error if err != nil { return nil, err } return prices, nil } func (r *Repo) upsertOrigin(model any) error { return r.db.Clauses(clause.OnConflict{ Columns: []clause.Column{{Name: "merch_uuid"}}, DoUpdates: clause.AssignmentColumns([]string{"link"}), }).Create(model).Error }