feat: V2 microservices stack — backend services, gateway, JWT auth
Add full V2 architecture: identity, content, studio (.NET 10) and file, render, notification, gateway (Go) services with vendored deps, plus DB migrations, event/API contracts, and an init-db script. Wire the Next.js frontend to the gateway: server-side JWT auth routes (login/register/refresh/logout/me), gateway fetch helper, and session/ cookie/jwt helpers under src/lib. Containerize the stack via docker-compose.v2.yml and per-service Dockerfiles. Base images resolve through a Nexus mirror (Docker Hub) and MCR directly; npm/NuGet pull from Nexus groups. Self-host fonts via next/font/local to avoid Google Fonts (geo-blocked). Add CI workflow and ignore .env.v2, *.stackdump, and .NET bin/obj. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,195 @@
|
||||
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"`
|
||||
}
|
||||
Reference in New Issue
Block a user