forked from wrenn/wrenn
Merge branch 'main' of git.omukk.dev:wrenn/wrenn into dev
This commit is contained in:
@ -1 +1 @@
|
|||||||
0.1.0
|
0.1.1
|
||||||
|
|||||||
@ -533,7 +533,7 @@ func (h *meHandler) DeleteAccount(w http.ResponseWriter, r *http.Request) {
|
|||||||
writeError(w, http.StatusInternalServerError, "db_error", "failed to start transaction")
|
writeError(w, http.StatusInternalServerError, "db_error", "failed to start transaction")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer tx.Rollback(ctx)
|
defer func() { _ = tx.Rollback(ctx) }()
|
||||||
|
|
||||||
qtx := h.db.WithTx(tx)
|
qtx := h.db.WithTx(tx)
|
||||||
|
|
||||||
|
|||||||
@ -43,14 +43,14 @@ type wsAuthMsg struct {
|
|||||||
// authenticated context. The caller must send this as the first message after
|
// authenticated context. The caller must send this as the first message after
|
||||||
// connecting.
|
// connecting.
|
||||||
func wsAuthenticate(ctx context.Context, conn *websocket.Conn, jwtSecret []byte, queries *db.Queries) (auth.AuthContext, error) {
|
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
|
var msg wsAuthMsg
|
||||||
if err := conn.ReadJSON(&msg); err != nil {
|
if err := conn.ReadJSON(&msg); err != nil {
|
||||||
return auth.AuthContext{}, fmt.Errorf("read auth message: %w", err)
|
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 == "" {
|
if msg.Type != "auth" || msg.Token == "" {
|
||||||
return auth.AuthContext{}, fmt.Errorf("first message must be type 'auth' with a token")
|
return auth.AuthContext{}, fmt.Errorf("first message must be type 'auth' with a token")
|
||||||
|
|||||||
@ -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 <token> required")
|
writeError(w, http.StatusUnauthorized, "unauthorized", "X-API-Key or Authorization: Bearer <token> required")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,6 +20,8 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
emailtypes "git.omukk.dev/wrenn/wrenn/pkg/email"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Config holds SMTP connection credentials. All fields except Host are
|
// Config holds SMTP connection credentials. All fields except Host are
|
||||||
@ -32,26 +34,10 @@ type Config struct {
|
|||||||
FromEmail string // envelope sender address
|
FromEmail string // envelope sender address
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mailer sends transactional emails.
|
// Re-export public types so existing internal/ callers don't need to change imports.
|
||||||
type Mailer interface {
|
type Mailer = emailtypes.Mailer
|
||||||
Send(ctx context.Context, to string, subject string, data EmailData) error
|
type EmailData = emailtypes.EmailData
|
||||||
}
|
type Button = emailtypes.Button
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
||||||
// New constructs a Mailer. If cfg.Host is empty, returns a no-op mailer
|
// 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
|
// that logs at debug level and discards. Panics if templates fail to parse
|
||||||
|
|||||||
@ -10,11 +10,11 @@ import (
|
|||||||
"github.com/jackc/pgx/v5/pgxpool"
|
"github.com/jackc/pgx/v5/pgxpool"
|
||||||
"github.com/redis/go-redis/v9"
|
"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/audit"
|
||||||
"git.omukk.dev/wrenn/wrenn/pkg/auth"
|
"git.omukk.dev/wrenn/wrenn/pkg/auth"
|
||||||
"git.omukk.dev/wrenn/wrenn/pkg/config"
|
"git.omukk.dev/wrenn/wrenn/pkg/config"
|
||||||
"git.omukk.dev/wrenn/wrenn/pkg/db"
|
"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/lifecycle"
|
||||||
"git.omukk.dev/wrenn/wrenn/pkg/scheduler"
|
"git.omukk.dev/wrenn/wrenn/pkg/scheduler"
|
||||||
)
|
)
|
||||||
|
|||||||
27
pkg/email/types.go
Normal file
27
pkg/email/types.go
Normal file
@ -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
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user