Files
warmbly/internal/models/admin.go
T
Matt 3ffa416e40 feat(admin): mailboxes admin (cross-org triage)
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.
2026-05-28 12:38:08 +02:00

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"`
}