mirror of
https://github.com/warmbly/warmbly.git
synced 2026-08-19 08:01:16 +00:00
d227038ca0
Disable the linters that fire on legacy code without flagging real bugs: `unused` (orphan repos kept for future feature flags), `unconvert` (defensive type conversions), `gosimple` (style suggestions in code we don't want to touch). govet: disable `shadow` (idiomatic `err :=` re-decls in transaction patterns) and `nilness` (legitimate defensive nil checks that look tautological to the analyzer). Ran `gofmt -w internal/ cmd/` — every Go file now passes gofmt -l with no output. Kept: govet, staticcheck, ineffassign, typecheck, bodyclose, noctx, sqlclosecheck, gofmt, goimports, misspell — the real-bug checks.
253 lines
8.5 KiB
Go
253 lines
8.5 KiB
Go
package models
|
|
|
|
import (
|
|
"encoding/json"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
// RealtimeEventType represents the type of realtime event
|
|
type RealtimeEventType string
|
|
|
|
const (
|
|
// Campaign events
|
|
EventCampaignStatus RealtimeEventType = "campaign.status"
|
|
EventCampaignProgress RealtimeEventType = "campaign.progress"
|
|
EventCampaignLog RealtimeEventType = "campaign.log"
|
|
|
|
// Email account events
|
|
EventEmailError RealtimeEventType = "email.error"
|
|
EventEmailStatus RealtimeEventType = "email.status"
|
|
|
|
// Task events
|
|
EventTaskStatus RealtimeEventType = "task.status"
|
|
|
|
// Contact events
|
|
EventContactChanged RealtimeEventType = "contact.changed"
|
|
|
|
// Warmup events
|
|
EventWarmupProgress RealtimeEventType = "warmup.progress"
|
|
EventWarmupBlocked RealtimeEventType = "warmup.blocked"
|
|
|
|
// Worker events
|
|
EventWorkerStatus RealtimeEventType = "worker.status"
|
|
|
|
// Inbox events
|
|
EventInboxNew RealtimeEventType = "inbox.new"
|
|
EventInboxUpdate RealtimeEventType = "inbox.update"
|
|
|
|
// Analytics events
|
|
EventAnalyticsEmailOpen RealtimeEventType = "analytics.email_open"
|
|
EventAnalyticsEmailClick RealtimeEventType = "analytics.email_click"
|
|
EventAnalyticsEmailReply RealtimeEventType = "analytics.email_reply"
|
|
EventAnalyticsBounce RealtimeEventType = "analytics.email_bounce"
|
|
)
|
|
|
|
// RealtimeEventPriority represents the priority of realtime events
|
|
type RealtimeEventPriority string
|
|
|
|
const (
|
|
PriorityCritical RealtimeEventPriority = "critical"
|
|
PriorityNormal RealtimeEventPriority = "normal"
|
|
)
|
|
|
|
// CriticalEvents are events that should be persisted for catch-up
|
|
var CriticalEvents = map[RealtimeEventType]bool{
|
|
EventEmailError: true,
|
|
EventEmailStatus: true,
|
|
EventCampaignStatus: true,
|
|
EventWarmupBlocked: true,
|
|
}
|
|
|
|
// RealtimeMessage is the base message format sent over WebSocket
|
|
type RealtimeMessage struct {
|
|
Type RealtimeEventType `json:"type"`
|
|
Topic string `json:"topic,omitempty"`
|
|
Payload json.RawMessage `json:"payload"`
|
|
Timestamp time.Time `json:"timestamp"`
|
|
}
|
|
|
|
// ClientMessage represents messages from client to server
|
|
type ClientMessage struct {
|
|
Action string `json:"action"` // "subscribe" | "unsubscribe" | "ping"
|
|
Topics []string `json:"topics,omitempty"`
|
|
}
|
|
|
|
// SubscriptionResponse is sent when subscription changes
|
|
type SubscriptionResponse struct {
|
|
Action string `json:"action"` // "subscribed" | "unsubscribed"
|
|
Topics []string `json:"topics"`
|
|
Success bool `json:"success"`
|
|
Error string `json:"error,omitempty"`
|
|
}
|
|
|
|
// ConnectionInfo is sent when a client connects
|
|
type ConnectionInfo struct {
|
|
ConnectionID string `json:"connection_id"`
|
|
Topics []string `json:"available_topics"`
|
|
}
|
|
|
|
// CampaignStatusPayload for campaign status events
|
|
type CampaignStatusPayload struct {
|
|
CampaignID uuid.UUID `json:"campaign_id"`
|
|
Status string `json:"status"`
|
|
PrevStatus string `json:"prev_status,omitempty"`
|
|
Timestamp time.Time `json:"timestamp"`
|
|
}
|
|
|
|
// CampaignProgressPayload for campaign progress events
|
|
type CampaignProgressPayload struct {
|
|
CampaignID uuid.UUID `json:"campaign_id"`
|
|
Sent int `json:"sent"`
|
|
Pending int `json:"pending"`
|
|
Failed int `json:"failed"`
|
|
Delivered int `json:"delivered"`
|
|
Opened int `json:"opened"`
|
|
Clicked int `json:"clicked"`
|
|
Replied int `json:"replied"`
|
|
Bounced int `json:"bounced"`
|
|
Unsubscribed int `json:"unsubscribed"`
|
|
TotalContacts int `json:"total_contacts"`
|
|
CompletionPct float64 `json:"completion_pct"`
|
|
LastActivityAt time.Time `json:"last_activity_at"`
|
|
}
|
|
|
|
// CampaignLogPayload for campaign log events
|
|
type CampaignLogPayload struct {
|
|
CampaignID uuid.UUID `json:"campaign_id"`
|
|
LogType string `json:"log_type"` // "info" | "warning" | "error"
|
|
Message string `json:"message"`
|
|
Metadata any `json:"metadata,omitempty"`
|
|
Timestamp time.Time `json:"timestamp"`
|
|
}
|
|
|
|
// EmailErrorPayload for email account error events
|
|
type EmailErrorPayload struct {
|
|
AccountID uuid.UUID `json:"account_id"`
|
|
ErrorType string `json:"error_type"` // "auth_error" | "rate_limit" | "disabled" | "bounce_rate"
|
|
Message string `json:"message"`
|
|
Recoverable bool `json:"recoverable"`
|
|
Timestamp time.Time `json:"timestamp"`
|
|
}
|
|
|
|
// EmailStatusPayload for email account status events
|
|
type EmailStatusPayload struct {
|
|
AccountID uuid.UUID `json:"account_id"`
|
|
Status string `json:"status"` // "healthy" | "warning" | "error" | "disabled"
|
|
HealthScore int `json:"health_score"`
|
|
Message string `json:"message,omitempty"`
|
|
Timestamp time.Time `json:"timestamp"`
|
|
}
|
|
|
|
// TaskStatusPayload for task lifecycle events
|
|
type TaskStatusPayload struct {
|
|
TaskID uuid.UUID `json:"task_id"`
|
|
CampaignID *uuid.UUID `json:"campaign_id,omitempty"`
|
|
Status string `json:"status"` // "pending" | "processing" | "completed" | "failed"
|
|
Error string `json:"error,omitempty"`
|
|
Timestamp time.Time `json:"timestamp"`
|
|
}
|
|
|
|
// ContactChangedPayload for contact change events
|
|
type ContactChangedPayload struct {
|
|
ContactID uuid.UUID `json:"contact_id"`
|
|
CampaignID uuid.UUID `json:"campaign_id"`
|
|
Action string `json:"action"` // "added" | "updated" | "removed"
|
|
Timestamp time.Time `json:"timestamp"`
|
|
}
|
|
|
|
// WarmupProgressPayload for warmup progress events
|
|
type WarmupProgressPayload struct {
|
|
AccountID uuid.UUID `json:"account_id"`
|
|
DailySent int `json:"daily_sent"`
|
|
DailyTarget int `json:"daily_target"`
|
|
TotalSent int `json:"total_sent"`
|
|
WarmupDays int `json:"warmup_days"`
|
|
HealthScore int `json:"health_score"`
|
|
ReplyRate float64 `json:"reply_rate"`
|
|
Timestamp time.Time `json:"timestamp"`
|
|
}
|
|
|
|
// WarmupBlockedPayload for warmup blocked events
|
|
type WarmupBlockedPayload struct {
|
|
AccountID uuid.UUID `json:"account_id"`
|
|
Reason string `json:"reason"`
|
|
BlockedAt time.Time `json:"blocked_at"`
|
|
}
|
|
|
|
// WorkerStatusPayload for worker status events
|
|
type WorkerStatusPayload struct {
|
|
WorkerID uuid.UUID `json:"worker_id"`
|
|
Status string `json:"status"` // "online" | "offline" | "maintenance"
|
|
AccountCount int `json:"account_count"`
|
|
Timestamp time.Time `json:"timestamp"`
|
|
}
|
|
|
|
// InboxNewPayload for new inbox email events
|
|
type InboxNewPayload struct {
|
|
EmailID uuid.UUID `json:"email_id"`
|
|
AccountID uuid.UUID `json:"account_id"`
|
|
From string `json:"from"`
|
|
Subject string `json:"subject"`
|
|
Snippet string `json:"snippet"`
|
|
ThreadID string `json:"thread_id,omitempty"`
|
|
Timestamp time.Time `json:"timestamp"`
|
|
}
|
|
|
|
// InboxUpdatePayload for inbox update events
|
|
type InboxUpdatePayload struct {
|
|
EmailID uuid.UUID `json:"email_id"`
|
|
AccountID uuid.UUID `json:"account_id"`
|
|
Flags []string `json:"flags"` // "read" | "starred" | "archived"
|
|
Timestamp time.Time `json:"timestamp"`
|
|
}
|
|
|
|
// AnalyticsEventPayload for analytics events (open, click, reply, bounce)
|
|
type AnalyticsEventPayload struct {
|
|
CampaignID uuid.UUID `json:"campaign_id"`
|
|
ContactID uuid.UUID `json:"contact_id"`
|
|
EmailID *uuid.UUID `json:"email_id,omitempty"`
|
|
Link string `json:"link,omitempty"` // For click events
|
|
Reason string `json:"reason,omitempty"` // For bounce events
|
|
Timestamp time.Time `json:"timestamp"`
|
|
}
|
|
|
|
// RealtimeEvent represents a persisted realtime event (for catch-up)
|
|
type RealtimeEvent struct {
|
|
ID uuid.UUID `json:"id"`
|
|
UserID uuid.UUID `json:"user_id"`
|
|
OrgID *uuid.UUID `json:"org_id,omitempty"`
|
|
EventType RealtimeEventType `json:"event_type"`
|
|
Priority RealtimeEventPriority `json:"priority"`
|
|
Payload json.RawMessage `json:"payload"`
|
|
Delivered bool `json:"delivered"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
ExpiresAt time.Time `json:"expires_at"`
|
|
}
|
|
|
|
// TopicFormat defines topic naming patterns
|
|
const (
|
|
TopicUser = "user:%s" // user:{userID}
|
|
TopicOrg = "org:%s" // org:{orgID}
|
|
TopicCampaign = "campaign:%s" // campaign:{campaignID}
|
|
TopicAccount = "account:%s" // account:{accountID}
|
|
TopicInbox = "inbox:%s" // inbox:{accountID}
|
|
)
|
|
|
|
// WebSocketToken represents a token for WebSocket authentication
|
|
type WebSocketToken struct {
|
|
Token string `json:"token"`
|
|
UserID uuid.UUID `json:"user_id"`
|
|
OrgID uuid.UUID `json:"org_id"`
|
|
ExpiresAt time.Time `json:"expires_at"`
|
|
}
|
|
|
|
// RealtimeInfo contains WebSocket connection information
|
|
type RealtimeInfo struct {
|
|
WebSocketURL string `json:"websocket_url"`
|
|
Topics []string `json:"topics"`
|
|
MaxRetries int `json:"max_retries"`
|
|
RetryDelayMs int `json:"retry_delay_ms"`
|
|
}
|