mirror of
https://github.com/warmbly/warmbly.git
synced 2026-08-19 16:01:16 +00:00
3ffa416e40
Adds GET /admin/mailboxes — paginated platform-wide mailbox list that
joins email_accounts → users → organizations so the table answers
"whose mailbox is this and where does it live" without N+1 fetches.
Search covers mailbox email / owner email / org name; status filter
defaults to active so the active surface shows first ("inactive" /
"all" both available). Provider filter speeds up "show me every Gmail
mailbox" investigations. Cursor pagination matches the rest of the
admin lists.
Frontend page surfaces warmup-on/off, send budget, and last-sync time
with red-when-never / amber-when-stale-over-24h tone so an
investigator can spot dead mailboxes fast. Mailbox email links into
the owning user's detail page; org name links into the workspace
admin so the pivot path stays one click in either direction.
Gated on AdminPermViewUsers since mailbox triage is tightly coupled to
user/org context today; a dedicated bit can be carved later if
mailbox-specific actions land.
653 lines
25 KiB
Go
653 lines
25 KiB
Go
package models
|
|
|
|
import (
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
// AdminUserSearch represents search parameters for admin user listing
|
|
type AdminUserSearch struct {
|
|
Query string `form:"q"`
|
|
Status string `form:"status"` // active, banned, all
|
|
IsAdmin *bool `form:"is_admin"`
|
|
Cursor *uuid.UUID `form:"cursor"`
|
|
Limit int `form:"limit"`
|
|
SortBy string `form:"sort_by"` // created_at, email, name
|
|
SortDesc bool `form:"sort_desc"`
|
|
}
|
|
|
|
// AdminUserDetail represents a user with admin-relevant statistics
|
|
type AdminUserDetail struct {
|
|
ID uuid.UUID `json:"id"`
|
|
FirstName string `json:"first_name"`
|
|
LastName string `json:"last_name"`
|
|
Email string `json:"email"`
|
|
MaxOrganizations int `json:"max_organizations"`
|
|
FreeTrialUsed bool `json:"free_trial_used"`
|
|
AdminPermissions AdminPermission `json:"admin_permissions"`
|
|
AdminGrantedAt *time.Time `json:"admin_granted_at,omitempty"`
|
|
AdminGrantedBy *uuid.UUID `json:"admin_granted_by,omitempty"`
|
|
BannedAt *time.Time `json:"banned_at,omitempty"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
UpdatedAt time.Time `json:"updated_at"`
|
|
|
|
// Statistics
|
|
OrganizationCount int `json:"organization_count"`
|
|
EmailAccountCount int `json:"email_account_count"`
|
|
CampaignCount int `json:"campaign_count"`
|
|
}
|
|
|
|
// AdminUsersResult represents paginated user listing
|
|
type AdminUsersResult struct {
|
|
Data []AdminUserDetail `json:"data"`
|
|
Pagination Pagination `json:"pagination"`
|
|
}
|
|
|
|
// UserBan represents a ban record for a user
|
|
type UserBan struct {
|
|
ID uuid.UUID `json:"id"`
|
|
UserID uuid.UUID `json:"user_id"`
|
|
BannedBy uuid.UUID `json:"banned_by"`
|
|
Reason string `json:"reason"`
|
|
BannedAt time.Time `json:"banned_at"`
|
|
UnbannedAt *time.Time `json:"unbanned_at,omitempty"`
|
|
UnbannedBy *uuid.UUID `json:"unbanned_by,omitempty"`
|
|
UnbanReason *string `json:"unban_reason,omitempty"`
|
|
|
|
// Joined data
|
|
BannedByUser *AdminUserSummary `json:"banned_by_user,omitempty"`
|
|
UnbannedByUser *AdminUserSummary `json:"unbanned_by_user,omitempty"`
|
|
}
|
|
|
|
// AdminUserSummary is a minimal user representation for joined data
|
|
type AdminUserSummary struct {
|
|
ID uuid.UUID `json:"id"`
|
|
FirstName string `json:"first_name"`
|
|
LastName string `json:"last_name"`
|
|
Email string `json:"email"`
|
|
}
|
|
|
|
// BanScope is a bitmask describing which actions are blocked while a
|
|
// user is banned. The existing banned_at column still marks "banned",
|
|
// but the scope decides what concretely stops working. Kept in sync
|
|
// with the values in 000045_ban_scope.up.sql.
|
|
type BanScope uint32
|
|
|
|
const (
|
|
BanScopeLogin BanScope = 1 << iota // 1 — block authentication
|
|
BanScopeOrgCreate // 2 — refuse new workspace creation
|
|
BanScopeSend // 4 — block outbound campaign sends
|
|
)
|
|
|
|
// BanScopeAll is the legacy "everything" mask applied at migration to
|
|
// preserve the pre-bitmask "fully banned" semantics. New bans should
|
|
// pick a more specific scope where possible.
|
|
const BanScopeAll = BanScopeLogin | BanScopeOrgCreate | BanScopeSend
|
|
|
|
func (s BanScope) Has(flag BanScope) bool { return s&flag == flag }
|
|
|
|
// BanUserRequest represents the request to ban a user
|
|
type BanUserRequest struct {
|
|
Reason string `json:"reason" binding:"required"`
|
|
// Scope is a BanScope bitmask. Zero / missing falls back to
|
|
// BanScopeLogin so the historic "you can't log in" behaviour
|
|
// stays the default for ambiguous calls.
|
|
Scope BanScope `json:"scope,omitempty"`
|
|
}
|
|
|
|
// UnbanUserRequest represents the request to unban a user
|
|
type UnbanUserRequest struct {
|
|
Reason string `json:"reason" binding:"required"`
|
|
}
|
|
|
|
// AdminWorkerDetail represents a worker with admin-relevant details
|
|
type AdminWorkerDetail struct {
|
|
ID uuid.UUID `json:"id"`
|
|
Name string `json:"name"`
|
|
Notes string `json:"notes"`
|
|
IPAddr string `json:"ip_addr"`
|
|
Active bool `json:"active"`
|
|
FreeTier bool `json:"free_tier"`
|
|
WorkerType WorkerType `json:"worker_type"`
|
|
AccountCount int `json:"account_count"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
UpdatedAt time.Time `json:"updated_at"`
|
|
|
|
// Statistics
|
|
EmailsSentToday int `json:"emails_sent_today"`
|
|
EmailsSentTotal int `json:"emails_sent_total"`
|
|
ActiveCampaigns int `json:"active_campaigns"`
|
|
ConnectedEmails int `json:"connected_emails"`
|
|
WarmupEmails int `json:"warmup_emails"`
|
|
}
|
|
|
|
// AdminWorkersResult represents paginated worker listing
|
|
type AdminWorkersResult struct {
|
|
Data []AdminWorkerDetail `json:"data"`
|
|
Pagination Pagination `json:"pagination"`
|
|
}
|
|
|
|
// AdminUpdateWorker represents the request to update a worker
|
|
type AdminUpdateWorker struct {
|
|
Name *string `json:"name,omitempty"`
|
|
Notes *string `json:"notes,omitempty"`
|
|
Active *bool `json:"active,omitempty"`
|
|
WorkerType *WorkerType `json:"worker_type,omitempty"`
|
|
}
|
|
|
|
// AdminWorkerEmail represents an email account connected to a worker
|
|
type AdminWorkerEmail struct {
|
|
ID uuid.UUID `json:"id"`
|
|
Email string `json:"email"`
|
|
UserID uuid.UUID `json:"user_id"`
|
|
OrganizationID *uuid.UUID `json:"organization_id,omitempty"`
|
|
Status string `json:"status"`
|
|
Provider string `json:"provider"`
|
|
WarmupEnabled bool `json:"warmup_enabled"`
|
|
LastSyncedAt time.Time `json:"last_synced_at"`
|
|
}
|
|
|
|
// ReassignEmailsRequest represents the request to reassign emails
|
|
type ReassignEmailsRequest struct {
|
|
EmailIDs []uuid.UUID `json:"email_ids" binding:"required"`
|
|
NewWorkerID uuid.UUID `json:"new_worker_id" binding:"required"`
|
|
}
|
|
|
|
// WarmupAppealStatus represents the status of a warmup appeal
|
|
type WarmupAppealStatus string
|
|
|
|
const (
|
|
WarmupAppealStatusPending WarmupAppealStatus = "pending"
|
|
WarmupAppealStatusApproved WarmupAppealStatus = "approved"
|
|
WarmupAppealStatusRejected WarmupAppealStatus = "rejected"
|
|
)
|
|
|
|
// WarmupAppeal represents an appeal for a blocked warmup account
|
|
type WarmupAppeal struct {
|
|
ID uuid.UUID `json:"id"`
|
|
EmailAccountID uuid.UUID `json:"email_account_id"`
|
|
UserID uuid.UUID `json:"user_id"`
|
|
Reason string `json:"reason"`
|
|
Status WarmupAppealStatus `json:"status"`
|
|
ReviewedBy *uuid.UUID `json:"reviewed_by,omitempty"`
|
|
ReviewedAt *time.Time `json:"reviewed_at,omitempty"`
|
|
ReviewNotes *string `json:"review_notes,omitempty"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
|
|
// Joined data
|
|
User *AdminUserSummary `json:"user,omitempty"`
|
|
EmailAccount *AdminWorkerEmail `json:"email_account,omitempty"`
|
|
ReviewedByUser *AdminUserSummary `json:"reviewed_by_user,omitempty"`
|
|
}
|
|
|
|
// WarmupAppealsResult represents paginated appeal listing
|
|
type WarmupAppealsResult struct {
|
|
Data []WarmupAppeal `json:"data"`
|
|
Pagination Pagination `json:"pagination"`
|
|
}
|
|
|
|
// ReviewAppealRequest represents the request to review an appeal
|
|
type ReviewAppealRequest struct {
|
|
Approved bool `json:"approved"`
|
|
Notes string `json:"notes"`
|
|
}
|
|
|
|
// AdminBlockedAccount represents a blocked warmup account
|
|
type AdminBlockedAccount struct {
|
|
ID uuid.UUID `json:"id"`
|
|
Email string `json:"email"`
|
|
UserID uuid.UUID `json:"user_id"`
|
|
BlockedAt time.Time `json:"blocked_at"`
|
|
BlockedBy *uuid.UUID `json:"blocked_by,omitempty"`
|
|
BlockReason string `json:"block_reason"`
|
|
HasAppeal bool `json:"has_appeal"`
|
|
AppealStatus *WarmupAppealStatus `json:"appeal_status,omitempty"`
|
|
|
|
// Joined data
|
|
User *AdminUserSummary `json:"user,omitempty"`
|
|
}
|
|
|
|
// AdminBlockedAccountsResult represents paginated blocked accounts
|
|
type AdminBlockedAccountsResult struct {
|
|
Data []AdminBlockedAccount `json:"data"`
|
|
Pagination Pagination `json:"pagination"`
|
|
}
|
|
|
|
// BlockAccountRequest represents the request to block an account
|
|
type BlockAccountRequest struct {
|
|
Reason string `json:"reason" binding:"required"`
|
|
}
|
|
|
|
// AdminCampaignDetail represents a campaign with admin-relevant details
|
|
type AdminCampaignDetail struct {
|
|
ID uuid.UUID `json:"id"`
|
|
Name string `json:"name"`
|
|
UserID uuid.UUID `json:"user_id"`
|
|
OrganizationID uuid.UUID `json:"organization_id"`
|
|
Status string `json:"status"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
StartedAt *time.Time `json:"started_at,omitempty"`
|
|
StoppedAt *time.Time `json:"stopped_at,omitempty"`
|
|
|
|
// Statistics
|
|
TotalContacts int `json:"total_contacts"`
|
|
EmailsSent int `json:"emails_sent"`
|
|
EmailsOpened int `json:"emails_opened"`
|
|
EmailsClicked int `json:"emails_clicked"`
|
|
EmailsReplied int `json:"emails_replied"`
|
|
EmailsBounced int `json:"emails_bounced"`
|
|
|
|
// Joined data
|
|
User *AdminUserSummary `json:"user,omitempty"`
|
|
Organization *Organization `json:"organization,omitempty"`
|
|
}
|
|
|
|
// AdminCampaignsResult represents paginated campaign listing
|
|
type AdminCampaignsResult struct {
|
|
Data []AdminCampaignDetail `json:"data"`
|
|
Pagination Pagination `json:"pagination"`
|
|
}
|
|
|
|
// AdminCampaignSearch represents search parameters for campaigns
|
|
type AdminCampaignSearch struct {
|
|
Query string `form:"q"`
|
|
UserID *uuid.UUID `form:"user_id"`
|
|
OrgID *uuid.UUID `form:"org_id"`
|
|
Status string `form:"status"` // active, paused, completed, all
|
|
Cursor *uuid.UUID `form:"cursor"`
|
|
Limit int `form:"limit"`
|
|
SortBy string `form:"sort_by"`
|
|
SortDesc bool `form:"sort_desc"`
|
|
}
|
|
|
|
// AdminAuditLog represents an audit log entry for admin actions
|
|
type AdminAuditLog struct {
|
|
ID uuid.UUID `json:"id"`
|
|
AdminUserID uuid.UUID `json:"admin_user_id"`
|
|
Action string `json:"action"`
|
|
TargetType string `json:"target_type"`
|
|
TargetID uuid.UUID `json:"target_id"`
|
|
Details map[string]any `json:"details,omitempty"`
|
|
IPAddress string `json:"ip_address"`
|
|
UserAgent string `json:"user_agent"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
|
|
// Joined data
|
|
AdminUser *AdminUserSummary `json:"admin_user,omitempty"`
|
|
}
|
|
|
|
// AdminAuditLogsResult represents paginated audit logs
|
|
type AdminAuditLogsResult struct {
|
|
Data []AdminAuditLog `json:"data"`
|
|
Pagination Pagination `json:"pagination"`
|
|
}
|
|
|
|
// AdminAuditLogSearch represents search parameters for audit logs
|
|
type AdminAuditLogSearch struct {
|
|
AdminUserID *uuid.UUID `form:"admin_user_id"`
|
|
Action string `form:"action"`
|
|
TargetType string `form:"target_type"`
|
|
TargetID *uuid.UUID `form:"target_id"`
|
|
StartDate *time.Time `form:"start_date"`
|
|
EndDate *time.Time `form:"end_date"`
|
|
Cursor *uuid.UUID `form:"cursor"`
|
|
Limit int `form:"limit"`
|
|
}
|
|
|
|
// PlatformOverview represents high-level platform statistics
|
|
type PlatformOverview struct {
|
|
TotalUsers int64 `json:"total_users"`
|
|
ActiveUsers int64 `json:"active_users"` // Active in last 30 days
|
|
NewUsersToday int64 `json:"new_users_today"`
|
|
NewUsersThisWeek int64 `json:"new_users_this_week"`
|
|
|
|
TotalCampaigns int64 `json:"total_campaigns"`
|
|
ActiveCampaigns int64 `json:"active_campaigns"`
|
|
|
|
TotalEmailsSent int64 `json:"total_emails_sent"`
|
|
EmailsSentToday int64 `json:"emails_sent_today"`
|
|
|
|
TotalWorkers int64 `json:"total_workers"`
|
|
ActiveWorkers int64 `json:"active_workers"`
|
|
|
|
WarmupBlockedCount int64 `json:"warmup_blocked_count"`
|
|
PendingAppeals int64 `json:"pending_appeals"`
|
|
|
|
ActiveSubscriptions int64 `json:"active_subscriptions"`
|
|
TrialingUsers int64 `json:"trialing_users"`
|
|
}
|
|
|
|
// AdminMailboxRow is one row in the platform-wide mailbox admin list.
|
|
// Joins the connected mailbox with owner + workspace so the table can
|
|
// answer "whose mailbox is this and where does it live" without
|
|
// fan-out fetches.
|
|
type AdminMailboxRow struct {
|
|
ID uuid.UUID `json:"id"`
|
|
Email string `json:"email"`
|
|
Provider string `json:"provider"`
|
|
Status string `json:"status"`
|
|
UserID uuid.UUID `json:"user_id"`
|
|
OwnerEmail string `json:"owner_email"`
|
|
OrganizationID *uuid.UUID `json:"organization_id,omitempty"`
|
|
OrgName *string `json:"org_name,omitempty"`
|
|
WorkerID *uuid.UUID `json:"worker_id,omitempty"`
|
|
WarmupEnabled bool `json:"warmup_enabled"`
|
|
CampaignLimit int `json:"campaign_limit"`
|
|
LastSyncedAt *time.Time `json:"last_synced_at,omitempty"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
}
|
|
|
|
// AdminMailboxesResult is the paginated wrapper.
|
|
type AdminMailboxesResult struct {
|
|
Data []AdminMailboxRow `json:"data"`
|
|
Pagination Pagination `json:"pagination"`
|
|
}
|
|
|
|
// AdminMailboxSearch covers the query knobs the admin table needs.
|
|
// All fields optional; empty status = active, "all" returns every
|
|
// status. provider lets ops filter to "all gmail mailboxes" quickly.
|
|
type AdminMailboxSearch struct {
|
|
Query string `form:"q"`
|
|
Status string `form:"status"`
|
|
Provider string `form:"provider"`
|
|
Cursor *uuid.UUID `form:"cursor"`
|
|
Limit int `form:"limit"`
|
|
}
|
|
|
|
// DailyEmailStats represents daily email statistics for graphs
|
|
type DailyEmailStats struct {
|
|
Date time.Time `json:"date"`
|
|
TotalSent int64 `json:"total_sent"`
|
|
TotalDelivered int64 `json:"total_delivered"`
|
|
TotalBounced int64 `json:"total_bounced"`
|
|
TotalOpened int64 `json:"total_opened"`
|
|
TotalClicked int64 `json:"total_clicked"`
|
|
TotalReplied int64 `json:"total_replied"`
|
|
}
|
|
|
|
// HourlyEmailStats represents hourly email statistics
|
|
type HourlyEmailStats struct {
|
|
Hour int `json:"hour"`
|
|
TotalSent int64 `json:"total_sent"`
|
|
}
|
|
|
|
// WorkerLoadStats represents worker load statistics
|
|
type WorkerLoadStats struct {
|
|
WorkerID uuid.UUID `json:"worker_id"`
|
|
WorkerName string `json:"worker_name"`
|
|
EmailsSentToday int64 `json:"emails_sent_today"`
|
|
QueuedEmails int64 `json:"queued_emails"`
|
|
ConnectedEmails int64 `json:"connected_emails"`
|
|
CPUUsage float64 `json:"cpu_usage,omitempty"`
|
|
MemoryUsage float64 `json:"memory_usage,omitempty"`
|
|
}
|
|
|
|
// UserGrowthStats represents user growth statistics
|
|
type UserGrowthStats struct {
|
|
Date time.Time `json:"date"`
|
|
NewUsers int64 `json:"new_users"`
|
|
TotalUsers int64 `json:"total_users"`
|
|
}
|
|
|
|
// AnalyticsTrends represents trend data for analytics
|
|
type AnalyticsTrends struct {
|
|
UsersGrowthPercent float64 `json:"users_growth_percent"`
|
|
EmailsGrowthPercent float64 `json:"emails_growth_percent"`
|
|
CampaignsGrowthPercent float64 `json:"campaigns_growth_percent"`
|
|
RevenueGrowthPercent float64 `json:"revenue_growth_percent"`
|
|
}
|
|
|
|
// CreatePlanRequest represents the request to create a custom plan
|
|
type CreatePlanRequest struct {
|
|
Name string `json:"name" binding:"required"`
|
|
MaxContacts uint `json:"max_contacts"`
|
|
DailyEmails uint `json:"daily_emails"`
|
|
AIGeneration bool `json:"ai_generation"`
|
|
AccountLimit uint `json:"account_limit"`
|
|
Price float32 `json:"price"`
|
|
DiscountedPrice float32 `json:"discounted_price"`
|
|
Duration Duration `json:"duration"` // month/year
|
|
DedicatedWorkers int `json:"dedicated_workers"`
|
|
DailyCampaignLimit *int `json:"daily_campaign_limit,omitempty"`
|
|
MaxCampaigns *int `json:"max_campaigns,omitempty"`
|
|
MaxActiveCampaigns *int `json:"max_active_campaigns,omitempty"`
|
|
MaxTeamMembers *int `json:"max_team_members,omitempty"`
|
|
MaxEmailAccounts *int `json:"max_email_accounts,omitempty"`
|
|
Public bool `json:"public"` // false for enterprise-only
|
|
}
|
|
|
|
// UpdatePlanRequest represents the request to update a plan
|
|
type UpdatePlanRequest struct {
|
|
Name *string `json:"name,omitempty"`
|
|
MaxContacts *uint `json:"max_contacts,omitempty"`
|
|
DailyEmails *uint `json:"daily_emails,omitempty"`
|
|
AIGeneration *bool `json:"ai_generation,omitempty"`
|
|
AccountLimit *uint `json:"account_limit,omitempty"`
|
|
Price *float32 `json:"price,omitempty"`
|
|
DiscountedPrice *float32 `json:"discounted_price,omitempty"`
|
|
Duration *Duration `json:"duration,omitempty"`
|
|
DedicatedWorkers *int `json:"dedicated_workers,omitempty"`
|
|
DailyCampaignLimit *int `json:"daily_campaign_limit,omitempty"`
|
|
MaxCampaigns *int `json:"max_campaigns,omitempty"`
|
|
MaxActiveCampaigns *int `json:"max_active_campaigns,omitempty"`
|
|
MaxTeamMembers *int `json:"max_team_members,omitempty"`
|
|
MaxEmailAccounts *int `json:"max_email_accounts,omitempty"`
|
|
Public *bool `json:"public,omitempty"`
|
|
}
|
|
|
|
// AdminEnterpriseInquiry represents an enterprise inquiry with admin details
|
|
type AdminEnterpriseInquiry struct {
|
|
ID uuid.UUID `json:"id"`
|
|
UserID *uuid.UUID `json:"user_id,omitempty"`
|
|
CompanyName string `json:"company_name"`
|
|
ContactName string `json:"contact_name"`
|
|
ContactEmail string `json:"contact_email"`
|
|
Phone *string `json:"phone,omitempty"`
|
|
TeamSize *string `json:"team_size,omitempty"`
|
|
MonthlyEmailVolume *string `json:"monthly_email_volume,omitempty"`
|
|
Message *string `json:"message,omitempty"`
|
|
Status string `json:"status"`
|
|
AssignedTo *uuid.UUID `json:"assigned_to,omitempty"`
|
|
Notes *string `json:"notes,omitempty"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
UpdatedAt time.Time `json:"updated_at"`
|
|
|
|
// Joined data
|
|
User *AdminUserSummary `json:"user,omitempty"`
|
|
AssignedAdmin *AdminUserSummary `json:"assigned_admin,omitempty"`
|
|
}
|
|
|
|
// AdminEnterpriseInquiriesResult represents paginated inquiries
|
|
type AdminEnterpriseInquiriesResult struct {
|
|
Data []AdminEnterpriseInquiry `json:"data"`
|
|
Pagination Pagination `json:"pagination"`
|
|
}
|
|
|
|
// UpdateEnterpriseInquiryRequest represents the request to update an inquiry
|
|
type UpdateEnterpriseInquiryRequest struct {
|
|
Status *string `json:"status,omitempty"`
|
|
AssignedTo *uuid.UUID `json:"assigned_to,omitempty"`
|
|
Notes *string `json:"notes,omitempty"`
|
|
}
|
|
|
|
// AdminInfo represents an admin user for listing
|
|
type AdminInfo struct {
|
|
ID uuid.UUID `json:"id"`
|
|
FirstName string `json:"first_name"`
|
|
LastName string `json:"last_name"`
|
|
Email string `json:"email"`
|
|
AdminPermissions AdminPermission `json:"admin_permissions"`
|
|
AdminGrantedAt *time.Time `json:"admin_granted_at,omitempty"`
|
|
AdminGrantedBy *uuid.UUID `json:"admin_granted_by,omitempty"`
|
|
|
|
// Joined
|
|
GrantedByUser *AdminUserSummary `json:"granted_by_user,omitempty"`
|
|
}
|
|
|
|
// AdminsResult represents paginated admin listing
|
|
type AdminsResult struct {
|
|
Data []AdminInfo `json:"data"`
|
|
Pagination Pagination `json:"pagination"`
|
|
}
|
|
|
|
// GrantAdminRequest represents the request to grant admin permissions
|
|
type GrantAdminRequest struct {
|
|
Permissions AdminPermission `json:"permissions" binding:"required"`
|
|
}
|
|
|
|
// AdminUserRateLimits represents rate limits for a specific user
|
|
type AdminUserRateLimits struct {
|
|
UserID uuid.UUID `json:"user_id"`
|
|
LimitWSMessagePM *int `json:"limit_ws_message_pm,omitempty"`
|
|
LimitWSJoinPM *int `json:"limit_ws_join_pm,omitempty"`
|
|
LimitWSEventPM *int `json:"limit_ws_event_pm,omitempty"`
|
|
MaxConnections *int `json:"max_connections,omitempty"`
|
|
DailyEmailLimit *int `json:"daily_email_limit,omitempty"`
|
|
UpdatedAt time.Time `json:"updated_at"`
|
|
}
|
|
|
|
// UpdateUserRateLimitsRequest represents the request to update user rate limits
|
|
type UpdateUserRateLimitsRequest struct {
|
|
LimitWSMessagePM *int `json:"limit_ws_message_pm,omitempty"`
|
|
LimitWSJoinPM *int `json:"limit_ws_join_pm,omitempty"`
|
|
LimitWSEventPM *int `json:"limit_ws_event_pm,omitempty"`
|
|
MaxConnections *int `json:"max_connections,omitempty"`
|
|
DailyEmailLimit *int `json:"daily_email_limit,omitempty"`
|
|
}
|
|
|
|
// AdminUserPreview represents a full preview of a user's account
|
|
type AdminUserPreview struct {
|
|
User AdminUserDetail `json:"user"`
|
|
Organizations []Organization `json:"organizations"`
|
|
Subscriptions []Subscription `json:"subscriptions"`
|
|
EmailAccounts []AdminWorkerEmail `json:"email_accounts"`
|
|
RecentBans []UserBan `json:"recent_bans"`
|
|
RateLimits *AdminUserRateLimits `json:"rate_limits,omitempty"`
|
|
}
|
|
|
|
// WarmupPoolInfo represents information about a warmup pool
|
|
type WarmupPoolInfo struct {
|
|
Type string `json:"type"`
|
|
TotalParticipants int64 `json:"total_participants"`
|
|
ActiveParticipants int64 `json:"active_participants"`
|
|
BlockedCount int64 `json:"blocked_count"`
|
|
}
|
|
|
|
// WarmupPoolParticipant represents a participant in a warmup pool
|
|
type WarmupPoolParticipant struct {
|
|
ID uuid.UUID `json:"id"`
|
|
Email string `json:"email"`
|
|
UserID uuid.UUID `json:"user_id"`
|
|
JoinedAt time.Time `json:"joined_at"`
|
|
EmailsSent int64 `json:"emails_sent"`
|
|
EmailsReceived int64 `json:"emails_received"`
|
|
ReputationScore float64 `json:"reputation_score"`
|
|
IsBlocked bool `json:"is_blocked"`
|
|
BlockedAt *time.Time `json:"blocked_at,omitempty"`
|
|
|
|
// Joined data
|
|
User *AdminUserSummary `json:"user,omitempty"`
|
|
}
|
|
|
|
// WarmupPoolParticipantsResult represents paginated pool participants
|
|
type WarmupPoolParticipantsResult struct {
|
|
Data []WarmupPoolParticipant `json:"data"`
|
|
Pagination Pagination `json:"pagination"`
|
|
}
|
|
|
|
// WorkerStats represents statistics for a specific worker
|
|
type WorkerStats struct {
|
|
WorkerID uuid.UUID `json:"worker_id"`
|
|
TotalEmailsSent int64 `json:"total_emails_sent"`
|
|
EmailsSentToday int64 `json:"emails_sent_today"`
|
|
EmailsSentThisWeek int64 `json:"emails_sent_this_week"`
|
|
AverageDeliveryTime float64 `json:"average_delivery_time_ms"`
|
|
SuccessRate float64 `json:"success_rate"`
|
|
QueueDepth int64 `json:"queue_depth"`
|
|
}
|
|
|
|
// EmailDistribution represents email distribution across workers
|
|
type EmailDistribution struct {
|
|
WorkerID uuid.UUID `json:"worker_id"`
|
|
WorkerName string `json:"worker_name"`
|
|
EmailCount int64 `json:"email_count"`
|
|
Percentage float64 `json:"percentage"`
|
|
}
|
|
|
|
// AdminOrgSearch are the query params for the admin organization listing.
|
|
type AdminOrgSearch struct {
|
|
Query string `form:"q"`
|
|
Status string `form:"status"` // active, pending_deletion, all
|
|
Cursor *uuid.UUID `form:"cursor"`
|
|
Limit int `form:"limit"`
|
|
SortBy string `form:"sort_by"` // created_at, name
|
|
SortDesc bool `form:"sort_desc"`
|
|
}
|
|
|
|
// AdminOrgListItem is one row in the admin org list. It carries enough
|
|
// summary state for the table (owner, counts, deletion status) without
|
|
// joining to plans or subscriptions — those land on the detail endpoint.
|
|
type AdminOrgListItem struct {
|
|
ID uuid.UUID `json:"id"`
|
|
Name string `json:"name"`
|
|
Slug *string `json:"slug,omitempty"`
|
|
OwnerUserID uuid.UUID `json:"owner_user_id"`
|
|
OwnerEmail string `json:"owner_email"`
|
|
OwnerFirstName string `json:"owner_first_name"`
|
|
OwnerLastName string `json:"owner_last_name"`
|
|
OwnerBannedAt *time.Time `json:"owner_banned_at,omitempty"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
DeletionScheduledFor *time.Time `json:"deletion_scheduled_for,omitempty"`
|
|
|
|
// Resource counts. Cheap enough to inline on the list query so the
|
|
// table can show usage at a glance without an extra round-trip.
|
|
MemberCount int `json:"member_count"`
|
|
EmailAccountCount int `json:"email_account_count"`
|
|
CampaignCount int `json:"campaign_count"`
|
|
ActiveCampaigns int `json:"active_campaigns"`
|
|
}
|
|
|
|
// AdminOrgsResult is the paginated response for the admin org listing.
|
|
type AdminOrgsResult struct {
|
|
Data []AdminOrgListItem `json:"data"`
|
|
Pagination Pagination `json:"pagination"`
|
|
}
|
|
|
|
// AdminOrgDetail is the full payload for the org detail page. Carries
|
|
// three limit blocks side-by-side so the UI can explain *why* each
|
|
// effective number is what it is:
|
|
//
|
|
// - Limits — plan defaults (nil = unlimited)
|
|
// - Overrides — raw override row (0 per field = inherit)
|
|
// - EffectiveLimits — what the runtime actually enforces
|
|
type AdminOrgDetail struct {
|
|
AdminOrgListItem
|
|
|
|
UpdatedAt time.Time `json:"updated_at"`
|
|
DeletionScheduledAt *time.Time `json:"deletion_scheduled_at,omitempty"`
|
|
Limits *OrganizationLimits `json:"limits,omitempty"`
|
|
Overrides *OrganizationLimitOverrides `json:"overrides,omitempty"`
|
|
EffectiveLimits *OrganizationLimits `json:"effective_limits,omitempty"`
|
|
Counts *OrganizationCounts `json:"counts,omitempty"`
|
|
|
|
// Plan / subscription context. Either may be nil (org with no active
|
|
// subscription — e.g. trial or freshly created).
|
|
PlanName *string `json:"plan_name,omitempty"`
|
|
SubscriptionStatus *string `json:"subscription_status,omitempty"`
|
|
IsEnterprise bool `json:"is_enterprise"`
|
|
CurrentPeriodEnd *time.Time `json:"current_period_end,omitempty"`
|
|
TrialEnd *time.Time `json:"trial_end,omitempty"`
|
|
}
|
|
|
|
// AdminOrgMember is a member row enriched with the joined user.
|
|
type AdminOrgMember struct {
|
|
OrganizationMember
|
|
User *AdminUserSummary `json:"user,omitempty"`
|
|
}
|
|
|
|
// AdminOrgMembersResult is the response for /admin/organizations/:id/members.
|
|
type AdminOrgMembersResult struct {
|
|
Data []AdminOrgMember `json:"data"`
|
|
}
|