web

package
v1.2.7 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 4, 2026 License: MIT Imports: 32 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

View Source
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.

View Source
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) Cancel

func (j *Job) Cancel()

Cancel cancels the job

func (*Job) Complete

func (j *Job) Complete()

Complete marks the job as completed

func (*Job) Context

func (j *Job) Context() context.Context

Context returns the job's context

func (*Job) GetStatus

func (j *Job) GetStatus() JobStatus

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

func (j *Job) IsCancelled() bool

IsCancelled returns true if the job was cancelled

func (*Job) MarshalJSON added in v0.3.2

func (j *Job) MarshalJSON() ([]byte, error)

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

func (j *Job) Pause(daySent int, errorMsg string)

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

func (j *Job) RecordAuthFailure() bool

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

func (j *Job) SetDailyLimit(limit int)

SetDailyLimit sets the job's daily send limit under lock.

func (*Job) StopWithError

func (j *Job) StopWithError(errorType, errorMsg string)

StopWithError stops the job due to an error

func (*Job) Update

func (j *Job) Update(sent, failed int, currentBroker, currentBrokerID string)

Update updates the job progress. currentBrokerID is used by the brokers page to refresh only the one row that just changed (see internal/web/templates/brokers.html's refreshBrokerRow) instead of re-fetching the entire broker table on every poll tick.

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 JobStatus

type JobStatus string

JobStatus represents the status of a background job

const (
	JobStatusRunning   JobStatus = "running"
	JobStatusCompleted JobStatus = "completed"
	JobStatusCancelled JobStatus = "cancelled"
	JobStatusPaused    JobStatus = "paused" // Paused due to daily limit
	JobStatusError     JobStatus = "error"  // Stopped due to auth/config error
)

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
}

func NewServer

func NewServer(port int, cfg *config.Config, configPath string, brokerDB *broker.BrokerDatabase, historyStore *history.Store, tmplEngine *emaTemplate.Engine) (*Server, error)

func (*Server) Shutdown

func (s *Server) Shutdown(ctx context.Context) error

Shutdown stops the server; called before Start, Start returns at once.

func (*Server) Start

func (s *Server) Start() error

Start starts the web server and opens the browser

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) Delete

func (s *SessionStore) Delete(id string)

Delete removes a session

func (*SessionStore) Get

func (s *SessionStore) Get(id string) *Session

Get retrieves a session by ID, returns nil if not found or expired

func (*SessionStore) Update

func (s *SessionStore) Update(id string, updateFn func(*Session)) bool

Update updates a session's data and extends its expiry

type Stats

type Stats struct {
	TotalBrokers int
	Sent         int
	Failed       int
	Pending      int
}

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL