ultisuite-backend/internal/api/mail/drive_save.go
R3D347HR4Y bd7534658a Refactor and enhance unified frontend and API features
- Updated environment configuration to unify frontend for mail and drive under a single service.
- Revised README to reflect changes in frontend setup and routing for the unified application.
- Introduced new API documentation endpoints for better accessibility of API specifications.
- Enhanced drive and mail services with improved handling of file uploads and metadata enrichment.
- Implemented new API token management features, including creation, listing, and revocation of tokens.
- Added tests for new functionalities in drive and mail services to ensure reliability and correctness.
2026-06-07 15:44:30 +02:00

134 lines
3.3 KiB
Go

package mail
import (
"context"
"errors"
"github.com/jackc/pgx/v5"
"github.com/ultisuite/ulti-backend/internal/nextcloud"
)
const driveSourceUltimail = "ultimail"
func (s *Service) SetDriveUploader(uploader DriveUploader) {
s.driveUploader = uploader
}
func (s *Service) SaveAttachmentToDrive(
ctx context.Context,
externalID, email, sub, displayName, messageID, attachmentID, folderPath string,
) (string, error) {
if s.driveUploader == nil {
return "", ErrDriveUnavailable
}
if s.storage == nil {
return "", errors.New("object storage unavailable")
}
folderPath = normalizeDriveFolder(folderPath)
userID, err := s.ensureMessageOwned(ctx, externalID, messageID)
if err != nil {
return "", err
}
var (
filename string
contentType string
size int64
s3Key string
existing string
)
err = s.db.QueryRow(ctx, `
SELECT a.filename, a.content_type, a.size, a.s3_key, COALESCE(a.drive_path, '')
FROM attachments a
WHERE a.id = $1 AND a.message_id = $2
`, attachmentID, messageID).Scan(&filename, &contentType, &size, &s3Key, &existing)
if err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return "", ErrAttachmentNotFound
}
return "", err
}
if existing != "" {
return existing, nil
}
if folderPath != "/" {
if err := s.driveUploader.EnsureNextcloudFolder(ctx, email, sub, displayName, folderPath); err != nil {
return "", err
}
}
destPath, err := uniqueDriveFilePath(ctx, s.driveUploader, email, sub, displayName, folderPath, filename)
if err != nil {
return "", err
}
obj, err := s.storage.Get(ctx, s3Key)
if err != nil {
return "", err
}
defer obj.Close()
destPath = nextcloud.NormalizeClientPath(destPath)
if err := s.driveUploader.UploadFile(ctx, email, sub, displayName, destPath, obj, contentType, size); err != nil {
return "", err
}
s.driveUploader.NotifyFileChanged(externalID, destPath)
if err := s.recordDriveFileSource(ctx, userID, destPath, driveSourceUltimail); err != nil {
return "", err
}
_, err = s.db.Exec(ctx, `
UPDATE attachments SET drive_path = $1 WHERE id = $2 AND message_id = $3
`, destPath, attachmentID, messageID)
if err != nil {
return "", err
}
return destPath, nil
}
func (s *Service) SaveMessageAttachmentsToDrive(
ctx context.Context,
externalID, email, sub, displayName, messageID, folderPath string,
) ([]map[string]any, error) {
list, err := s.ListMessageAttachments(ctx, externalID, messageID)
if err != nil {
return nil, err
}
out := make([]map[string]any, 0, len(list))
for _, att := range list {
if att["is_inline"] == true {
continue
}
id, _ := att["id"].(string)
if id == "" {
continue
}
drivePath, err := s.SaveAttachmentToDrive(ctx, externalID, email, sub, displayName, messageID, id, folderPath)
if err != nil {
return nil, err
}
entry := make(map[string]any, len(att)+1)
for k, v := range att {
entry[k] = v
}
entry["drive_path"] = drivePath
out = append(out, entry)
}
return out, nil
}
func (s *Service) recordDriveFileSource(ctx context.Context, userID, filePath, source string) error {
filePath = nextcloud.NormalizeClientPath(filePath)
_, err := s.db.Exec(ctx, `
INSERT INTO drive_file_sources (user_id, file_path, source)
VALUES ($1, $2, $3)
ON CONFLICT (user_id, file_path) DO UPDATE SET source = EXCLUDED.source
`, userID, filePath, source)
return err
}