Add delete account endpoint and file encryption at rest
Delete Account (Plan #2): - DELETE /api/auth/account/ with password or "DELETE" confirmation - Cascade delete across 15+ tables in correct FK order - Auth provider detection (email/apple/google) for /auth/me/ - File cleanup after account deletion - Handler + repository tests (12 tests) Encryption at Rest (Plan #3): - AES-256-GCM envelope encryption (per-file DEK wrapped by KEK) - Encrypt on upload, auto-decrypt on serve via StorageService.ReadFile() - MediaHandler serves decrypted files via c.Blob() - TaskService email image loading uses ReadFile() - cmd/migrate-encrypt CLI tool with --dry-run for existing files - Encryption service + storage service tests (18 tests)
This commit is contained in:
@@ -4,7 +4,6 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -848,39 +847,33 @@ func (s *TaskService) sendTaskCompletedNotification(task *models.Task, completio
|
||||
}
|
||||
}
|
||||
|
||||
// loadCompletionImagesForEmail reads completion images from disk and prepares them for email embedding
|
||||
// loadCompletionImagesForEmail reads completion images from disk and prepares them for email embedding.
|
||||
// Uses StorageService.ReadFile to transparently handle encrypted files.
|
||||
func (s *TaskService) loadCompletionImagesForEmail(images []models.TaskCompletionImage) []EmbeddedImage {
|
||||
var emailImages []EmbeddedImage
|
||||
|
||||
uploadDir := s.storageService.GetUploadDir()
|
||||
|
||||
for i, img := range images {
|
||||
// Resolve file path from stored URL
|
||||
filePath := s.resolveImageFilePath(img.ImageURL, uploadDir)
|
||||
if filePath == "" {
|
||||
log.Warn().Str("image_url", img.ImageURL).Msg("Could not resolve image file path")
|
||||
continue
|
||||
}
|
||||
|
||||
// Read file from disk
|
||||
data, err := os.ReadFile(filePath)
|
||||
// Read file via storage service (handles encryption transparently)
|
||||
data, mimeType, err := s.storageService.ReadFile(img.ImageURL)
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Str("path", filePath).Msg("Failed to read completion image for email")
|
||||
log.Warn().Err(err).Str("image_url", img.ImageURL).Msg("Failed to read completion image for email")
|
||||
continue
|
||||
}
|
||||
|
||||
// Determine content type from extension
|
||||
contentType := s.getContentTypeFromPath(filePath)
|
||||
// Use detected MIME type, fall back to extension-based detection
|
||||
if mimeType == "application/octet-stream" {
|
||||
mimeType = s.getContentTypeFromPath(img.ImageURL)
|
||||
}
|
||||
|
||||
// Create embedded image with unique Content-ID
|
||||
emailImages = append(emailImages, EmbeddedImage{
|
||||
ContentID: fmt.Sprintf("completion-image-%d", i+1),
|
||||
Filename: filepath.Base(filePath),
|
||||
ContentType: contentType,
|
||||
Filename: filepath.Base(img.ImageURL),
|
||||
ContentType: mimeType,
|
||||
Data: data,
|
||||
})
|
||||
|
||||
log.Debug().Str("path", filePath).Int("size", len(data)).Msg("Loaded completion image for email")
|
||||
log.Debug().Str("image_url", img.ImageURL).Int("size", len(data)).Msg("Loaded completion image for email")
|
||||
}
|
||||
|
||||
return emailImages
|
||||
|
||||
Reference in New Issue
Block a user