apskel-pos-backend/internal/processor/file_manager_processor.go
aditya.siregar 4f5950543e init
2025-07-18 20:10:29 +07:00

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
}