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