init
This commit is contained in:
@@ -0,0 +1,227 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user