package service import ( "context" "path/filepath" "strings" "time" "eslogad-be/internal/contract" "github.com/google/uuid" ) type FileStorage interface { Upload(ctx context.Context, bucket, key string, content []byte, contentType string) (string, error) EnsureBucket(ctx context.Context, bucket string) error } type FileServiceImpl struct { storage FileStorage userProcessor UserProcessor profileBucket string docBucket string } func NewFileService(storage FileStorage, userProcessor UserProcessor, profileBucket, docBucket string) *FileServiceImpl { return &FileServiceImpl{storage: storage, userProcessor: userProcessor, profileBucket: profileBucket, docBucket: docBucket} } func (s *FileServiceImpl) UploadProfileAvatar(ctx context.Context, userID uuid.UUID, filename string, content []byte, contentType string) (string, error) { if err := s.storage.EnsureBucket(ctx, s.profileBucket); err != nil { return "", err } ext := strings.ToLower(strings.TrimPrefix(filepath.Ext(filename), ".")) if ext := mimeExtFromContentType(contentType); ext != "" { ext = ext } key := buildObjectKey("profile", userID, ext) url, err := s.storage.Upload(ctx, s.profileBucket, key, content, contentType) if err != nil { return "", err } _, _ = s.userProcessor.UpdateUserProfile(ctx, userID, &contract.UpdateUserProfileRequest{AvatarURL: &url}) return url, nil } func (s *FileServiceImpl) UploadDocument(ctx context.Context, userID uuid.UUID, filename string, content []byte, contentType string) (string, string, error) { if err := s.storage.EnsureBucket(ctx, s.docBucket); err != nil { return "", "", err } ext := strings.ToLower(strings.TrimPrefix(filepath.Ext(filename), ".")) if ext := mimeExtFromContentType(contentType); ext != "" { ext = ext } key := buildObjectKey("documents", userID, ext) url, err := s.storage.Upload(ctx, s.docBucket, key, content, contentType) if err != nil { return "", "", err } return url, key, nil } func buildObjectKey(prefix string, userID uuid.UUID, ext string) string { now := time.Now().UTC() parts := []string{ prefix, userID.String(), now.Format("2006/01/02"), uuid.New().String(), } key := strings.Join(parts, "/") if ext != "" { key += "." + ext } return key } func mimeExtFromContentType(ct string) string { switch strings.ToLower(ct) { case "image/jpeg", "image/jpg": return "jpg" case "image/png": return "png" case "image/webp": return "webp" case "application/pdf": return "pdf" default: return "" } }