package store import ( "context" "database/sql" "encoding/json" "errors" ) const publishStatusPublished = 3 type Banner struct { ID int64 `db:"id"` ImageURL string `db:"image_url"` AltText string `db:"alt_text"` TargetType int `db:"target_type"` TargetValue string `db:"target_value"` Sort int `db:"sort"` } type Content struct { ContentKey string `db:"content_key"` ContentType string `db:"content_type"` Title string `db:"title"` Summary string `db:"summary"` BodyHTML string `db:"body_html"` BodyText string `db:"body_text"` CoverURL string `db:"cover_url"` Version int `db:"version"` PublishedAt string `db:"published_at"` AssetIDs sql.NullString `db:"asset_ids"` } type MemberLevel struct { ID int64 `db:"id"` Code string `db:"level_code"` Name string `db:"name"` IconURL string `db:"icon_url"` UpgradeAmount int64 `db:"upgrade_amount"` GiftAmount int64 `db:"gift_amount"` DailyOrderLimit int `db:"daily_order_limit"` CommissionRate int `db:"commission_rate"` DailyWithdrawLimit int `db:"daily_withdraw_limit"` RuleJSON json.RawMessage `db:"rule_json"` Version int `db:"version"` Sort int `db:"sort"` } type MemberNotice struct { ID int64 `db:"id"` Title string `db:"title"` Content string `db:"content"` SourceType int `db:"source_type"` StartAt string `db:"start_at"` EndAt string `db:"end_at"` CreatedAt string `db:"created_at"` } type MemberNoticePage struct { Items []MemberNotice Total int64 } type TaskSummary struct { TodayCount int `db:"today_count" json:"today_count"` TodayCommissionAmount int64 `db:"today_commission_amount" json:"today_commission_amount"` YesterdayCommissionAmount int64 `db:"yesterday_commission_amount" json:"yesterday_commission_amount"` } type LeaderboardMember struct { MemberID int64 `db:"member_id"` Username string `db:"username"` AvatarURL string `db:"avatar_url"` CompletedCount int64 `db:"completed_count"` } type CustomerService struct { ID int64 `db:"id"` ServiceCode string `db:"service_code"` Name string `db:"name"` AvatarURL string `db:"avatar_url"` WorkTime string `db:"work_time"` OnlineStatus int `db:"online_status"` ServiceURL string `db:"service_url"` BackupContact string `db:"backup_contact"` } func (s *Store) CurrentSiteConfig(ctx context.Context, tenantID int64) (json.RawMessage, error) { if s == nil || s.db == nil { return nil, errors.New("store is unavailable") } var raw []byte err := s.db.GetContext(ctx, &raw, `SELECT config_json FROM hg_site_config_version WHERE tenant_id=? AND publish_status=? AND (effective_at IS NULL OR effective_at<=UTC_TIMESTAMP(3)) ORDER BY COALESCE(effective_at,published_at,created_at) DESC,id DESC LIMIT 1`, tenantID, publishStatusPublished) if errors.Is(err, sql.ErrNoRows) { return json.RawMessage(`{}`), nil } return json.RawMessage(raw), err } func (s *Store) ActiveBanners(ctx context.Context, tenantID int64, position string) ([]Banner, error) { if s == nil || s.db == nil { return nil, errors.New("store is unavailable") } rows := make([]Banner, 0) err := s.db.SelectContext(ctx, &rows, `SELECT id,image_url,alt_text,target_type,COALESCE(target_value,'') AS target_value,sort FROM hg_banner WHERE tenant_id=? AND position=? AND publish_status=? AND (start_at IS NULL OR start_at<=UTC_TIMESTAMP(3)) AND (end_at IS NULL OR end_at>UTC_TIMESTAMP(3)) AND deleted_at IS NULL ORDER BY sort ASC,id ASC`, tenantID, position, publishStatusPublished) return rows, err } func (s *Store) PublishedContent(ctx context.Context, tenantID int64, key string) (*Content, error) { if s == nil || s.db == nil { return nil, errors.New("store is unavailable") } var row Content err := s.db.GetContext(ctx, &row, `SELECT c.content_key,c.content_type,v.title,COALESCE(v.summary,'') AS summary, v.body_html,COALESCE(v.body_text,'') AS body_text,COALESCE(v.cover_url,'') AS cover_url, v.version_no AS version,COALESCE(DATE_FORMAT(c.published_at,'%Y-%m-%dT%H:%i:%sZ'),'') AS published_at,v.asset_ids FROM hg_content c JOIN hg_content_version v ON v.tenant_id=c.tenant_id AND v.id=c.published_version_id WHERE c.tenant_id=? AND c.content_key=? AND c.publish_status=? AND v.publish_status=? AND (v.effective_at IS NULL OR v.effective_at<=UTC_TIMESTAMP(3)) AND c.deleted_at IS NULL LIMIT 1`, tenantID, key, publishStatusPublished, publishStatusPublished) if errors.Is(err, sql.ErrNoRows) { return nil, ErrResourceNotFound } return &row, err } func (s *Store) ActiveMemberLevels(ctx context.Context, tenantID int64) ([]MemberLevel, error) { if s == nil || s.db == nil { return nil, errors.New("store is unavailable") } rows := make([]MemberLevel, 0) err := s.db.SelectContext(ctx, &rows, `SELECT l.id,l.level_code,l.name,l.icon_url,l.sort, v.upgrade_amount,v.gift_amount,v.daily_order_limit,v.commission_rate,v.daily_withdraw_limit, COALESCE(v.rule_json,JSON_OBJECT()) AS rule_json,v.version_no AS version FROM hg_app_member_level l JOIN hg_app_member_level_version v ON v.tenant_id=l.tenant_id AND v.id=l.current_version_id WHERE l.tenant_id=? AND l.status=1 AND v.publish_status=? AND (v.effective_at IS NULL OR v.effective_at<=UTC_TIMESTAMP(3)) AND l.deleted_at IS NULL ORDER BY l.sort ASC,l.id ASC`, tenantID, publishStatusPublished) return rows, err } func (s *Store) ActiveMemberNotices(ctx context.Context, tenantID, memberID int64, limit int) ([]MemberNotice, error) { if s == nil || s.db == nil { return nil, errors.New("store is unavailable") } rows := make([]MemberNotice, 0) err := s.db.SelectContext(ctx, &rows, `SELECT id,title,content,source_type, DATE_FORMAT(start_at,'%Y-%m-%dT%H:%i:%sZ') AS start_at, DATE_FORMAT(end_at,'%Y-%m-%dT%H:%i:%sZ') AS end_at, DATE_FORMAT(created_at,'%Y-%m-%dT%H:%i:%sZ') AS created_at FROM hg_app_member_notice WHERE tenant_id=? AND member_id IN (0,?) AND notice_status=1 AND start_at<=UTC_TIMESTAMP(3) AND end_at>UTC_TIMESTAMP(3) ORDER BY start_at DESC,id DESC LIMIT ?`, tenantID, memberID, limit) return rows, err } func (s *Store) LatestPopupNotice(ctx context.Context, tenantID, memberID int64) (*MemberNotice, error) { if s == nil || s.db == nil { return nil, errors.New("store is unavailable") } var row MemberNotice err := s.db.GetContext(ctx, &row, `SELECT id,title,content,source_type, DATE_FORMAT(start_at,'%Y-%m-%dT%H:%i:%sZ') AS start_at, DATE_FORMAT(end_at,'%Y-%m-%dT%H:%i:%sZ') AS end_at, DATE_FORMAT(created_at,'%Y-%m-%dT%H:%i:%sZ') AS created_at FROM hg_app_member_notice WHERE tenant_id=? AND member_id IN (0,?) AND notice_status=1 AND start_at<=UTC_TIMESTAMP(3) AND end_at>UTC_TIMESTAMP(3) ORDER BY start_at DESC,id DESC LIMIT 1`, tenantID, memberID) if errors.Is(err, sql.ErrNoRows) { return nil, nil } return &row, err } func (s *Store) MarkMemberNoticeRead(ctx context.Context, tenantID, memberID, noticeID int64) error { if s == nil || s.db == nil { return errors.New("store is unavailable") } result, err := s.db.ExecContext(ctx, `INSERT INTO hg_app_member_notice_read (tenant_id,notice_id,member_id,read_at,created_at) SELECT n.tenant_id,n.id,?,UTC_TIMESTAMP(3),UTC_TIMESTAMP(3) FROM hg_app_member_notice n WHERE n.tenant_id=? AND n.id=? AND n.member_id IN (0,?) AND n.notice_status=1 AND n.start_at<=UTC_TIMESTAMP(3) AND n.end_at>UTC_TIMESTAMP(3) ON DUPLICATE KEY UPDATE read_at=VALUES(read_at)`, memberID, tenantID, noticeID, memberID) if err != nil { return err } affected, err := result.RowsAffected() if err != nil { return err } if affected == 0 { return ErrResourceNotFound } return nil } func (s *Store) MemberNotices(ctx context.Context, tenantID, memberID int64, offset, limit int) (*MemberNoticePage, error) { if s == nil || s.db == nil { return nil, errors.New("store is unavailable") } const where = ` WHERE tenant_id=? AND member_id IN (0,?) AND notice_status=1 AND start_at<=UTC_TIMESTAMP(3) AND end_at>UTC_TIMESTAMP(3)` args := []any{tenantID, memberID} var total int64 if err := s.db.GetContext(ctx, &total, `SELECT COUNT(*) FROM hg_app_member_notice`+where, args...); err != nil { return nil, err } items := make([]MemberNotice, 0) args = append(args, limit, offset) err := s.db.SelectContext(ctx, &items, `SELECT id,title,content,source_type, DATE_FORMAT(start_at,'%Y-%m-%dT%H:%i:%sZ') AS start_at, DATE_FORMAT(end_at,'%Y-%m-%dT%H:%i:%sZ') AS end_at, DATE_FORMAT(created_at,'%Y-%m-%dT%H:%i:%sZ') AS created_at FROM hg_app_member_notice`+where+` ORDER BY created_at DESC,id DESC LIMIT ? OFFSET ?`, args...) return &MemberNoticePage{Items: items, Total: total}, err } func (s *Store) MemberTaskSummary(ctx context.Context, tenantID, memberID int64) (*TaskSummary, error) { if s == nil || s.db == nil { return nil, errors.New("store is unavailable") } var row TaskSummary err := s.db.GetContext(ctx, &row, `SELECT (SELECT COUNT(*) FROM hg_trade_order o WHERE o.tenant_id=? AND o.member_id=? AND o.created_at>=UTC_DATE() AND o.created_at=UTC_DATE() AND t.settled_at=UTC_DATE()-INTERVAL 1 DAY AND y.settled_at