package store import ( "spider/internal/model" "spider/internal/plugin" "strings" "gorm.io/gorm" ) // SaveRaw inserts a merchant into merchants_raw from plugin output. // Dedup: same tg_username + same source_url => skip. // Returns true if a new record was inserted. func (s *Store) SaveRaw(data plugin.MerchantData) (bool, error) { if strings.TrimSpace(data.TgUsername) == "" { return false, nil // no tg_username = don't insert } username := strings.TrimPrefix(strings.TrimSpace(data.TgUsername), "@") tgLink := data.TgLink if tgLink == "" && username != "" { tgLink = "https://t.me/" + username } raw := model.MerchantRaw{ MerchantName: data.MerchantName, TgUsername: username, TgLink: tgLink, Website: data.Website, Email: data.Email, Phone: data.Phone, IndustryTag: data.IndustryTag, SourceType: data.SourceType, SourceName: data.SourceName, SourceURL: data.SourceURL, OriginalText: data.OriginalText, Status: "raw", } // Dedup: same tg_username + source_url => skip var count int64 s.DB.Model(&model.MerchantRaw{}). Where("tg_username = ? AND source_url = ?", username, data.SourceURL). Count(&count) if count > 0 { return false, nil } if err := s.DB.Create(&raw).Error; err != nil { return false, err } return true, nil } // ListRawByStatus returns raw merchants with the given status. func (s *Store) ListRawByStatus(status string, limit int) ([]model.MerchantRaw, error) { var raws []model.MerchantRaw q := s.DB.Where("status = ?", status) if limit > 0 { q = q.Limit(limit) } err := q.Find(&raws).Error return raws, err } // UpdateRawStatus sets the status of a raw merchant. func (s *Store) UpdateRawStatus(id uint, status string) error { return s.DB.Model(&model.MerchantRaw{}).Where("id = ?", id).Update("status", status).Error } // SaveClean upserts a clean merchant by tg_username. func (s *Store) SaveClean(m *model.MerchantClean) error { var existing model.MerchantClean err := s.DB.Where("tg_username = ?", m.TgUsername).First(&existing).Error if err == gorm.ErrRecordNotFound { return s.DB.Create(m).Error } if err != nil { return err } m.ID = existing.ID return s.DB.Save(m).Error } // ListClean returns paginated clean merchants with optional filters. func (s *Store) ListClean(filters map[string]string, page, pageSize int) ([]model.MerchantClean, int64, error) { var merchants []model.MerchantClean var total int64 q := s.DB.Model(&model.MerchantClean{}) if v, ok := filters["status"]; ok && v != "" { q = q.Where("status = ?", v) } if v, ok := filters["level"]; ok && v != "" { q = q.Where("level = ?", v) } if v, ok := filters["industry_tag"]; ok && v != "" { q = q.Where("industry_tag = ?", v) } if v, ok := filters["search"]; ok && v != "" { like := "%" + v + "%" q = q.Where("tg_username LIKE ? OR merchant_name LIKE ?", like, like) } q.Count(&total) offset := (page - 1) * pageSize err := q.Order("created_at DESC").Offset(offset).Limit(pageSize).Find(&merchants).Error return merchants, total, err }