Files
flatrender/services/notification/internal/models/models.go
T
soroush.asadi 507ac7e6a4
Build backend images / build content-svc (push) Failing after 56s
Build backend images / build file-svc (push) Failing after 47s
Build backend images / build gateway (push) Failing after 1m0s
Build backend images / build identity-svc (push) Failing after 56s
Build backend images / build notification-svc (push) Failing after 11s
Build backend images / build render-svc (push) Failing after 4m5s
Build backend images / build studio-svc (push) Failing after 56s
feat(notifications+admin): SMS (Kavenegar) + Email (SMTP) channels & templates
Backend (notification-svc):
- channel_config table (per-tenant Kavenegar + SMTP settings) + migration 18
- sender pkg: Kavenegar SMS client + SMTP mailer (STARTTLS / implicit TLS), stdlib only
- endpoints: GET/PUT /v1/channels[/:channel], POST /v1/sms/send, POST /v1/email/send
  (template + {{var}} rendering); deliveries logged
- seeded 3 Persian email templates: welcome / account_verification / promotion
- gateway routes /v1/{channels,sms,email}/* → notification

Admin UI:
- /admin/messaging: SMS + Email provider config cards, test-send, email template editor
- nav link + fa/en labels

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

226 lines
8.7 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"`
}