package logic import ( "context" "time" modentity "spark-server/internal/module/moderation/entity" "spark-server/internal/pkg/errs" "spark-server/internal/pkg/reqctx" ) // Apply records hits and runs account penalties. Call before rejecting send. func (s *Service) Apply(ctx context.Context, userID, conversationID int64, in *modentity.ApplyInput) error { if in == nil || len(in.Hits) == 0 { return nil } if !s.Ready() { return errs.ErrInner } return s.recordHits(ctx, userID, conversationID, in.Hits) } // Enforce runs mute/disable after hits are recorded. Returns ErrContentBlocked when send must stop. func (s *Service) Enforce(ctx context.Context, userID int64, res *modentity.ScanResult) error { if res == nil { return nil } switch res.Action { case modentity.ActionReject, modentity.ActionHide: return errs.ErrContentBlocked case modentity.ActionMute: if s.users == nil || res.MuteSeconds <= 0 { return errs.ErrContentBlocked } until := time.Now().Add(time.Duration(res.MuteSeconds) * time.Second) if err := s.users.ExtendSendMutedUntil(ctx, userID, until); err != nil { return err } return errs.ErrContentBlocked case modentity.ActionDisable: if s.users == nil { return errs.ErrContentBlocked } if err := s.users.DisableAccount(ctx, userID); err != nil { return err } return errs.ErrContentBlocked default: return nil } } func (s *Service) recordHits(ctx context.Context, userID, conversationID int64, hits []modentity.HitMatch) error { ip := "" if rc := reqctx.From(ctx); rc != nil { ip = rc.RealIP } now := time.Now() rows := make([]HitRecord, 0, len(hits)) for i := range hits { h := hits[i] id, err := s.ids.NextID() if err != nil { return err } rows = append(rows, HitRecord{ ID: id, UserID: userID, ConversationID: conversationID, WordID: h.WordID, Category: h.Category, Action: h.Action.Effective(), Excerpt: h.Excerpt, RealIP: ip, CreatedAt: now, }) } return s.repo.InsertHits(ctx, rows) }