package documentupload import ( "context" "io" "mime" "path/filepath" "time" "queryorchestration/internal/database/repository" "queryorchestration/internal/serviceconfig/objectstore" "github.com/aws/aws-sdk-go-v2/service/s3" "github.com/google/uuid" "github.com/jackc/pgx/v5/pgtype" ) type File struct { ClientID string Content io.Reader BatchID *uuid.UUID // Optional batch ID for batch processing Filename string // Original filename for MIME type detection } // detectContentType determines the MIME type based on file extension func detectContentType(filename string) string { // Use Go's standard library to detect MIME type from extension contentType := mime.TypeByExtension(filepath.Ext(filename)) if contentType == "" { // Default to binary if extension is unknown return "application/octet-stream" } return contentType } func (s *Service) Upload(ctx context.Context, file File) error { _, err := s.UploadAndReturnKey(ctx, file) return err } // UploadAndReturnKey uploads a file and returns the BucketKey used func (s *Service) UploadAndReturnKey(ctx context.Context, file File) (objectstore.BucketKey, error) { var key objectstore.BucketKey err := s.cfg.ExecuteDBTransaction(ctx, func(ctx context.Context, q *repository.Queries) error { now := time.Now().UTC() part, err := s.cfg.GetDBQueries().GetDocumentUploadCurrentPart(ctx, &repository.GetDocumentUploadCurrentPartParams{ Clientid: &file.ClientID, Querydate: pgtype.Timestamptz{ Valid: true, Time: now, }, }) if err != nil { return err } uploadId := uuid.New() newPart := s.cfg.GetDirectoryPart(part.Part, part.Count) key = objectstore.BucketKey{ ClientID: file.ClientID, Location: objectstore.Import, CreatedAt: now, EntityID: uploadId, Part: &newPart, BatchID: file.BatchID, } keyStr := key.String() bucket := s.cfg.GetBucket() err = q.AddDocumentUpload(ctx, &repository.AddDocumentUploadParams{ ID: uploadId, Clientid: file.ClientID, Bucket: bucket, Key: keyStr, Part: newPart, Createdat: pgtype.Timestamp{ Valid: true, Time: now, }, Filename: &file.Filename, BatchID: file.BatchID, }) if err != nil { return err } // Detect content type from original filename contentType := detectContentType(file.Filename) // Prepare metadata for S3 object metadata := make(map[string]string) if file.Filename != "" { metadata["original-filename"] = file.Filename } _, err = s.cfg.GetStoreClient().PutObject(ctx, &s3.PutObjectInput{ Bucket: &bucket, Key: &keyStr, Body: file.Content, ContentType: &contentType, Metadata: metadata, }) if err != nil { return err } return nil }) return key, err }