diff --git a/internal/api/handlers_me.go b/internal/api/handlers_me.go index 15aa0bb..fefd041 100644 --- a/internal/api/handlers_me.go +++ b/internal/api/handlers_me.go @@ -533,7 +533,7 @@ func (h *meHandler) DeleteAccount(w http.ResponseWriter, r *http.Request) { writeError(w, http.StatusInternalServerError, "db_error", "failed to start transaction") return } - defer tx.Rollback(ctx) + defer func() { _ = tx.Rollback(ctx) }() qtx := h.db.WithTx(tx) diff --git a/internal/api/helpers_ws.go b/internal/api/helpers_ws.go index ec1c126..8488cbd 100644 --- a/internal/api/helpers_ws.go +++ b/internal/api/helpers_ws.go @@ -43,14 +43,14 @@ type wsAuthMsg struct { // authenticated context. The caller must send this as the first message after // connecting. func wsAuthenticate(ctx context.Context, conn *websocket.Conn, jwtSecret []byte, queries *db.Queries) (auth.AuthContext, error) { - conn.SetReadDeadline(time.Now().Add(5 * time.Second)) + _ = conn.SetReadDeadline(time.Now().Add(5 * time.Second)) var msg wsAuthMsg if err := conn.ReadJSON(&msg); err != nil { return auth.AuthContext{}, fmt.Errorf("read auth message: %w", err) } - conn.SetReadDeadline(time.Time{}) // clear deadline + _ = conn.SetReadDeadline(time.Time{}) // clear deadline if msg.Type != "auth" || msg.Token == "" { return auth.AuthContext{}, fmt.Errorf("first message must be type 'auth' with a token") diff --git a/internal/api/middleware_auth.go b/internal/api/middleware_auth.go index b671047..580c8c0 100644 --- a/internal/api/middleware_auth.go +++ b/internal/api/middleware_auth.go @@ -96,4 +96,4 @@ func requireAPIKeyOrJWT(queries *db.Queries, jwtSecret []byte) func(http.Handler writeError(w, http.StatusUnauthorized, "unauthorized", "X-API-Key or Authorization: Bearer required") }) } -} \ No newline at end of file +} diff --git a/internal/email/email.go b/internal/email/email.go index 89482e0..d9a13fd 100644 --- a/internal/email/email.go +++ b/internal/email/email.go @@ -20,6 +20,8 @@ import ( "net/url" "strconv" "strings" + + emailtypes "git.omukk.dev/wrenn/wrenn/pkg/email" ) // Config holds SMTP connection credentials. All fields except Host are @@ -32,26 +34,10 @@ type Config struct { FromEmail string // envelope sender address } -// Mailer sends transactional emails. -type Mailer interface { - Send(ctx context.Context, to string, subject string, data EmailData) error -} - -// EmailData is the generic payload for all transactional emails. -// Templates conditionally render each field based on presence. -type EmailData struct { - RecipientName string // optional — used after "Hello" - Message string // main body (plain text; HTML template wraps it) - Button *Button // optional CTA button - Closing string // optional closing/footer message -} - -// Button represents a call-to-action link rendered as a button in HTML -// and as a plain URL in the text variant. -type Button struct { - Text string // button label - URL string // target URL -} +// Re-export public types so existing internal/ callers don't need to change imports. +type Mailer = emailtypes.Mailer +type EmailData = emailtypes.EmailData +type Button = emailtypes.Button // New constructs a Mailer. If cfg.Host is empty, returns a no-op mailer // that logs at debug level and discards. Panics if templates fail to parse diff --git a/pkg/cpextension/extension.go b/pkg/cpextension/extension.go index b2065f2..fcbdca4 100644 --- a/pkg/cpextension/extension.go +++ b/pkg/cpextension/extension.go @@ -10,11 +10,11 @@ import ( "github.com/jackc/pgx/v5/pgxpool" "github.com/redis/go-redis/v9" - "git.omukk.dev/wrenn/wrenn/internal/email" "git.omukk.dev/wrenn/wrenn/pkg/audit" "git.omukk.dev/wrenn/wrenn/pkg/auth" "git.omukk.dev/wrenn/wrenn/pkg/config" "git.omukk.dev/wrenn/wrenn/pkg/db" + "git.omukk.dev/wrenn/wrenn/pkg/email" "git.omukk.dev/wrenn/wrenn/pkg/lifecycle" "git.omukk.dev/wrenn/wrenn/pkg/scheduler" ) diff --git a/pkg/email/types.go b/pkg/email/types.go new file mode 100644 index 0000000..ed98e4e --- /dev/null +++ b/pkg/email/types.go @@ -0,0 +1,27 @@ +// Package email defines the public types for transactional email sending. +// The implementation lives in internal/email — this package only exposes +// the interface and data types so the cloud repo can use them via ServerContext. +package email + +import "context" + +// Mailer sends transactional emails. +type Mailer interface { + Send(ctx context.Context, to string, subject string, data EmailData) error +} + +// EmailData is the generic payload for all transactional emails. +// Templates conditionally render each field based on presence. +type EmailData struct { + RecipientName string // optional — used after "Hello" + Message string // main body (plain text; HTML template wraps it) + Button *Button // optional CTA button + Closing string // optional closing/footer message +} + +// Button represents a call-to-action link rendered as a button in HTML +// and as a plain URL in the text variant. +type Button struct { + Text string // button label + URL string // target URL +}