5fbfcd4149
continuous-integration/drone/push Build is passing
- Updated the `UploadDocuments` method to sanitize document file IDs before saving, ensuring only valid references are stored. - Introduced `DetachDocumentFileID` method in the `companyService` to remove file IDs from all companies referencing a deleted file, improving data integrity. - Enhanced the `companyRepository` with a new method to handle the removal of document file IDs from the database. - Updated the `filestore` handler to utilize the new detachment functionality when files are deleted, ensuring consistent state across domain entities. This update improves the management of document file IDs within the company domain, enhancing data integrity and reference handling.
166 lines
4.5 KiB
Go
166 lines
4.5 KiB
Go
package company
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"mime/multipart"
|
|
|
|
"tm/pkg/filestore"
|
|
)
|
|
|
|
const (
|
|
companyDocumentCategory = "company_document"
|
|
maxCompanyDocumentsUpload = 20
|
|
)
|
|
|
|
// UploadDocumentsResponse is returned after uploading company documents.
|
|
type UploadDocumentsResponse struct {
|
|
DocumentFileIDs []string `json:"document_file_ids"`
|
|
Uploaded []filestore.UploadedFileResult `json:"uploaded"`
|
|
Failed []filestore.FailedUploadResult `json:"failed,omitempty"`
|
|
}
|
|
|
|
// UploadDocuments uploads one or more files and appends their IDs to the company.
|
|
func (s *companyService) UploadDocuments(ctx context.Context, companyID, uploadedBy string, files []*multipart.FileHeader) (*UploadDocumentsResponse, error) {
|
|
if s.fileStore == nil {
|
|
return nil, errors.New("file storage is not configured")
|
|
}
|
|
if len(files) == 0 {
|
|
return nil, errors.New("no files provided")
|
|
}
|
|
if len(files) > maxCompanyDocumentsUpload {
|
|
return nil, errors.New("too many files in request")
|
|
}
|
|
|
|
company, err := s.repository.GetByID(ctx, companyID)
|
|
if err != nil {
|
|
return nil, errors.New("company not found")
|
|
}
|
|
|
|
opts := filestore.UploadOptions{
|
|
UploadedBy: uploadedBy,
|
|
Category: companyDocumentCategory,
|
|
MaxSize: filestore.MaxUploadSize,
|
|
Extra: map[string]string{
|
|
"company_id": companyID,
|
|
},
|
|
}
|
|
|
|
uploaded := make([]filestore.UploadedFileResult, 0, len(files))
|
|
failed := make([]filestore.FailedUploadResult, 0)
|
|
newIDs := make([]string, 0, len(files))
|
|
|
|
for _, file := range files {
|
|
result, uploadErr := filestore.UploadFromMultipartFile(s.fileStore, file, opts)
|
|
if uploadErr != nil {
|
|
code := filestore.ErrCodeUploadFailed
|
|
var fsErr *filestore.FileStoreError
|
|
if errors.As(uploadErr, &fsErr) {
|
|
code = fsErr.Code
|
|
}
|
|
failed = append(failed, filestore.FailedUploadResult{
|
|
Filename: file.Filename,
|
|
Error: uploadErr.Error(),
|
|
Code: code,
|
|
})
|
|
continue
|
|
}
|
|
uploaded = append(uploaded, result)
|
|
newIDs = append(newIDs, result.FileID)
|
|
}
|
|
|
|
if len(uploaded) == 0 {
|
|
s.logger.Warn("Company document upload failed for all files", map[string]interface{}{
|
|
"company_id": companyID,
|
|
"file_count": len(files),
|
|
})
|
|
return &UploadDocumentsResponse{
|
|
DocumentFileIDs: s.sanitizeDocumentFileIDs(company.DocumentFileIDs),
|
|
Uploaded: uploaded,
|
|
Failed: failed,
|
|
}, errors.New("all file uploads failed")
|
|
}
|
|
|
|
company.DocumentFileIDs = s.sanitizeDocumentFileIDs(company.DocumentFileIDs)
|
|
company.DocumentFileIDs = mergeDocumentFileIDs(company.DocumentFileIDs, newIDs)
|
|
if err := s.repository.Update(ctx, company); err != nil {
|
|
s.logger.Error("Failed to save company documents", map[string]interface{}{
|
|
"error": err.Error(),
|
|
"company_id": companyID,
|
|
})
|
|
return nil, errors.New("failed to update company documents")
|
|
}
|
|
|
|
s.logger.Info("Company documents uploaded", map[string]interface{}{
|
|
"company_id": companyID,
|
|
"uploaded_count": len(uploaded),
|
|
"failed_count": len(failed),
|
|
"documents_total": len(company.DocumentFileIDs),
|
|
})
|
|
|
|
return &UploadDocumentsResponse{
|
|
DocumentFileIDs: company.DocumentFileIDs,
|
|
Uploaded: uploaded,
|
|
Failed: failed,
|
|
}, nil
|
|
}
|
|
|
|
func mergeDocumentFileIDs(existing, additional []string) []string {
|
|
seen := make(map[string]struct{}, len(existing)+len(additional))
|
|
merged := make([]string, 0, len(existing)+len(additional))
|
|
|
|
appendUnique := func(ids []string) {
|
|
for _, id := range ids {
|
|
if id == "" {
|
|
continue
|
|
}
|
|
if _, ok := seen[id]; ok {
|
|
continue
|
|
}
|
|
seen[id] = struct{}{}
|
|
merged = append(merged, id)
|
|
}
|
|
}
|
|
|
|
appendUnique(existing)
|
|
appendUnique(additional)
|
|
return merged
|
|
}
|
|
|
|
// DetachDocumentFileID removes a file ID from every company that still references it.
|
|
func (s *companyService) DetachDocumentFileID(ctx context.Context, fileID string) error {
|
|
if err := s.repository.RemoveDocumentFileID(ctx, fileID); err != nil {
|
|
s.logger.Error("Failed to detach document file ID from companies", map[string]interface{}{
|
|
"error": err.Error(),
|
|
"file_id": fileID,
|
|
})
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *companyService) sanitizeDocumentFileIDs(ids []string) []string {
|
|
if len(ids) == 0 || s.fileStore == nil {
|
|
return ids
|
|
}
|
|
|
|
sanitized := make([]string, 0, len(ids))
|
|
for _, id := range ids {
|
|
if id == "" {
|
|
continue
|
|
}
|
|
exists, err := s.fileStore.Exists(id)
|
|
if err != nil {
|
|
s.logger.Warn("Failed to verify company document file", map[string]interface{}{
|
|
"error": err.Error(),
|
|
"file_id": id,
|
|
})
|
|
continue
|
|
}
|
|
if exists {
|
|
sanitized = append(sanitized, id)
|
|
}
|
|
}
|
|
return sanitized
|
|
}
|