feat(notifications+admin): marketing campaigns
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

- 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>
This commit is contained in:
soroush.asadi
2026-06-02 18:17:19 +03:30
parent 507ac7e6a4
commit 6dbb14d146
11 changed files with 496 additions and 2 deletions
+7
View File
@@ -40,6 +40,7 @@ func main() {
prefH := handlers.NewPreferenceHandler(store)
tplH := handlers.NewTemplateHandler(store)
chH := handlers.NewChannelHandler(store)
campH := handlers.NewCampaignHandler(store)
r := gin.Default()
@@ -93,6 +94,12 @@ func main() {
v1.POST("/sms/send", auth, admin, chH.SendSMS)
v1.POST("/email/send", auth, admin, chH.SendEmail)
// ── Marketing campaigns (admin) ───────────────────────────────────────────
v1.GET("/campaigns", auth, admin, campH.List)
v1.POST("/campaigns", auth, admin, campH.Create)
v1.POST("/campaigns/:id/send", auth, admin, campH.Send)
v1.DELETE("/campaigns/:id", auth, admin, campH.Delete)
// ── Internal: create notification (service-to-service) ───────────────────
v1.POST("/internal/notifications", serviceAuth, notifH.CreateInternal)