90 lines
3.0 KiB
Go
90 lines
3.0 KiB
Go
package domain
|
||
|
||
import (
|
||
"fmt"
|
||
"strings"
|
||
)
|
||
|
||
// Sweep fetch path — reuses the dual-path split (api | crawler); no third path.
|
||
const (
|
||
SweepPathAPI = "api"
|
||
SweepPathCrawler = "crawler"
|
||
)
|
||
|
||
/*
|
||
RadarSweep 是一次每日巡(或手動觸發)的執行紀錄。
|
||
|
||
計數以累加更新為主:Job 中途失敗時已寫入的進度保留,供續跑判斷
|
||
(judged_external_ids 標記已判過的貼文,避免重跑)。
|
||
*/
|
||
type RadarSweep struct {
|
||
ID string `bson:"_id" json:"id"`
|
||
OwnerUID int64 `bson:"owner_uid" json:"owner_uid"`
|
||
WatchID string `bson:"watch_id" json:"watch_id"`
|
||
JobID string `bson:"job_id,omitempty" json:"job_id,omitempty"`
|
||
Path string `bson:"path" json:"path"`
|
||
HitCount int `bson:"hit_count" json:"hit_count"`
|
||
JudgedCount int `bson:"judged_count" json:"judged_count"`
|
||
CreatedCount int `bson:"created_count" json:"created_count"`
|
||
TruncatedCount int `bson:"truncated_count" json:"truncated_count"`
|
||
FailedReason string `bson:"failed_reason,omitempty" json:"failed_reason,omitempty"`
|
||
CreditsUsed int `bson:"credits_used" json:"credits_used"`
|
||
JudgedExternalIDs []string `bson:"judged_external_ids,omitempty" json:"judged_external_ids,omitempty"`
|
||
StartedAt int64 `bson:"started_at" json:"started_at"`
|
||
EndedAt int64 `bson:"ended_at,omitempty" json:"ended_at,omitempty"`
|
||
}
|
||
|
||
// SweepDelta is an incremental progress patch applied with $inc / $addToSet.
|
||
type SweepDelta struct {
|
||
HitCount int
|
||
JudgedCount int
|
||
CreatedCount int
|
||
TruncatedCount int
|
||
CreditsUsed int
|
||
JudgedExternalIDs []string
|
||
FailedReason *string // nil = leave unchanged; non-nil (incl. empty) = set
|
||
EndedAt int64 // 0 = leave unchanged
|
||
}
|
||
|
||
// SweepListFilter pages sweeps for an owner, optionally scoped to one watch.
|
||
type SweepListFilter struct {
|
||
WatchID string
|
||
Page int
|
||
PageSize int
|
||
}
|
||
|
||
func IsSweepPath(s string) bool {
|
||
switch s {
|
||
case SweepPathAPI, SweepPathCrawler:
|
||
return true
|
||
}
|
||
return false
|
||
}
|
||
|
||
func (s *RadarSweep) Normalize() error {
|
||
if s.OwnerUID <= 0 {
|
||
return fmt.Errorf("%w: owner_uid required", ErrValidation)
|
||
}
|
||
if strings.TrimSpace(s.WatchID) == "" {
|
||
return fmt.Errorf("%w: watch_id required", ErrValidation)
|
||
}
|
||
if s.Path == "" {
|
||
s.Path = SweepPathAPI
|
||
}
|
||
if !IsSweepPath(s.Path) {
|
||
return fmt.Errorf("%w: unknown sweep path %q", ErrValidation, s.Path)
|
||
}
|
||
// failed_reason must never hold tokens; strip obvious bearer-like blobs is out of scope —
|
||
// callers are responsible. We only reject empty path/owner.
|
||
if s.FailedReason != "" {
|
||
s.FailedReason = strings.TrimSpace(s.FailedReason)
|
||
}
|
||
s.JudgedExternalIDs = dedupeStrings(s.JudgedExternalIDs)
|
||
return nil
|
||
}
|
||
|
||
// MergeJudgedExternalIDs appends new IDs without duplicates (order preserved).
|
||
func MergeJudgedExternalIDs(existing, add []string) []string {
|
||
return dedupeStrings(append(append([]string{}, existing...), add...))
|
||
}
|