Skip to content

feat(coderd): add new dispatch logic for coder inbox #16764

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 22 commits into from
Mar 5, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
add coder inbox delivery target
  • Loading branch information
defelmnq committed Mar 3, 2025
commit 95738133a37c6e263e3d6e39bdcacb96a9db2bde
81 changes: 81 additions & 0 deletions coderd/notifications/dispatch/inbox.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package dispatch

import (
"context"
"encoding/json"
"text/template"

"golang.org/x/xerrors"

"cdr.dev/slog"

"github.com/google/uuid"

"github.com/coder/coder/v2/coderd/database"
"github.com/coder/coder/v2/coderd/database/dbtime"
"github.com/coder/coder/v2/coderd/notifications/types"
markdown "github.com/coder/coder/v2/coderd/render"
)

type inboxStore interface {
InsertInboxNotification(ctx context.Context, arg database.InsertInboxNotificationParams) (database.InboxNotification, error)
}

// InboxHandler is responsible for dispatching notification messages in the Coder Inbox.
type InboxHandler struct {
log slog.Logger
store inboxStore
}

func NewInboxHandler(log slog.Logger, store inboxStore) *InboxHandler {
return &InboxHandler{log: log, store: store}
}

func (s *InboxHandler) Dispatcher(payload types.MessagePayload, titleTmpl, bodyTmpl string, _ template.FuncMap) (DeliveryFunc, error) {
subject, err := markdown.PlaintextFromMarkdown(titleTmpl)
if err != nil {
return nil, xerrors.Errorf("render subject: %w", err)
}

htmlBody, err := markdown.PlaintextFromMarkdown(bodyTmpl)
if err != nil {
return nil, xerrors.Errorf("render html body: %w", err)
}

return s.dispatch(payload, subject, htmlBody), nil
}

func (s *InboxHandler) dispatch(payload types.MessagePayload, title, body string) DeliveryFunc {
return func(ctx context.Context, msgID uuid.UUID) (bool, error) {
userID, err := uuid.Parse(payload.UserID)
if err != nil {
return false, xerrors.Errorf("parse user ID: %w", err)
}
templateID, err := uuid.Parse(payload.NotificationTemplateID)
if err != nil {
return false, xerrors.Errorf("parse template ID: %w", err)
}

actions, err := json.Marshal(payload.Actions)
if err != nil {
return false, xerrors.Errorf("marshal actions: %w", err)
}

_, err = s.store.InsertInboxNotification(ctx, database.InsertInboxNotificationParams{
ID: msgID,
UserID: userID,
TemplateID: templateID,
Targets: payload.Targets,
Title: title,
Content: body,
Icon: "https://cdn.coder.com/icons/coder-icon-512x512.png",
Actions: actions,
CreatedAt: dbtime.Now(),
})
if err != nil {
return false, xerrors.Errorf("insert inbox notification: %w", err)
}

return false, nil
}
}
5 changes: 3 additions & 2 deletions coderd/notifications/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ func NewManager(cfg codersdk.NotificationsConfig, store Store, helpers template.
stop: make(chan any),
done: make(chan any),

handlers: defaultHandlers(cfg, log),
handlers: defaultHandlers(cfg, log, store),
helpers: helpers,

clock: quartz.NewReal(),
Expand All @@ -121,10 +121,11 @@ func NewManager(cfg codersdk.NotificationsConfig, store Store, helpers template.
}

// defaultHandlers builds a set of known handlers; panics if any error occurs as these handlers should be valid at compile time.
func defaultHandlers(cfg codersdk.NotificationsConfig, log slog.Logger) map[database.NotificationMethod]Handler {
func defaultHandlers(cfg codersdk.NotificationsConfig, log slog.Logger, store Store) map[database.NotificationMethod]Handler {
return map[database.NotificationMethod]Handler{
database.NotificationMethodSmtp: dispatch.NewSMTPHandler(cfg.SMTP, log.Named("dispatcher.smtp")),
database.NotificationMethodWebhook: dispatch.NewWebhookHandler(cfg.Webhook, log.Named("dispatcher.webhook")),
database.NotificationMethodInbox: dispatch.NewInboxHandler(log.Named("dispatcher.inbox"), store),
}
}

Expand Down
2 changes: 2 additions & 0 deletions coderd/notifications/spec.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ type Store interface {
GetNotificationsSettings(ctx context.Context) (string, error)
GetApplicationName(ctx context.Context) (string, error)
GetLogoURL(ctx context.Context) (string, error)

InsertInboxNotification(ctx context.Context, arg database.InsertInboxNotificationParams) (database.InboxNotification, error)
}

// Handler is responsible for preparing and delivering a notification by a given method.
Expand Down
3 changes: 3 additions & 0 deletions coderd/notifications/types/payload.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package types

import "github.com/google/uuid"

// MessagePayload describes the JSON payload to be stored alongside the notification message, which specifies all of its
// metadata, labels, and routing information.
//
Expand All @@ -18,4 +20,5 @@ type MessagePayload struct {
Actions []TemplateAction `json:"actions"`
Labels map[string]string `json:"labels"`
Data map[string]any `json:"data"`
Targets []uuid.UUID `json:"targets"`
}
Loading