mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-20 13:31:56 +00:00
Merge branch 'fix/BE-Document_s3' into 'development'
feat(BE): add function read and download in document See merge request mbugroup/lti-api!126
This commit is contained in:
@@ -8,6 +8,7 @@ import (
|
|||||||
"mime/multipart"
|
"mime/multipart"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
commonRepo "gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
commonRepo "gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
||||||
"gitlab.com/mbugroup/lti-api.git/internal/config"
|
"gitlab.com/mbugroup/lti-api.git/internal/config"
|
||||||
@@ -29,6 +30,7 @@ type DocumentService interface {
|
|||||||
DeleteDocuments(ctx context.Context, ids []uint, removeFromStorage bool) error
|
DeleteDocuments(ctx context.Context, ids []uint, removeFromStorage bool) error
|
||||||
DeleteByTarget(ctx context.Context, documentableType string, documentableID uint64, removeFromStorage bool) error
|
DeleteByTarget(ctx context.Context, documentableType string, documentableID uint64, removeFromStorage bool) error
|
||||||
PublicURL(document entity.Document) string
|
PublicURL(document entity.Document) string
|
||||||
|
PresignURL(ctx context.Context, document entity.Document, expires time.Duration) (string, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type DocumentUploadRequest struct {
|
type DocumentUploadRequest struct {
|
||||||
@@ -293,6 +295,16 @@ func (s *documentService) PublicURL(document entity.Document) string {
|
|||||||
return s.storage.URL(document.Path)
|
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) {
|
func (s *documentService) generateObjectKey(ext string) (string, error) {
|
||||||
normalizedExt := strings.TrimSpace(ext)
|
normalizedExt := strings.TrimSpace(ext)
|
||||||
if normalizedExt != "" && !strings.HasPrefix(normalizedExt, ".") {
|
if normalizedExt != "" && !strings.HasPrefix(normalizedExt, ".") {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/aws/aws-sdk-go-v2/aws"
|
"github.com/aws/aws-sdk-go-v2/aws"
|
||||||
awsconfig "github.com/aws/aws-sdk-go-v2/config"
|
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)
|
Upload(ctx context.Context, key string, body io.Reader, size int64, contentType string) (DocumentStorageUploadResult, error)
|
||||||
Delete(ctx context.Context, key string) error
|
Delete(ctx context.Context, key string) error
|
||||||
URL(key string) string
|
URL(key string) string
|
||||||
|
PresignURL(ctx context.Context, key string, expires time.Duration) (string, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type DocumentStorageUploadResult struct {
|
type DocumentStorageUploadResult struct {
|
||||||
@@ -36,9 +38,10 @@ type S3DocumentStorageConfig struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type s3DocumentStorage struct {
|
type s3DocumentStorage struct {
|
||||||
client *s3.Client
|
client *s3.Client
|
||||||
bucket string
|
presignClient *s3.PresignClient
|
||||||
base string
|
bucket string
|
||||||
|
base string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewS3DocumentStorage(ctx context.Context, cfg S3DocumentStorageConfig) (DocumentStorage, error) {
|
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) {
|
client := s3.NewFromConfig(awsCfg, func(o *s3.Options) {
|
||||||
o.UsePathStyle = cfg.ForcePathStyle
|
o.UsePathStyle = cfg.ForcePathStyle
|
||||||
})
|
})
|
||||||
|
presignClient := s3.NewPresignClient(client)
|
||||||
|
|
||||||
baseURL := strings.TrimSuffix(strings.TrimSpace(cfg.BaseURL), "/")
|
baseURL := strings.TrimSuffix(strings.TrimSpace(cfg.BaseURL), "/")
|
||||||
if baseURL == "" {
|
if baseURL == "" {
|
||||||
@@ -97,9 +101,10 @@ func NewS3DocumentStorage(ctx context.Context, cfg S3DocumentStorageConfig) (Doc
|
|||||||
}
|
}
|
||||||
|
|
||||||
return &s3DocumentStorage{
|
return &s3DocumentStorage{
|
||||||
client: client,
|
client: client,
|
||||||
bucket: bucket,
|
presignClient: presignClient,
|
||||||
base: baseURL,
|
bucket: bucket,
|
||||||
|
base: baseURL,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,3 +163,23 @@ func (s *s3DocumentStorage) URL(key string) string {
|
|||||||
}
|
}
|
||||||
return fmt.Sprintf("%s/%s", s.base, key)
|
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
|
||||||
|
}
|
||||||
|
|||||||
@@ -71,13 +71,14 @@ func (u *UniformityController) GetOne(c *fiber.Ctx) error {
|
|||||||
withDetails := c.QueryBool("with_details", false)
|
withDetails := c.QueryBool("with_details", false)
|
||||||
calculation := service.UniformityCalculation{}
|
calculation := service.UniformityCalculation{}
|
||||||
var document *entity.Document
|
var document *entity.Document
|
||||||
|
var documentURL string
|
||||||
var meanWeight float64
|
var meanWeight float64
|
||||||
if result.MeanUp > 0 {
|
if result.MeanUp > 0 {
|
||||||
meanWeight = math.Round(result.MeanUp / 1.10)
|
meanWeight = math.Round(result.MeanUp / 1.10)
|
||||||
}
|
}
|
||||||
if withDetails {
|
if withDetails {
|
||||||
var err error
|
var err error
|
||||||
calculation, document, err = u.UniformityService.CalculateUniformityFromDocument(c, id)
|
calculation, document, documentURL, err = u.UniformityService.CalculateUniformityFromDocument(c, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -111,7 +112,7 @@ func (u *UniformityController) GetOne(c *fiber.Ctx) error {
|
|||||||
Code: fiber.StatusOK,
|
Code: fiber.StatusOK,
|
||||||
Status: "success",
|
Status: "success",
|
||||||
Message: "Get production uniformity successfully",
|
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,
|
Code: fiber.StatusCreated,
|
||||||
Status: "success",
|
Status: "success",
|
||||||
Message: "Create uniformity successfully",
|
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,
|
Code: fiber.StatusOK,
|
||||||
Status: "success",
|
Status: "success",
|
||||||
Message: "Update uniformity successfully",
|
Message: "Update uniformity successfully",
|
||||||
Data: dto.ToUniformityDetailDTO(*result, calculation, document, standardDTO),
|
Data: dto.ToUniformityDetailDTO(*result, calculation, document, "", standardDTO),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ type UniformityInfoDTO struct {
|
|||||||
ProjectFlock string `json:"project_flock"`
|
ProjectFlock string `json:"project_flock"`
|
||||||
Kandang string `json:"kandang"`
|
Kandang string `json:"kandang"`
|
||||||
FileName string `json:"file_name"`
|
FileName string `json:"file_name"`
|
||||||
|
FileURL string `json:"file_url"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type UniformityDetailDTO struct {
|
type UniformityDetailDTO struct {
|
||||||
@@ -97,6 +98,7 @@ func ToUniformityDetailDTO(
|
|||||||
entityData entity.ProjectFlockKandangUniformity,
|
entityData entity.ProjectFlockKandangUniformity,
|
||||||
calc service.UniformityCalculation,
|
calc service.UniformityCalculation,
|
||||||
document *entity.Document,
|
document *entity.Document,
|
||||||
|
documentURL string,
|
||||||
standard *UniformityStandardDTO,
|
standard *UniformityStandardDTO,
|
||||||
) UniformityDetailDTO {
|
) UniformityDetailDTO {
|
||||||
info := UniformityInfoDTO{
|
info := UniformityInfoDTO{
|
||||||
@@ -105,10 +107,14 @@ func ToUniformityDetailDTO(
|
|||||||
ProjectFlock: resolveProjectFlockName(entityData.ProjectFlockKandang),
|
ProjectFlock: resolveProjectFlockName(entityData.ProjectFlockKandang),
|
||||||
Kandang: resolveKandangName(entityData.ProjectFlockKandang),
|
Kandang: resolveKandangName(entityData.ProjectFlockKandang),
|
||||||
FileName: "",
|
FileName: "",
|
||||||
|
FileURL: "",
|
||||||
}
|
}
|
||||||
if document != nil {
|
if document != nil {
|
||||||
info.FileName = document.Name
|
info.FileName = document.Name
|
||||||
}
|
}
|
||||||
|
if documentURL != "" {
|
||||||
|
info.FileURL = documentURL
|
||||||
|
}
|
||||||
|
|
||||||
return UniformityDetailDTO{
|
return UniformityDetailDTO{
|
||||||
Id: entityData.Id,
|
Id: entityData.Id,
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ type UniformityService interface {
|
|||||||
Approval(ctx *fiber.Ctx, req *validation.Approve) ([]entity.ProjectFlockKandangUniformity, error)
|
Approval(ctx *fiber.Ctx, req *validation.Approve) ([]entity.ProjectFlockKandangUniformity, error)
|
||||||
ParseBodyWeightExcel(ctx *fiber.Ctx, file *multipart.FileHeader) ([]BodyWeightExcelRow, error)
|
ParseBodyWeightExcel(ctx *fiber.Ctx, file *multipart.FileHeader) ([]BodyWeightExcelRow, error)
|
||||||
ComputeUniformity(rows []BodyWeightExcelRow) (UniformityCalculation, 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 {
|
type uniformityService struct {
|
||||||
@@ -592,50 +592,53 @@ func (s uniformityService) ComputeUniformity(rows []BodyWeightExcelRow) (Uniform
|
|||||||
return computeUniformity(rows)
|
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 {
|
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))
|
documents, err := s.DocumentSvc.ListByTarget(c.Context(), "UNIFORMITY", uint64(uniformityID))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return UniformityCalculation{}, nil, err
|
return UniformityCalculation{}, nil, "", err
|
||||||
}
|
}
|
||||||
if len(documents) == 0 {
|
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]
|
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 == "" {
|
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)
|
req, err := http.NewRequestWithContext(c.Context(), http.MethodGet, url, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return UniformityCalculation{}, nil, err
|
return UniformityCalculation{}, nil, "", err
|
||||||
}
|
}
|
||||||
resp, err := http.DefaultClient.Do(req)
|
resp, err := http.DefaultClient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return UniformityCalculation{}, nil, err
|
return UniformityCalculation{}, nil, "", err
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
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)
|
rows, err := parseBodyWeightExcelReader(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return UniformityCalculation{}, nil, err
|
return UniformityCalculation{}, nil, "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
calculation, err := computeUniformity(rows)
|
calculation, err := computeUniformity(rows)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return UniformityCalculation{}, nil, err
|
return UniformityCalculation{}, nil, "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
return calculation, &document, nil
|
return calculation, &document, url, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *uniformityService) createUniformityApproval(
|
func (s *uniformityService) createUniformityApproval(
|
||||||
|
|||||||
Reference in New Issue
Block a user