| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186 |
- package handler
- import (
- "net/http"
- "strconv"
- "spider/internal/model"
- "github.com/gin-gonic/gin"
- "gorm.io/gorm"
- )
- // MerchantHandler handles merchant queries.
- type MerchantHandler struct {
- db *gorm.DB
- }
- // Stats returns aggregate statistics for merchants.
- // GET /merchants/stats
- func (h *MerchantHandler) Stats(c *gin.Context) {
- type countRow struct {
- Key string `json:"key"`
- Count int64 `json:"count"`
- }
- var rawTotal int64
- h.db.Model(&model.MerchantRaw{}).Count(&rawTotal)
- var cleanTotal int64
- h.db.Model(&model.MerchantClean{}).Count(&cleanTotal)
- // Count by status in clean table.
- statusCounts := map[string]int64{}
- var statusRows []struct {
- Status string
- Cnt int64
- }
- h.db.Model(&model.MerchantClean{}).
- Select("status, count(*) as cnt").
- Group("status").
- Scan(&statusRows)
- for _, r := range statusRows {
- statusCounts[r.Status] = r.Cnt
- }
- // Count by source_type in raw table.
- var sourceRows []struct {
- SourceType string
- Cnt int64
- }
- h.db.Model(&model.MerchantRaw{}).
- Select("source_type, count(*) as cnt").
- Group("source_type").
- Scan(&sourceRows)
- bySource := map[string]int64{}
- for _, r := range sourceRows {
- bySource[r.SourceType] = r.Cnt
- }
- // Count by industry in clean table.
- var industryRows []struct {
- Industry string
- Cnt int64
- }
- h.db.Model(&model.MerchantClean{}).
- Select("industry, count(*) as cnt").
- Group("industry").
- Scan(&industryRows)
- byIndustry := map[string]int64{}
- for _, r := range industryRows {
- byIndustry[r.Industry] = r.Cnt
- }
- OK(c, gin.H{
- "raw_total": rawTotal,
- "clean_total": cleanTotal,
- "valid": statusCounts["valid"],
- "invalid": statusCounts["invalid"],
- "bot": statusCounts["bot"],
- "duplicate": statusCounts["duplicate"],
- "group": statusCounts["group"],
- "by_source": bySource,
- "by_industry": byIndustry,
- })
- }
- // ListRaw returns raw merchants with filters and pagination.
- // GET /merchants/raw?status=&source_type=&page=&page_size=
- func (h *MerchantHandler) ListRaw(c *gin.Context) {
- page, pageSize, offset := parsePage(c)
- query := h.db.Model(&model.MerchantRaw{})
- if status := c.Query("status"); status != "" {
- query = query.Where("status = ?", status)
- }
- if sourceType := c.Query("source_type"); sourceType != "" {
- query = query.Where("source_type = ?", sourceType)
- }
- var total int64
- if err := query.Count(&total).Error; err != nil {
- Fail(c, 500, err.Error())
- return
- }
- var items []model.MerchantRaw
- if err := query.Order("created_at DESC").Limit(pageSize).Offset(offset).Find(&items).Error; err != nil {
- Fail(c, 500, err.Error())
- return
- }
- PageOK(c, items, total, page, pageSize)
- }
- // ListClean returns clean merchants with filters and pagination.
- // GET /merchants/clean?status=&industry=&min_score=&sort=quality_score&order=desc&page=&page_size=
- func (h *MerchantHandler) ListClean(c *gin.Context) {
- page, pageSize, offset := parsePage(c)
- query := h.db.Model(&model.MerchantClean{})
- if status := c.Query("status"); status != "" {
- query = query.Where("status = ?", status)
- }
- if industry := c.Query("industry"); industry != "" {
- query = query.Where("industry = ?", industry)
- }
- if minScore := c.Query("min_score"); minScore != "" {
- if score, err := strconv.ParseFloat(minScore, 64); err == nil {
- query = query.Where("quality_score >= ?", score)
- }
- }
- sortField := c.DefaultQuery("sort", "quality_score")
- // whitelist sort fields to prevent SQL injection
- allowedSort := map[string]bool{
- "quality_score": true,
- "created_at": true,
- "updated_at": true,
- "member_count": true,
- }
- if !allowedSort[sortField] {
- sortField = "quality_score"
- }
- order := c.DefaultQuery("order", "desc")
- if order != "asc" && order != "desc" {
- order = "desc"
- }
- var total int64
- if err := query.Count(&total).Error; err != nil {
- Fail(c, 500, err.Error())
- return
- }
- var items []model.MerchantClean
- if err := query.Order(sortField + " " + order).Limit(pageSize).Offset(offset).Find(&items).Error; err != nil {
- Fail(c, 500, err.Error())
- return
- }
- PageOK(c, items, total, page, pageSize)
- }
- // GetByID fetches a merchant by ID, checking clean table first then raw.
- // GET /merchants/:id
- func (h *MerchantHandler) GetByID(c *gin.Context) {
- id, err := strconv.ParseUint(c.Param("id"), 10, 64)
- if err != nil {
- Fail(c, http.StatusBadRequest, "invalid id")
- return
- }
- var clean model.MerchantClean
- if err := h.db.First(&clean, id).Error; err == nil {
- OK(c, gin.H{"source": "clean", "data": clean})
- return
- }
- var raw model.MerchantRaw
- if err := h.db.First(&raw, id).Error; err == nil {
- OK(c, gin.H{"source": "raw", "data": raw})
- return
- }
- Fail(c, 404, "merchant not found")
- }
|