add middlewares, handlers and database models

This commit is contained in:
juancwu 2025-12-16 10:46:34 -05:00
commit 7e288ea67a
24 changed files with 1045 additions and 14 deletions

View file

@ -14,6 +14,12 @@ var (
ErrInvalidCredentials = errors.New("invalid email or password")
ErrNoPassword = errors.New("account uses passwordless login. Use magic link")
ErrPasswordsDoNotMatch = errors.New("passwords do not match")
ErrEmailAlreadyExists = errors.New("email already exists")
ErrWeakPassword = errors.New("password must be at least 12 characters")
ErrCommonPassword = errors.New("password is too common, please choose a stronger one")
ErrEmailNotVerified = errors.New("email not verified")
ErrInvalidEmail = errors.New("invalid email address")
ErrNameRequired = errors.New("name is required")
)
type AuthService struct {

View file

@ -2,6 +2,7 @@ package service
import (
"context"
"fmt"
"log/slog"
"github.com/resend/resend-go/v2"
@ -10,12 +11,16 @@ import (
type EmailParams struct {
From string
To []string
Bcc []string
Cc []string
ReplyTo string
Subject string
Text string
Html string
}
type EmailClient interface {
SendWithContext(ctx context.Context, params EmailParams) (string, error)
SendWithContext(ctx context.Context, params *EmailParams) (string, error)
}
type ResendClient struct {
@ -33,12 +38,16 @@ func NewResendClient(apiKey string) *ResendClient {
return &ResendClient{client: client}
}
func (c *ResendClient) SendWithContext(ctx context.Context, params EmailParams) (string, error) {
func (c *ResendClient) SendWithContext(ctx context.Context, params *EmailParams) (string, error) {
res, err := c.client.Emails.SendWithContext(ctx, &resend.SendEmailRequest{
From: params.From,
To: params.To,
Bcc: params.Bcc,
Cc: params.Cc,
ReplyTo: params.ReplyTo,
Subject: params.Subject,
Text: params.Text,
Html: params.Html,
})
if err != nil {
return "", err
@ -63,3 +72,45 @@ func NewEmailService(client EmailClient, fromEmail, appURL, appName string, isDe
appName: appName,
}
}
func (s *EmailService) SendMagicLinkEmail(email, token, name string) error {
magicURL := fmt.Sprintf("%s/auth/magic-link/%s", s.appURL, token)
subject, body := magicLinkEmailTemplate(magicURL, s.appName)
if s.isDev {
slog.Info("email sent (dev mode)", "type", "magic_link", "to", email, "subject", subject, "url", magicURL)
return nil
}
if s.client == nil {
return fmt.Errorf("email service not configured (missing RESEND_API_KEY)")
}
params := &EmailParams{
From: s.fromEmail,
To: []string{email},
Subject: subject,
Text: body,
}
_, err := s.client.SendWithContext(context.Background(), params)
if err == nil {
slog.Info("email sent", "type", "magic_link", "to", email)
}
return err
}
func magicLinkEmailTemplate(magicURL, appName string) (string, string) {
subject := fmt.Sprintf("Sign in to %s", appName)
body := fmt.Sprintf(`Click this link to sign in to your account:
%s
This link expires in 10 minutes and can only be used once.
If you didn't request this, ignore this email.
Best,
The %s Team`, magicURL, appName)
return subject, body
}