add notification
Ezt a generátort akkor éri meg elővenni, amikor a tartalom egy konkrét recipiensnek szól, nem csak egy címnek, és akár több csatornán is el kell érnie: egy rendelésállapot-értesítés, egy kommentválasz, egy fiókbiztonsági riasztás.
go tool gpsystem add notification <modul> <név>
Egy notificationt hoz létre az internal/modules/<modul>/notifications/<név>.go-ban: egy payload structot, egy Via-t (alapból [database, mail]), egy ToMail-t és egy ToDatabase-t, plusz a templates/<név>.html.tmpl + templates/<név>.txt.tmpl fájlokat. A projekt első notificationjénél (bármelyik modulban) bedrótozza a közös notify.Sender-t is minden belépési pontba. Idempotens: egy második notification, akár ugyanabban, akár másik modulban, újrahasználja a bekötést.
go tool gpsystem add notification contact orderShipped
Miért különbözik az add mail-től
Az add mail olyan tartalomhoz való, ami közvetlenül egy címre megy, recipient-fogalom nélkül: egy contact-form visszaigazolás, egy jelszó-visszaállító link. Az add notification olyan tartalomhoz, ami egy recipiensnek szól (egy stabil id-vel azonosított usernek), akár több csatornán egyszerre kézbesítve. A kettő független és összeadódik: egy notification ToMail-je ugyanazt a mail.Message/mail.Template-et építi, mint egy mail-notifier Notify-ja; lásd a Notification oldalt és annak kidolgozott példáját, ami mindkettőt egymás mellett használja.
Mit generál
package notifications
//go:embed templates/ordershipped.html.tmpl templates/ordershipped.txt.tmpl
var orderShippedTemplatesFS embed.FS
type OrderShipped struct {
}
var (
_ notify.Notification = OrderShipped{}
_ notify.Mailable = OrderShipped{}
_ notify.Databasable = OrderShipped{}
)
func (OrderShipped) NotificationName() string { return "contact.order_shipped" }
func (OrderShipped) Via(ctx context.Context, r notify.Recipient) []string {
return []string{notify.ChannelDatabase, notify.ChannelMail}
}
func (n OrderShipped) ToMail(ctx context.Context, r notify.Recipient) (*kitmail.Message, error) {
return kitmail.NewMessage().
WithSubject("TODO: subject for OrderShipped").
WithBody(kitmail.Template(orderShippedTemplatesFS, "templates/ordershipped.html.tmpl", n).
WithTextTemplate("templates/ordershipped.txt.tmpl")), nil
// nincs WithTo: a mail csatorna automatikusan r-re routol
}
func (n OrderShipped) ToDatabase(ctx context.Context, r notify.Recipient) (any, error) {
return n, nil
}
Plusz egy teszt memory mail csatornával és memory database store-ral, és két kitöltendő placeholder sablon. A payload struct üresen indul, ugyanúgy, ahogy az add event és az add mail is üres structot generál: add hozzá a sablonjaidnak szükséges mezőket, és tedd be a notify.ChannelBroadcast-ot a Via-ba, ha az add realtime már lefutott.
A projekt első notificationjénél:
type Dependencies struct {
// ...
Notifier notify.Sender
// gpsystem:dependencies
}
mailer := smtp.MustNewIfConfigured(ctx, cfg.Mail)
notifier := notify.NewHub(
notify.NewMailChannel(mailer),
notifydatabase.NewChannel(notifydatabase.NewStore(pg.NewDB(pool))),
)
// ...
deps := contact.Dependencies{
// ...
Notifier: notifier,
// gpsystem:dependencies-init
}
A notifier belépési pontonként egyszer konstruálódik (ugyanaz a minta, mint a közös mailernél és adatbázis-poolnál), és minden notificationt tartalmazó modul osztozik rajta. A database csatorna mindig a kit pgx-executorán (pg.NewDB(pool)) keresztül perzisztál, függetlenül a projekt üzleti moduljainak --db választásától: a pool mindenhol elérhető, ahol a notifier épül, míg egy bun.DB csak később, a modulonkénti Dependencies literálon belül konstruálódik.
Ha az add realtime már lefutott, a notifier-konstrukció egy notify/broadcast.Publisher-t is épít, és hozzáadja a notify.NewBroadcastChannel(...)-t a Hub-hoz, plusz a closerét:
pub := broadcast.MustNewPublisher(ctx, cfg.Realtime.BroadcastValkey(), cfg.Realtime.Broadcast)
notifier := notify.NewHub(
notify.NewMailChannel(mailer),
notifydatabase.NewChannel(notifydatabase.NewStore(pg.NewDB(pool))),
notify.NewBroadcastChannel(pub, cfg.Realtime.Broadcast.ChannelPrefix),
)
add realtimeazután fut le, hogy már létezik az első notification, a további modulokban keletkező notificationök is a korábbi (mail+database-only) konstrukciót használják újra, ahelyett hogy egy második, ütköző konstrukciót emitálnának: kösd be kézzel a broadcast csatornát abba a belépési pontba, vagy futtasd az add realtime-ot az első add notification előtt, hogy mindenhol automatikusan bedrótozódjon.Bedrótozza a migrációt is (idempotens: kihagyja, ha már létezik egy notifications tábla migráció):
CREATE TABLE notifications (
id uuid PRIMARY KEY, recipient_id text NOT NULL, name text NOT NULL,
payload jsonb NOT NULL DEFAULT '{}'::jsonb, read_at timestamptz,
created_at timestamptz NOT NULL DEFAULT now()
);
Küldés
Semmi nem hívja automatikusan a notificationt: küldd el egy service-ből (szinkron) vagy egy listenerből (aszinkron, add event / add listener révén):
return deps.Notifier.Send(ctx,
notify.Recipient{ID: identity.Subject, Email: identity.Email, Name: identity.Username},
contactnotifications.OrderShipped{ /* ... */ },
)
Az add notification ugyanazoknál a gpsystem:pools/gpsystem:dependencies-init/gpsystem:dependencies/gpsystem:config anchoroknál ír, mint az add mail; ha valamelyik hiányzik, lásd ott, mi történik.