Documentation
¶
Index ¶
- Variables
- type BrokerWithStatus
- type Job
- func (j *Job) Cancel()
- func (j *Job) Complete()
- func (j *Job) Context() context.Context
- func (j *Job) GetStatus() JobStatus
- func (j *Job) IsCancelled() bool
- func (j *Job) MarshalJSON() ([]byte, error)
- func (j *Job) Pause(daySent int, errorMsg string)
- func (j *Job) RecordAuthFailure() bool
- func (j *Job) ResetAuthFailures()
- func (j *Job) SetDailyLimit(limit int)
- func (j *Job) StopWithError(errorType, errorMsg string)
- func (j *Job) Update(sent, failed int, currentBroker, currentBrokerID string)
- type JobManager
- func (jm *JobManager) AnyActive() bool
- func (jm *JobManager) Cleanup(maxAge time.Duration)
- func (jm *JobManager) Create(total int, profileID string) *Job
- func (jm *JobManager) CreateIfNoActive(total int, profileID string) (*Job, bool)
- func (jm *JobManager) Get(id string) *Job
- func (jm *JobManager) GetActive(profileID string) *Job
- type JobStatus
- type PipelineStats
- type RateLimiter
- type Server
- type Session
- type SessionStore
- type Stats
Constants ¶
This section is empty.
Variables ¶
var OpenBrowser = func(url string) { if cmd, args := browserCommand(runtime.GOOS, url); cmd != "" { _ = exec.Command(cmd, args...).Start() } }
OpenBrowser opens url in the default browser; tests replace it.
var Version = "dev"
Version is the build version shown in the web UI footer. main sets it from its own -ldflags-injected version at startup; it stays "dev" otherwise.
Functions ¶
This section is empty.
Types ¶
type BrokerWithStatus ¶
type BrokerWithStatus struct {
broker.Broker
Status string // "never", "sent", "failed"
LastSent string // formatted date or empty
TotalSent int
Excluded bool // true if excluded via config.Options.ExcludedBrokers/ExcludedCategories
ManualMode bool // config.Options.send_mode == "manual" - row shows "Email" + "Mark sent" instead of "Send"
// contains filtered or unexported fields
}
BrokerWithStatus combines broker info with history status
type Job ¶
type Job struct {
ID string `json:"id"`
ProfileID string `json:"profile_id"`
Status JobStatus `json:"status"`
Progress int `json:"progress"`
Sent int `json:"sent"`
Failed int `json:"failed"`
Total int `json:"total"`
CurrentBroker string `json:"current_broker"`
CurrentBrokerID string `json:"current_broker_id"`
StartedAt time.Time `json:"started_at"`
CompletedAt time.Time `json:"completed_at,omitempty"`
Error string `json:"error,omitempty"`
ErrorType string `json:"error_type,omitempty"` // "auth", "rate_limit", etc.
DailyLimit int `json:"daily_limit,omitempty"` // Max emails per day
DaySent int `json:"day_sent,omitempty"` // Emails sent today
// contains filtered or unexported fields
}
Job represents a background email sending job
func (*Job) GetStatus ¶
GetStatus returns the job's current status. Status is mutated under j.mu by Update/Complete/Cancel/StopWithError, so reading the field directly (as GetActive and Cleanup used to) is a data race - go through this.
func (*Job) IsCancelled ¶
IsCancelled returns true if the job was cancelled
func (*Job) MarshalJSON ¶ added in v0.3.2
MarshalJSON snapshots the job under the lock, so a status poll can't read a half-updated Job while processSendJob is mid-Update. The exported fields' json tags do the rest.
func (*Job) Pause ¶
Pause marks the job paused (daily send limit reached) with the given day-sent count and message, all under one lock - processSendJob used to set these three fields directly, racing with any concurrent read (e.g. a status poll marshalling the job).
func (*Job) RecordAuthFailure ¶
RecordAuthFailure records an auth failure and returns true if job should stop
func (*Job) ResetAuthFailures ¶
func (j *Job) ResetAuthFailures()
ResetAuthFailures resets the consecutive auth failure counter
func (*Job) SetDailyLimit ¶
SetDailyLimit sets the job's daily send limit under lock.
func (*Job) StopWithError ¶
StopWithError stops the job due to an error
type JobManager ¶
type JobManager struct {
// contains filtered or unexported fields
}
JobManager manages background jobs
func NewJobManager ¶
func NewJobManager() *JobManager
NewJobManager creates a new job manager and starts its background cleanup loop, which evicts completed jobs older than jobRetention.
func (*JobManager) AnyActive ¶ added in v0.9.0
func (jm *JobManager) AnyActive() bool
AnyActive reports whether any profile has a send job running.
func (*JobManager) Cleanup ¶
func (jm *JobManager) Cleanup(maxAge time.Duration)
Cleanup removes completed jobs older than the specified duration
func (*JobManager) Create ¶
func (jm *JobManager) Create(total int, profileID string) *Job
Create creates a new job with the given total count, scoped to profileID. Does not check for an already-active job for that profile - callers that need "only one job per profile" must use CreateIfNoActive instead, or they reopen the TOCTOU window CreateIfNoActive exists to close.
func (*JobManager) CreateIfNoActive ¶ added in v0.7.0
func (jm *JobManager) CreateIfNoActive(total int, profileID string) (*Job, bool)
CreateIfNoActive creates a job for profileID only if none is running, under one lock, so two concurrent requests can't double-send. Returns (existing, false) or (new, true).
func (*JobManager) Get ¶
func (jm *JobManager) Get(id string) *Job
Get returns a job by ID, or nil if not found
func (*JobManager) GetActive ¶
func (jm *JobManager) GetActive(profileID string) *Job
GetActive returns the currently running job for the given profile, or nil if none. Scoped per-profile so switching profiles in the web UI doesn't report a false "job already running" - two profiles can send concurrently, each against its own daily limit and history.
type PipelineStats ¶
type PipelineStats struct {
EmailSent int
AwaitingResponse int
FormRequired int
FormFilled int
AwaitingCaptcha int
CaptchaSolved int
AwaitingConfirmation int
Confirmed int
Rejected int
Failed int
PendingTasks int
NeedsReview int
}
PipelineStats holds stats for the pipeline dashboard
type RateLimiter ¶
type RateLimiter struct {
// contains filtered or unexported fields
}
RateLimiter caps requests per key in a sliding window. Keys are a fixed handful of endpoint names, so the map never needs pruning.
func NewRateLimiter ¶
func NewRateLimiter(limit int, window time.Duration) *RateLimiter
func (*RateLimiter) Allow ¶
func (rl *RateLimiter) Allow(key string) bool
type Server ¶
type Server struct {
// BrokerOverride is serve's --brokers flag, so a reload resolves the list
// the same way startup did.
BrokerOverride string
// contains filtered or unexported fields
}
type Session ¶
type Session struct {
ID string
Step string
Profile config.Profile
Email config.Email
ManualSend bool // user chose to send removal emails by hand - no SMTP
CreatedAt time.Time
ExpiresAt time.Time
}
Session holds wizard state securely on the server
type SessionStore ¶
type SessionStore struct {
// contains filtered or unexported fields
}
SessionStore manages secure server-side sessions Credentials are never sent to the client - only an opaque session ID
func NewSessionStore ¶
func NewSessionStore(ttl time.Duration) *SessionStore
NewSessionStore creates a new session store with automatic cleanup
func (*SessionStore) Create ¶
func (s *SessionStore) Create() (string, error)
Create creates a new session and returns its ID
func (*SessionStore) Get ¶
func (s *SessionStore) Get(id string) *Session
Get retrieves a session by ID, returns nil if not found or expired