Documentation
¶
Overview ¶
Package internal is every implementation of the notification module. Nothing outside modules/notification can import it, which is the compiler enforcing idea 3.
Index ¶
- func RegisterRoutes(api *httpx.API, svc contracts.Service)
- func SendMail(mailer contracts.Mailer, recipients contracts.RecipientLookup, ...) events.Subscription
- type Mail
- type SMTP
- type Service
- func (s *Service) ListFor(_ context.Context, tx db.Tx[db.Tenant], recipient uuid.UUID, q crud.Query) ([]*contracts.Notification, int64, error)
- func (s *Service) MarkRead(ctx context.Context, tx db.Tx[db.Tenant], id, recipient uuid.UUID) (*contracts.Notification, error)
- func (s *Service) Notify(ctx context.Context, tx db.Tx[db.Tenant], n contracts.Notice) (*contracts.Notification, error)
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func RegisterRoutes ¶
RegisterRoutes mounts the two routes a per-recipient resource has.
There is no rest.Spec, and the reason is one sentence: a Spec's list route is the whole tenant and these rows are addressed to somebody. Every caller's list is a different list, so there is no permission to ask for either — SignedIn is the whole of it, and the scoping is that the recipient is the principal rather than a parameter.
func SendMail ¶
func SendMail(mailer contracts.Mailer, recipients contracts.RecipientLookup, hosts contracts.HostLookup, secure bool) events.Subscription
SendMail is the subscription that sends the message.
It is a subscriber and not part of Notify for the reason the event exists: the request that raised the notice commits its row and returns, and the worker talks to the mail server. So a slow relay costs nobody a response, a failure is retried by the outbox on the kernel's own ladder, and a message that can never be sent ends in platformkit_dead_letters rather than in a log line somebody has to notice.
What it is handed is two identifiers. The row, the address and the host are all read here, inside the transaction the kernel opened in the event's own tenant — see contracts.EmailRequested for why the payload is that thin, and for the two consequences: a notice deleted in the meantime is a skip, and an address changed in the meantime is the one the mail goes to.
Types ¶
type Mail ¶
Mail is one outgoing mail server, shaped like config.Mail so that main converts one to the other in a line and this module depends on a struct of its own — the arrangement modules/auth has with its OIDC provider.
type SMTP ¶
type SMTP struct {
// contains filtered or unexported fields
}
SMTP is the one production Mailer: stdlib net/smtp, STARTTLS when the server offers it, authentication only when a username is configured. There is no provider abstraction, because SMTP is what every service worth naming speaks.
func NewSMTP ¶
NewSMTP returns the sender for cfg. main wires notification.Mailbox instead when no host is configured, and says so at boot.
func (*SMTP) Send ¶
Send delivers one message. The connection is opened, used and closed per message: this runs in the worker, one event at a time, so a pool would buy nothing. A send that fails is retried by the outbox rather than here, because the kernel owns the retry ladder and two of them would be two policies — and mailStep is what guarantees a send fails at all rather than waiting forever.
type Service ¶
type Service struct {
// contains filtered or unexported fields
}
Service writes notifications and reads one person's back. Its one field is how it turns a recipient into an address, which the application supplies.
func NewService ¶
func NewService(recipients contracts.RecipientLookup) *Service
NewService returns the service. module.go constructs it.
func (*Service) ListFor ¶
func (s *Service) ListFor(_ context.Context, tx db.Tx[db.Tenant], recipient uuid.UUID, q crud.Query) ([]*contracts.Notification, int64, error)
ListFor is a page of one person's notifications. The recipient filter is set here rather than taken from the caller's query, so there is no shape of Query that lists somebody else's rows; beyond that it is an ordinary crud.List, which is what gives it the paging, ordering and field checking every other list in the application has.
func (*Service) MarkRead ¶
func (s *Service) MarkRead(ctx context.Context, tx db.Tx[db.Tenant], id, recipient uuid.UUID) (*contracts.Notification, error)
MarkRead records that the recipient has seen it. A row belonging to somebody else is ErrNotFound rather than a refusal, which is what lets the route be SignedIn: any caller may ask, and the only thing they learn about somebody else's notification is that they do not have one with that id. Marking it read again changes nothing and publishes nothing.
func (*Service) Notify ¶
func (s *Service) Notify(ctx context.Context, tx db.Tx[db.Tenant], n contracts.Notice) (*contracts.Notification, error)
Notify writes the row and says so. When the notice asks for mail and the recipient has an address it publishes the request rather than sending anything (see contracts.EventEmailRequested); a recipient the lookup cannot find, or one with no address, is not an error, because refusing the whole call would mean somebody without an email address could not be told anything.