Files
flatrender/services/notification/internal/models/models.go
T
soroush.asadi 6dbb14d146
Build backend images / build content-svc (push) Failing after 14s
Build backend images / build file-svc (push) Failing after 22s
Build backend images / build gateway (push) Failing after 1m21s
Build backend images / build identity-svc (push) Failing after 1m43s
Build backend images / build notification-svc (push) Failing after 1m6s
Build backend images / build render-svc (push) Failing after 53s
Build backend images / build studio-svc (push) Failing after 1m5s
feat(notifications+admin): marketing campaigns
- campaigns table (migration 19) + CRUD + send endpoint in notification-svc
- audience resolution reads cross-schema from identity.users (all / verified /
  with_plan); send dispatches via the SMS or Email channel and logs deliveries
- endpoints: GET/POST /v1/campaigns, POST /v1/campaigns/:id/send, DELETE
- gateway route /v1/campaigns/* → notification
- /admin/marketing: create campaign (channel, audience, template/subject/body),
  list with status + sent counts, send, delete

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-02 18:17:19 +03:30

254 lines
9.9 KiB
Go

package models
import (
"time"
"github.com/google/uuid"
)
// ── Enum consts ───────────────────────────────────────────────────────────────
const (
// NotificationKind
KindRenderCompleted = "RenderCompleted"
KindRenderFailed = "RenderFailed"
KindRenderProgress = "RenderProgress"
KindPlanExpiring = "PlanExpiring"
KindPlanExpired = "PlanExpired"
KindPaymentSuccess = "PaymentSuccess"
KindPaymentFailed = "PaymentFailed"
KindStorageWarning = "StorageWarning"
KindStorageFull = "StorageFull"
KindExportExpiring = "ExportExpiring"
KindExportDeleted = "ExportDeleted"
KindGiftEarned = "GiftEarned"
KindQuestCompleted = "QuestCompleted"
KindLevelUp = "LevelUp"
KindAccountSecurity = "AccountSecurity"
KindSystemAnnouncement = "SystemAnnouncement"
KindTenantInvite = "TenantInvite"
KindMarketing = "Marketing"
KindOther = "Other"
// Priority
PriorityLow = "Low"
PriorityNormal = "Normal"
PriorityHigh = "High"
PriorityUrgent = "Urgent"
// DeliveryChannel
ChannelInApp = "InApp"
ChannelPush = "Push"
ChannelEmail = "Email"
ChannelSMS = "SMS"
ChannelTelegram = "Telegram"
ChannelWebhook = "Webhook"
// DeliveryStatus
StatusPending = "Pending"
StatusSent = "Sent"
StatusDelivered = "Delivered"
StatusFailed = "Failed"
StatusBounced = "Bounced"
StatusSuppressed = "Suppressed"
)
// ── Domain entities ──────────────────────────────────────────────────────────
type Notification struct {
ID uuid.UUID `json:"id"`
TenantID uuid.UUID `json:"tenant_id"`
UserID uuid.UUID `json:"user_id"`
NotificationType string `json:"notification_type"`
Priority string `json:"priority"`
Title string `json:"title"`
Message string `json:"message"`
Label *string `json:"label,omitempty"`
Signature *string `json:"signature,omitempty"`
Icon *string `json:"icon,omitempty"`
Image *string `json:"image,omitempty"`
AnimationDemo *string `json:"animation_demo,omitempty"`
Design *string `json:"design,omitempty"`
ActionURL *string `json:"action_url,omitempty"`
ActionText *string `json:"action_text,omitempty"`
RenderJobID *uuid.UUID `json:"render_job_id,omitempty"`
ExportID *uuid.UUID `json:"export_id,omitempty"`
PaymentID *uuid.UUID `json:"payment_id,omitempty"`
GiftID *uuid.UUID `json:"gift_id,omitempty"`
EarnedGiftID *uuid.UUID `json:"earned_gift_id,omitempty"`
IsEmergency bool `json:"is_emergency"`
Seen bool `json:"seen"`
SeenAt *time.Time `json:"seen_at,omitempty"`
Clicked bool `json:"clicked"`
ClickedAt *time.Time `json:"clicked_at,omitempty"`
GiftUsed bool `json:"gift_used"`
ExpireDate *time.Time `json:"expire_date,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
type NotificationPreference struct {
ID uuid.UUID `json:"id"`
UserID uuid.UUID `json:"user_id"`
NotificationType string `json:"notification_type"`
Channel string `json:"channel"`
Enabled bool `json:"enabled"`
UpdatedAt time.Time `json:"updated_at"`
}
type NotificationTemplate struct {
ID uuid.UUID `json:"id"`
TenantID *uuid.UUID `json:"tenant_id,omitempty"`
Code string `json:"code"`
Channel string `json:"channel"`
Locale string `json:"locale"`
Subject *string `json:"subject,omitempty"`
BodyText *string `json:"body_text,omitempty"`
BodyHTML *string `json:"body_html,omitempty"`
PushTitle *string `json:"push_title,omitempty"`
PushBody *string `json:"push_body,omitempty"`
PushIcon *string `json:"push_icon,omitempty"`
VariablesSchema *string `json:"variables_schema,omitempty"`
IsActive bool `json:"is_active"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
type NotificationDelivery struct {
ID uuid.UUID `json:"id"`
TenantID uuid.UUID `json:"tenant_id"`
UserID uuid.UUID `json:"user_id"`
NotificationID *uuid.UUID `json:"notification_id,omitempty"`
Channel string `json:"channel"`
Recipient string `json:"recipient"`
Subject *string `json:"subject,omitempty"`
Provider *string `json:"provider,omitempty"`
ProviderMessageID *string `json:"provider_message_id,omitempty"`
Status string `json:"status"`
ErrorMessage *string `json:"error_message,omitempty"`
Attempt int `json:"attempt"`
MaxAttempts int `json:"max_attempts"`
SentAt *time.Time `json:"sent_at,omitempty"`
DeliveredAt *time.Time `json:"delivered_at,omitempty"`
FailedAt *time.Time `json:"failed_at,omitempty"`
CreatedAt time.Time `json:"created_at"`
}
// ── Request / Response types ─────────────────────────────────────────────────
type PagedResponse[T any] struct {
Data []T `json:"data"`
Meta PaginationMeta `json:"meta"`
}
type PaginationMeta struct {
Page int `json:"page"`
PageSize int `json:"page_size"`
Total int64 `json:"total"`
HasMore bool `json:"has_more"`
}
type CreateNotificationRequest struct {
UserID uuid.UUID `json:"user_id" binding:"required"`
TenantID uuid.UUID `json:"tenant_id" binding:"required"`
NotificationType string `json:"notification_type" binding:"required"`
Priority *string `json:"priority"`
Title string `json:"title" binding:"required"`
Message string `json:"message" binding:"required"`
Label *string `json:"label"`
Icon *string `json:"icon"`
Image *string `json:"image"`
ActionURL *string `json:"action_url"`
ActionText *string `json:"action_text"`
RenderJobID *uuid.UUID `json:"render_job_id"`
ExportID *uuid.UUID `json:"export_id"`
PaymentID *uuid.UUID `json:"payment_id"`
GiftID *uuid.UUID `json:"gift_id"`
EarnedGiftID *uuid.UUID `json:"earned_gift_id"`
IsEmergency bool `json:"is_emergency"`
ExpireDate *time.Time `json:"expire_date"`
Channels []string `json:"channels"` // which delivery channels to trigger
}
type UpdatePreferenceRequest struct {
NotificationType string `json:"notification_type" binding:"required"`
Channel string `json:"channel" binding:"required"`
Enabled bool `json:"enabled"`
}
type TemplateUpsertRequest struct {
Code string `json:"code" binding:"required"`
Channel string `json:"channel" binding:"required"`
Locale string `json:"locale" binding:"required"`
Subject *string `json:"subject"`
BodyText *string `json:"body_text"`
BodyHTML *string `json:"body_html"`
PushTitle *string `json:"push_title"`
PushBody *string `json:"push_body"`
PushIcon *string `json:"push_icon"`
IsActive *bool `json:"is_active"`
}
type APIError struct {
Code string `json:"code"`
Message string `json:"message"`
}
// ── Channel provider config (SMS / Email) ────────────────────────────────────
type ChannelConfig struct {
TenantID uuid.UUID `json:"tenant_id"`
Channel string `json:"channel"` // 'sms' | 'email'
Settings map[string]any `json:"settings"`
Enabled bool `json:"enabled"`
UpdatedAt time.Time `json:"updated_at"`
}
type UpsertChannelConfigRequest struct {
Settings map[string]any `json:"settings"`
Enabled *bool `json:"enabled"`
}
type SendSMSRequest struct {
To string `json:"to" binding:"required"`
Message string `json:"message" binding:"required"`
}
type SendEmailRequest struct {
To string `json:"to" binding:"required"`
Subject string `json:"subject"`
BodyHTML string `json:"body_html"`
BodyText string `json:"body_text"`
TemplateCode string `json:"template_code"`
Locale string `json:"locale"`
Variables map[string]string `json:"variables"`
}
// ── Marketing campaigns ──────────────────────────────────────────────────────
type Campaign struct {
ID uuid.UUID `json:"id"`
TenantID uuid.UUID `json:"tenant_id"`
Name string `json:"name"`
Channel string `json:"channel"` // sms | email
Audience string `json:"audience"` // all | verified | with_plan
Subject *string `json:"subject,omitempty"`
BodyHTML *string `json:"body_html,omitempty"`
TemplateCode *string `json:"template_code,omitempty"`
Status string `json:"status"`
TotalCount int `json:"total_count"`
SentCount int `json:"sent_count"`
FailedCount int `json:"failed_count"`
CreatedAt time.Time `json:"created_at"`
SentAt *time.Time `json:"sent_at,omitempty"`
}
type CreateCampaignRequest struct {
Name string `json:"name" binding:"required"`
Channel string `json:"channel" binding:"required"` // sms | email
Audience string `json:"audience"`
Subject *string `json:"subject"`
BodyHTML *string `json:"body_html"`
TemplateCode *string `json:"template_code"`
}