diff --git a/internal/common/service/common.document.service.go b/internal/common/service/common.document.service.go index fe2a41cc..079e3eba 100644 --- a/internal/common/service/common.document.service.go +++ b/internal/common/service/common.document.service.go @@ -8,6 +8,7 @@ import ( "mime/multipart" "path/filepath" "strings" + "time" commonRepo "gitlab.com/mbugroup/lti-api.git/internal/common/repository" "gitlab.com/mbugroup/lti-api.git/internal/config" @@ -29,6 +30,7 @@ type DocumentService interface { DeleteDocuments(ctx context.Context, ids []uint, removeFromStorage bool) error DeleteByTarget(ctx context.Context, documentableType string, documentableID uint64, removeFromStorage bool) error PublicURL(document entity.Document) string + PresignURL(ctx context.Context, document entity.Document, expires time.Duration) (string, error) } type DocumentUploadRequest struct { @@ -293,6 +295,16 @@ func (s *documentService) PublicURL(document entity.Document) string { return s.storage.URL(document.Path) } +func (s *documentService) PresignURL(ctx context.Context, document entity.Document, expires time.Duration) (string, error) { + if s.storage == nil { + return "", errors.New("document storage not configured") + } + if strings.TrimSpace(document.Path) == "" { + return "", errors.New("document path is required") + } + return s.storage.PresignURL(ctx, document.Path, expires) +} + func (s *documentService) generateObjectKey(ext string) (string, error) { normalizedExt := strings.TrimSpace(ext) if normalizedExt != "" && !strings.HasPrefix(normalizedExt, ".") { diff --git a/internal/common/service/common.document.storage.go b/internal/common/service/common.document.storage.go index 24e6fade..42909dbd 100644 --- a/internal/common/service/common.document.storage.go +++ b/internal/common/service/common.document.storage.go @@ -6,6 +6,7 @@ import ( "fmt" "io" "strings" + "time" "github.com/aws/aws-sdk-go-v2/aws" awsconfig "github.com/aws/aws-sdk-go-v2/config" @@ -17,6 +18,7 @@ type DocumentStorage interface { Upload(ctx context.Context, key string, body io.Reader, size int64, contentType string) (DocumentStorageUploadResult, error) Delete(ctx context.Context, key string) error URL(key string) string + PresignURL(ctx context.Context, key string, expires time.Duration) (string, error) } type DocumentStorageUploadResult struct { @@ -36,9 +38,10 @@ type S3DocumentStorageConfig struct { } type s3DocumentStorage struct { - client *s3.Client - bucket string - base string + client *s3.Client + presignClient *s3.PresignClient + bucket string + base string } func NewS3DocumentStorage(ctx context.Context, cfg S3DocumentStorageConfig) (DocumentStorage, error) { @@ -86,6 +89,7 @@ func NewS3DocumentStorage(ctx context.Context, cfg S3DocumentStorageConfig) (Doc client := s3.NewFromConfig(awsCfg, func(o *s3.Options) { o.UsePathStyle = cfg.ForcePathStyle }) + presignClient := s3.NewPresignClient(client) baseURL := strings.TrimSuffix(strings.TrimSpace(cfg.BaseURL), "/") if baseURL == "" { @@ -97,9 +101,10 @@ func NewS3DocumentStorage(ctx context.Context, cfg S3DocumentStorageConfig) (Doc } return &s3DocumentStorage{ - client: client, - bucket: bucket, - base: baseURL, + client: client, + presignClient: presignClient, + bucket: bucket, + base: baseURL, }, nil } @@ -158,3 +163,23 @@ func (s *s3DocumentStorage) URL(key string) string { } return fmt.Sprintf("%s/%s", s.base, key) } + +func (s *s3DocumentStorage) PresignURL(ctx context.Context, key string, expires time.Duration) (string, error) { + key = strings.TrimPrefix(strings.TrimSpace(key), "/") + if key == "" { + return "", errors.New("storage key is required") + } + if expires <= 0 { + expires = 15 * time.Minute + } + + out, err := s.presignClient.PresignGetObject(ctx, &s3.GetObjectInput{ + Bucket: aws.String(s.bucket), + Key: aws.String(key), + }, s3.WithPresignExpires(expires)) + if err != nil { + return "", err + } + + return out.URL, nil +} diff --git a/internal/modules/production/uniformities/controllers/uniformity.controller.go b/internal/modules/production/uniformities/controllers/uniformity.controller.go index 12cc3739..4edf357b 100644 --- a/internal/modules/production/uniformities/controllers/uniformity.controller.go +++ b/internal/modules/production/uniformities/controllers/uniformity.controller.go @@ -71,13 +71,14 @@ func (u *UniformityController) GetOne(c *fiber.Ctx) error { withDetails := c.QueryBool("with_details", false) calculation := service.UniformityCalculation{} var document *entity.Document + var documentURL string var meanWeight float64 if result.MeanUp > 0 { meanWeight = math.Round(result.MeanUp / 1.10) } if withDetails { var err error - calculation, document, err = u.UniformityService.CalculateUniformityFromDocument(c, id) + calculation, document, documentURL, err = u.UniformityService.CalculateUniformityFromDocument(c, id) if err != nil { return err } @@ -111,7 +112,7 @@ func (u *UniformityController) GetOne(c *fiber.Ctx) error { Code: fiber.StatusOK, Status: "success", Message: "Get production uniformity successfully", - Data: dto.ToUniformityDetailDTO(*result, calculation, document, standardDTO), + Data: dto.ToUniformityDetailDTO(*result, calculation, document, documentURL, standardDTO), }) } @@ -154,7 +155,7 @@ func (u *UniformityController) CreateOne(c *fiber.Ctx) error { Code: fiber.StatusCreated, Status: "success", Message: "Create uniformity successfully", - Data: dto.ToUniformityDetailDTO(*result, calculation, document, standardDTO), + Data: dto.ToUniformityDetailDTO(*result, calculation, document, "", standardDTO), }) } @@ -237,7 +238,7 @@ func (u *UniformityController) UpdateOne(c *fiber.Ctx) error { Code: fiber.StatusOK, Status: "success", Message: "Update uniformity successfully", - Data: dto.ToUniformityDetailDTO(*result, calculation, document, standardDTO), + Data: dto.ToUniformityDetailDTO(*result, calculation, document, "", standardDTO), }) } diff --git a/internal/modules/production/uniformities/dto/uniformity.dto.go b/internal/modules/production/uniformities/dto/uniformity.dto.go index 1324d805..4a813b98 100644 --- a/internal/modules/production/uniformities/dto/uniformity.dto.go +++ b/internal/modules/production/uniformities/dto/uniformity.dto.go @@ -45,6 +45,7 @@ type UniformityInfoDTO struct { ProjectFlock string `json:"project_flock"` Kandang string `json:"kandang"` FileName string `json:"file_name"` + FileURL string `json:"file_url"` } type UniformityDetailDTO struct { @@ -97,6 +98,7 @@ func ToUniformityDetailDTO( entityData entity.ProjectFlockKandangUniformity, calc service.UniformityCalculation, document *entity.Document, + documentURL string, standard *UniformityStandardDTO, ) UniformityDetailDTO { info := UniformityInfoDTO{ @@ -105,10 +107,14 @@ func ToUniformityDetailDTO( ProjectFlock: resolveProjectFlockName(entityData.ProjectFlockKandang), Kandang: resolveKandangName(entityData.ProjectFlockKandang), FileName: "", + FileURL: "", } if document != nil { info.FileName = document.Name } + if documentURL != "" { + info.FileURL = documentURL + } return UniformityDetailDTO{ Id: entityData.Id, diff --git a/internal/modules/production/uniformities/services/uniformity.service.go b/internal/modules/production/uniformities/services/uniformity.service.go index 2e76e48f..c999867d 100644 --- a/internal/modules/production/uniformities/services/uniformity.service.go +++ b/internal/modules/production/uniformities/services/uniformity.service.go @@ -39,7 +39,7 @@ type UniformityService interface { Approval(ctx *fiber.Ctx, req *validation.Approve) ([]entity.ProjectFlockKandangUniformity, error) ParseBodyWeightExcel(ctx *fiber.Ctx, file *multipart.FileHeader) ([]BodyWeightExcelRow, error) ComputeUniformity(rows []BodyWeightExcelRow) (UniformityCalculation, error) - CalculateUniformityFromDocument(ctx *fiber.Ctx, uniformityID uint) (UniformityCalculation, *entity.Document, error) + CalculateUniformityFromDocument(ctx *fiber.Ctx, uniformityID uint) (UniformityCalculation, *entity.Document, string, error) } type uniformityService struct { @@ -592,50 +592,53 @@ func (s uniformityService) ComputeUniformity(rows []BodyWeightExcelRow) (Uniform return computeUniformity(rows) } -func (s uniformityService) CalculateUniformityFromDocument(c *fiber.Ctx, uniformityID uint) (UniformityCalculation, *entity.Document, error) { +func (s uniformityService) CalculateUniformityFromDocument(c *fiber.Ctx, uniformityID uint) (UniformityCalculation, *entity.Document, string, error) { if s.DocumentSvc == nil { - return UniformityCalculation{}, nil, fiber.NewError(fiber.StatusInternalServerError, "Document service not available") + return UniformityCalculation{}, nil, "", fiber.NewError(fiber.StatusInternalServerError, "Document service not available") } documents, err := s.DocumentSvc.ListByTarget(c.Context(), "UNIFORMITY", uint64(uniformityID)) if err != nil { - return UniformityCalculation{}, nil, err + return UniformityCalculation{}, nil, "", err } if len(documents) == 0 { - return UniformityCalculation{}, nil, fiber.NewError(fiber.StatusNotFound, "Uniformity document not found") + return UniformityCalculation{}, nil, "", fiber.NewError(fiber.StatusNotFound, "Uniformity document not found") } document := documents[0] - url := s.DocumentSvc.PublicURL(document) + url, err := s.DocumentSvc.PresignURL(c.Context(), document, 15*time.Minute) + if err != nil { + return UniformityCalculation{}, nil, "", err + } if url == "" { - return UniformityCalculation{}, nil, fiber.NewError(fiber.StatusBadRequest, "Uniformity document URL not available") + return UniformityCalculation{}, nil, "", fiber.NewError(fiber.StatusBadRequest, "Uniformity document URL not available") } req, err := http.NewRequestWithContext(c.Context(), http.MethodGet, url, nil) if err != nil { - return UniformityCalculation{}, nil, err + return UniformityCalculation{}, nil, "", err } resp, err := http.DefaultClient.Do(req) if err != nil { - return UniformityCalculation{}, nil, err + return UniformityCalculation{}, nil, "", err } defer resp.Body.Close() if resp.StatusCode < 200 || resp.StatusCode >= 300 { - return UniformityCalculation{}, nil, fiber.NewError(fiber.StatusBadRequest, "Failed to download uniformity document") + return UniformityCalculation{}, nil, "", fiber.NewError(fiber.StatusBadRequest, "Failed to download uniformity document") } rows, err := parseBodyWeightExcelReader(resp.Body) if err != nil { - return UniformityCalculation{}, nil, err + return UniformityCalculation{}, nil, "", err } calculation, err := computeUniformity(rows) if err != nil { - return UniformityCalculation{}, nil, err + return UniformityCalculation{}, nil, "", err } - return calculation, &document, nil + return calculation, &document, url, nil } func (s *uniformityService) createUniformityApproval(