- 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.
134 lines
3.3 KiB
Go
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
|
|
}
|