CLI referencia

add notification

Modul-tulajdonú, többcsatornás notification generálása: payload, Via, csatornánkénti tartalom, és a közös Hub bekötése.

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> <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

internal/modules/contact/notifications/ordershipped.go
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:

internal/modules/contact/register.go
type Dependencies struct {
    // ...
    Notifier notify.Sender
    // gpsystem:dependencies
}
cmd/contact/main.go és cmd/worker/main.go
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),
)
A notifier belépési pontonként egyszer konstruálódik, a projekt első notificationjénél. Ha az 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ó):

migrations/<időbélyeg>_notifications.sql
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):

internal/modules/contact/listeners/sendordershipped.go
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.

Copyright © 2026