feat: implement user profile management with avatars, login, and personal data
- Add avatar upload functionality with public URL access. - Implement user login with email and password authentication. - Create endpoints for fetching and updating the authenticated user's profile. - Set up database migrations for user profiles, including email and role management. - Introduce personal data management linked to user profiles. - Add email verification process with welcome email templates.
This commit is contained in:
98
mails/mails.go
Normal file
98
mails/mails.go
Normal file
@@ -0,0 +1,98 @@
|
||||
// Service mails handles transactional email delivery.
|
||||
package mails
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"encore.dev/beta/errs"
|
||||
"encore.dev/types/uuid"
|
||||
)
|
||||
|
||||
const outboxDir = "mails/outbox"
|
||||
|
||||
type TransactionalMailParams struct {
|
||||
To string `json:"to"`
|
||||
Subject string `json:"subject"`
|
||||
TextBody string `json:"text_body"`
|
||||
HTMLBody string `json:"html_body"`
|
||||
Metadata map[string]string `json:"metadata"`
|
||||
}
|
||||
|
||||
type TransactionalMail struct {
|
||||
ID uuid.UUID `json:"id"`
|
||||
To string `json:"to"`
|
||||
Subject string `json:"subject"`
|
||||
TextBody string `json:"text_body"`
|
||||
HTMLBody string `json:"html_body"`
|
||||
Metadata map[string]string `json:"metadata"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
// SendTransactional records a transactional email in the local outbox.
|
||||
// TODO: replace the file-backed outbox with an SMTP sender.
|
||||
//
|
||||
//encore:api private method=POST path=/mails/transactional
|
||||
func SendTransactional(ctx context.Context, p *TransactionalMailParams) (*TransactionalMail, error) {
|
||||
if strings.TrimSpace(p.To) == "" {
|
||||
return nil, &errs.Error{Code: errs.InvalidArgument, Message: "recipient email is required"}
|
||||
}
|
||||
if strings.TrimSpace(p.Subject) == "" {
|
||||
return nil, &errs.Error{Code: errs.InvalidArgument, Message: "email subject is required"}
|
||||
}
|
||||
|
||||
id, err := uuid.NewV4()
|
||||
if err != nil {
|
||||
return nil, errs.WrapCode(err, errs.Internal, "failed to generate mail id")
|
||||
}
|
||||
|
||||
mail := TransactionalMail{
|
||||
ID: id,
|
||||
To: p.To,
|
||||
Subject: p.Subject,
|
||||
TextBody: p.TextBody,
|
||||
HTMLBody: p.HTMLBody,
|
||||
Metadata: p.Metadata,
|
||||
CreatedAt: time.Now().UTC(),
|
||||
}
|
||||
if mail.Metadata == nil {
|
||||
mail.Metadata = map[string]string{}
|
||||
}
|
||||
|
||||
if err := saveToOutbox(ctx, &mail); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &mail, nil
|
||||
}
|
||||
|
||||
func saveToOutbox(ctx context.Context, mail *TransactionalMail) error {
|
||||
if err := ctx.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.MkdirAll(outboxDir, 0o755); err != nil {
|
||||
return errs.WrapCode(err, errs.Internal, "failed to create mail outbox")
|
||||
}
|
||||
|
||||
payload, err := json.MarshalIndent(mail, "", " ")
|
||||
if err != nil {
|
||||
return errs.WrapCode(err, errs.Internal, "failed to encode mail")
|
||||
}
|
||||
|
||||
baseFilename := mail.CreatedAt.Format("20060102T150405Z") + "_" + mail.ID.String()
|
||||
jsonPath := filepath.Join(outboxDir, baseFilename+".json")
|
||||
if err := os.WriteFile(jsonPath, payload, 0o644); err != nil {
|
||||
return errs.WrapCode(err, errs.Internal, "failed to write mail to outbox")
|
||||
}
|
||||
|
||||
if strings.TrimSpace(mail.HTMLBody) != "" {
|
||||
htmlPath := filepath.Join(outboxDir, baseFilename+".html")
|
||||
if err := os.WriteFile(htmlPath, []byte(mail.HTMLBody), 0o644); err != nil {
|
||||
return errs.WrapCode(err, errs.Internal, "failed to write mail html to outbox")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user