228 lines
6.6 KiB
Go
228 lines
6.6 KiB
Go
package processor
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"mime/multipart"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
|
|
"apskel-pos-be/internal/constants"
|
|
"apskel-pos-be/internal/entities"
|
|
"apskel-pos-be/internal/mappers"
|
|
"apskel-pos-be/internal/models"
|
|
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
type FileProcessor interface {
|
|
UploadFile(ctx context.Context, file *multipart.FileHeader, req *models.UploadFileRequest, organizationID, userID uuid.UUID) (*models.FileResponse, error)
|
|
GetFileByID(ctx context.Context, id uuid.UUID) (*models.FileResponse, error)
|
|
UpdateFile(ctx context.Context, id uuid.UUID, req *models.UpdateFileRequest) (*models.FileResponse, error)
|
|
ListFiles(ctx context.Context, req *models.ListFilesRequest) (*models.ListFilesResponse, error)
|
|
GetFileByOrganizationID(ctx context.Context, organizationID uuid.UUID) ([]*models.FileResponse, error)
|
|
GetFileByUserID(ctx context.Context, userID uuid.UUID) ([]*models.FileResponse, error)
|
|
}
|
|
|
|
type FileRepository interface {
|
|
Create(ctx context.Context, file *entities.File) error
|
|
GetByID(ctx context.Context, id uuid.UUID) (*entities.File, error)
|
|
GetByOrganizationID(ctx context.Context, organizationID uuid.UUID) ([]*entities.File, error)
|
|
GetByUserID(ctx context.Context, userID uuid.UUID) ([]*entities.File, error)
|
|
Update(ctx context.Context, file *entities.File) error
|
|
Delete(ctx context.Context, id uuid.UUID) error
|
|
List(ctx context.Context, filters map[string]interface{}, limit, offset int) ([]*entities.File, int64, error)
|
|
GetByFileName(ctx context.Context, fileName string) (*entities.File, error)
|
|
ExistsByFileName(ctx context.Context, fileName string) (bool, error)
|
|
}
|
|
|
|
type FileProcessorImpl struct {
|
|
fileRepo FileRepository
|
|
fileClient FileClient
|
|
}
|
|
|
|
func NewFileProcessorImpl(fileRepo FileRepository, fileClient FileClient) *FileProcessorImpl {
|
|
return &FileProcessorImpl{
|
|
fileRepo: fileRepo,
|
|
fileClient: fileClient,
|
|
}
|
|
}
|
|
|
|
func (p *FileProcessorImpl) UploadFile(ctx context.Context, file *multipart.FileHeader, req *models.UploadFileRequest, organizationID, userID uuid.UUID) (*models.FileResponse, error) {
|
|
if file == nil {
|
|
return nil, fmt.Errorf("file is required")
|
|
}
|
|
|
|
if file.Size == 0 {
|
|
return nil, fmt.Errorf("file cannot be empty")
|
|
}
|
|
|
|
const maxFileSize = 10 * 1024 * 1024 // 10MB
|
|
if file.Size > maxFileSize {
|
|
return nil, fmt.Errorf("file size exceeds maximum limit of 10MB")
|
|
}
|
|
|
|
if !constants.IsValidFileType(req.FileType) {
|
|
return nil, fmt.Errorf("invalid file type: %s", req.FileType)
|
|
}
|
|
|
|
originalName := file.Filename
|
|
fileName := mappers.GenerateFileName(originalName, organizationID, userID)
|
|
|
|
for {
|
|
exists, err := p.fileRepo.ExistsByFileName(ctx, fileName)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to check filename uniqueness: %w", err)
|
|
}
|
|
if !exists {
|
|
break
|
|
}
|
|
|
|
ext := filepath.Ext(fileName)
|
|
base := strings.TrimSuffix(fileName, ext)
|
|
fileName = fmt.Sprintf("%s_%d%s", base, time.Now().UnixNano(), ext)
|
|
}
|
|
|
|
src, err := file.Open()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to open file: %w", err)
|
|
}
|
|
defer src.Close()
|
|
|
|
fileContent := make([]byte, file.Size)
|
|
_, err = src.Read(fileContent)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read file content: %w", err)
|
|
}
|
|
|
|
fileURL, err := p.fileClient.UploadFile(ctx, fileName, fileContent)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to upload file to storage: %w", err)
|
|
}
|
|
|
|
mimeType := file.Header.Get("Content-Type")
|
|
if mimeType == "" {
|
|
mimeType = "application/octet-stream"
|
|
}
|
|
|
|
fileType := req.FileType
|
|
if fileType == "" {
|
|
fileType = constants.GetFileTypeFromMimeType(mimeType)
|
|
}
|
|
|
|
fileEntity := mappers.UploadFileRequestToEntity(
|
|
&models.UploadFileRequest{
|
|
FileType: fileType,
|
|
IsPublic: req.IsPublic,
|
|
Metadata: req.Metadata,
|
|
},
|
|
organizationID,
|
|
userID,
|
|
fileName,
|
|
originalName,
|
|
fileURL,
|
|
mimeType,
|
|
fileName,
|
|
file.Size,
|
|
)
|
|
|
|
if err := p.fileRepo.Create(ctx, fileEntity); err != nil {
|
|
return nil, fmt.Errorf("failed to save file metadata: %w", err)
|
|
}
|
|
|
|
response := mappers.FileEntityToResponse(fileEntity)
|
|
return response, nil
|
|
}
|
|
|
|
func (p *FileProcessorImpl) GetFileByID(ctx context.Context, id uuid.UUID) (*models.FileResponse, error) {
|
|
file, err := p.fileRepo.GetByID(ctx, id)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get file: %w", err)
|
|
}
|
|
|
|
response := mappers.FileEntityToResponse(file)
|
|
return response, nil
|
|
}
|
|
|
|
func (p *FileProcessorImpl) UpdateFile(ctx context.Context, id uuid.UUID, req *models.UpdateFileRequest) (*models.FileResponse, error) {
|
|
file, err := p.fileRepo.GetByID(ctx, id)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get file: %w", err)
|
|
}
|
|
|
|
updates := mappers.UpdateFileRequestToEntityUpdates(req)
|
|
for key, value := range updates {
|
|
switch key {
|
|
case "is_public":
|
|
file.IsPublic = value.(bool)
|
|
case "metadata":
|
|
file.Metadata = entities.Metadata(value.(map[string]interface{}))
|
|
}
|
|
}
|
|
|
|
if err := p.fileRepo.Update(ctx, file); err != nil {
|
|
return nil, fmt.Errorf("failed to update file: %w", err)
|
|
}
|
|
|
|
response := mappers.FileEntityToResponse(file)
|
|
return response, nil
|
|
}
|
|
|
|
func (p *FileProcessorImpl) ListFiles(ctx context.Context, req *models.ListFilesRequest) (*models.ListFilesResponse, error) {
|
|
filters := make(map[string]interface{})
|
|
|
|
if req.OrganizationID != nil {
|
|
filters["organization_id"] = *req.OrganizationID
|
|
}
|
|
if req.UserID != nil {
|
|
filters["user_id"] = *req.UserID
|
|
}
|
|
if req.FileType != nil {
|
|
filters["file_type"] = string(*req.FileType)
|
|
}
|
|
if req.IsPublic != nil {
|
|
filters["is_public"] = *req.IsPublic
|
|
}
|
|
|
|
offset := (req.Page - 1) * req.Limit
|
|
|
|
files, total, err := p.fileRepo.List(ctx, filters, req.Limit, offset)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to list files: %w", err)
|
|
}
|
|
|
|
fileResponses := mappers.FileEntitiesToResponses(files)
|
|
totalPages := (int(total) + req.Limit - 1) / req.Limit
|
|
|
|
response := &models.ListFilesResponse{
|
|
Files: fileResponses,
|
|
TotalCount: int(total),
|
|
Page: req.Page,
|
|
Limit: req.Limit,
|
|
TotalPages: totalPages,
|
|
}
|
|
|
|
return response, nil
|
|
}
|
|
|
|
func (p *FileProcessorImpl) GetFileByOrganizationID(ctx context.Context, organizationID uuid.UUID) ([]*models.FileResponse, error) {
|
|
files, err := p.fileRepo.GetByOrganizationID(ctx, organizationID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get files by organization: %w", err)
|
|
}
|
|
|
|
responses := mappers.FileEntitiesToResponses(files)
|
|
return responses, nil
|
|
}
|
|
|
|
func (p *FileProcessorImpl) GetFileByUserID(ctx context.Context, userID uuid.UUID) ([]*models.FileResponse, error) {
|
|
files, err := p.fileRepo.GetByUserID(ctx, userID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get files by user: %w", err)
|
|
}
|
|
|
|
responses := mappers.FileEntitiesToResponses(files)
|
|
return responses, nil
|
|
}
|