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:
fabio
2026-07-02 10:39:53 +02:00
parent 2608ce6e60
commit 167be9b0b3
94 changed files with 11696 additions and 1 deletions

BIN
mails/.DS_Store vendored Normal file

Binary file not shown.

98
mails/mails.go Normal file
View 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
}

3
mails/outbox/.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
*.json
*.html
!.gitkeep

1
mails/outbox/.gitkeep Normal file
View File

@@ -0,0 +1 @@