tender single document download
This commit is contained in:
@@ -6,6 +6,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"mime"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
@@ -53,6 +54,7 @@ type Service interface {
|
||||
GetDocumentSummaries(ctx context.Context, id string) ([]DocumentSummary, error)
|
||||
GetDocuments(ctx context.Context, id string) (*TenderDocumentsResponse, error)
|
||||
DownloadDocuments(ctx context.Context, id string) (*TenderDocumentDownload, error)
|
||||
DownloadDocument(ctx context.Context, id, filename string) (*TenderDocumentDownload, error)
|
||||
|
||||
// AI summarization operations
|
||||
GetAISummary(ctx context.Context, id string) (*AISummaryResponse, error)
|
||||
@@ -624,6 +626,96 @@ func (s *tenderService) DownloadDocuments(ctx context.Context, id string) (*Tend
|
||||
}, nil
|
||||
}
|
||||
|
||||
// DownloadDocument streams a single scraped document for a tender.
|
||||
func (s *tenderService) DownloadDocument(ctx context.Context, id, filename string) (*TenderDocumentDownload, error) {
|
||||
if id == "" {
|
||||
return nil, fmt.Errorf("tender ID cannot be empty")
|
||||
}
|
||||
if strings.TrimSpace(filename) == "" {
|
||||
return nil, fmt.Errorf("document filename cannot be empty")
|
||||
}
|
||||
if s.aiSummarizerStorage == nil {
|
||||
return nil, ErrTenderDocumentStorageUnavailable
|
||||
}
|
||||
|
||||
tender, err := s.repository.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to get tender for document download", map[string]interface{}{
|
||||
"tender_id": id,
|
||||
"filename": filename,
|
||||
"error": err.Error(),
|
||||
})
|
||||
return nil, fmt.Errorf("failed to get tender: %w", err)
|
||||
}
|
||||
if tender == nil {
|
||||
return nil, fmt.Errorf("tender not found")
|
||||
}
|
||||
if strings.TrimSpace(tender.ContractFolderID) == "" {
|
||||
return nil, ErrTenderDocumentNotFound
|
||||
}
|
||||
|
||||
documents, err := s.listDocumentsForTender(ctx, tender)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to list tender documents for download", map[string]interface{}{
|
||||
"tender_id": id,
|
||||
"filename": filename,
|
||||
"contract_folder_id": tender.ContractFolderID,
|
||||
"error": err.Error(),
|
||||
})
|
||||
return nil, fmt.Errorf("failed to list tender documents: %w", err)
|
||||
}
|
||||
|
||||
doc, ok := findStoredDocumentByFilename(documents, filename)
|
||||
if !ok {
|
||||
return nil, ErrTenderDocumentNotFound
|
||||
}
|
||||
|
||||
reader, err := s.aiSummarizerStorage.DownloadDocument(ctx, doc.ObjectName)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to open tender document from storage", map[string]interface{}{
|
||||
"tender_id": id,
|
||||
"filename": filename,
|
||||
"object_name": doc.ObjectName,
|
||||
"error": err.Error(),
|
||||
})
|
||||
return nil, fmt.Errorf("failed to download document: %w", err)
|
||||
}
|
||||
|
||||
downloadName := path.Base(doc.DocumentName)
|
||||
contentType := mime.TypeByExtension(filepath.Ext(downloadName))
|
||||
if contentType == "" {
|
||||
contentType = "application/octet-stream"
|
||||
}
|
||||
|
||||
s.logger.Info("Tender document download prepared", map[string]interface{}{
|
||||
"tender_id": id,
|
||||
"filename": downloadName,
|
||||
"size": doc.Size,
|
||||
})
|
||||
|
||||
return &TenderDocumentDownload{
|
||||
Reader: reader,
|
||||
Filename: downloadName,
|
||||
ContentType: contentType,
|
||||
Size: doc.Size,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func findStoredDocumentByFilename(documents []ai_summarizer.StoredDocument, filename string) (ai_summarizer.StoredDocument, bool) {
|
||||
filename = strings.TrimSpace(filename)
|
||||
if filename == "" {
|
||||
return ai_summarizer.StoredDocument{}, false
|
||||
}
|
||||
|
||||
wantBase := path.Base(filename)
|
||||
for _, doc := range documents {
|
||||
if doc.DocumentName == filename || path.Base(doc.DocumentName) == wantBase {
|
||||
return doc, true
|
||||
}
|
||||
}
|
||||
return ai_summarizer.StoredDocument{}, false
|
||||
}
|
||||
|
||||
func (s *tenderService) writeDocumentsArchive(ctx context.Context, writer *io.PipeWriter, documents []ai_summarizer.StoredDocument) {
|
||||
zipWriter := zip.NewWriter(writer)
|
||||
usedNames := make(map[string]int, len(documents))
|
||||
|
||||
Reference in New Issue
Block a user