narasiahli-be/app/module/chat/service/chat_schedule_file.service.go

338 lines
10 KiB
Go

package service
import (
"context"
"errors"
"fmt"
"io"
"math/rand"
"mime"
"narasi-ahli-be/app/database/entity"
"narasi-ahli-be/app/module/chat/mapper"
"narasi-ahli-be/app/module/chat/repository"
"narasi-ahli-be/app/module/chat/request"
"narasi-ahli-be/app/module/chat/response"
usersRepository "narasi-ahli-be/app/module/users/repository"
config "narasi-ahli-be/config/config"
minioStorage "narasi-ahli-be/config/config"
utilSvc "narasi-ahli-be/utils/service"
"path/filepath"
"strconv"
"strings"
"time"
"github.com/gofiber/fiber/v2"
"github.com/minio/minio-go/v7"
"github.com/rs/zerolog"
)
type chatScheduleFileService struct {
chatScheduleFileRepository repository.ChatScheduleFileRepository
chatScheduleRepository repository.ChatScheduleRepository
chatScheduleFileMapper *mapper.ChatScheduleFileMapper
Log zerolog.Logger
Cfg *config.Config
MinioStorage *minioStorage.MinioStorage
UsersRepo usersRepository.UsersRepository
}
type ChatScheduleFileService interface {
// File management operations
UploadChatScheduleFile(c *fiber.Ctx, chatScheduleID uint) error
GetChatScheduleFiles(authToken string, req request.ChatScheduleFileQueryRequest) (files []*response.ChatScheduleFileResponse, err error)
GetChatScheduleFileByID(authToken string, id uint) (file *response.ChatScheduleFileResponse, err error)
UpdateChatScheduleFile(authToken string, id uint, req request.ChatScheduleFileUpdateRequest) (err error)
DeleteChatScheduleFile(authToken string, id uint) (err error)
Viewer(c *fiber.Ctx) error
}
func NewChatScheduleFileService(
chatScheduleFileRepository repository.ChatScheduleFileRepository,
chatScheduleRepository repository.ChatScheduleRepository,
log zerolog.Logger,
cfg *config.Config,
minioStorage *minioStorage.MinioStorage,
usersRepo usersRepository.UsersRepository,
) ChatScheduleFileService {
return &chatScheduleFileService{
chatScheduleFileRepository: chatScheduleFileRepository,
chatScheduleRepository: chatScheduleRepository,
chatScheduleFileMapper: mapper.NewChatScheduleFileMapper(),
Log: log,
Cfg: cfg,
MinioStorage: minioStorage,
UsersRepo: usersRepo,
}
}
// UploadChatScheduleFile - Upload files for chat schedule
func (_i *chatScheduleFileService) UploadChatScheduleFile(c *fiber.Ctx, chatScheduleID uint) error {
bucketName := _i.MinioStorage.Cfg.ObjectStorage.MinioStorage.BucketName
form, err := c.MultipartForm()
if err != nil {
return err
}
// Create minio connection
minioClient, err := _i.MinioStorage.ConnectMinio()
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": true,
"msg": err.Error(),
})
}
for _, files := range form.File {
_i.Log.Info().Str("timestamp", time.Now().
Format(time.RFC3339)).Str("Service:Resource", "ChatScheduleFile::Upload").
Interface("files", files).Msg("")
for _, fileHeader := range files {
_i.Log.Info().Str("timestamp", time.Now().
Format(time.RFC3339)).Str("Service:Resource", "ChatScheduleFile::Upload").
Interface("data", fileHeader).Msg("")
src, err := fileHeader.Open()
if err != nil {
return err
}
defer src.Close()
filename := filepath.Base(fileHeader.Filename)
filenameAlt := filepath.Clean(filename[:len(filename)-len(filepath.Ext(filename))])
filename = strings.ReplaceAll(filename, " ", "")
filenameWithoutExt := filepath.Clean(filename[:len(filename)-len(filepath.Ext(filename))])
extension := filepath.Ext(fileHeader.Filename)[1:]
now := time.Now()
rand.New(rand.NewSource(now.UnixNano()))
randUniqueId := rand.Intn(1000000)
newFilenameWithoutExt := filenameWithoutExt + "_" + strconv.Itoa(randUniqueId)
newFilename := newFilenameWithoutExt + "." + extension
objectName := fmt.Sprintf("chat-schedules/upload/%d/%d/%s", now.Year(), now.Month(), newFilename)
// Get file type from form data
fileType := c.FormValue("file_type", "other")
description := c.FormValue("description", "")
isRequired := c.FormValue("is_required") == "true"
// Create file entity
fileEntity := &entity.ChatScheduleFiles{
ChatScheduleID: chatScheduleID,
FileName: newFilename,
OriginalName: filenameAlt,
FilePath: objectName,
FileSize: fileHeader.Size,
MimeType: fileHeader.Header.Get("Content-Type"),
FileType: fileType,
Description: description,
IsRequired: isRequired,
}
// Save to database
_, err = _i.chatScheduleFileRepository.CreateChatScheduleFile(fileEntity)
if err != nil {
return err
}
// Upload file to MinIO
_, err = minioClient.PutObject(context.Background(), bucketName, objectName, src, fileHeader.Size, minio.PutObjectOptions{})
if err != nil {
return err
}
}
}
_i.Log.Info().Str("timestamp", time.Now().
Format(time.RFC3339)).Str("Service:Resource", "ChatScheduleFile::Upload").
Interface("data", "Successfully uploaded").Msg("")
return nil
}
// GetChatScheduleFiles - Get files for a chat schedule
func (_i *chatScheduleFileService) GetChatScheduleFiles(authToken string, req request.ChatScheduleFileQueryRequest) (files []*response.ChatScheduleFileResponse, err error) {
userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
if userInfo == nil {
return nil, errors.New("user not found")
}
userID := userInfo.ID
// If chat schedule ID is provided, check if user has access
if req.ChatScheduleID != nil {
isParticipant, err := _i.chatScheduleRepository.CheckUserInChatSchedule(userID, *req.ChatScheduleID)
if err != nil {
return nil, err
}
if !isParticipant {
return nil, errors.New("user is not a participant in this chat session")
}
}
// Get files from repository
fileEntities, err := _i.chatScheduleFileRepository.GetChatScheduleFiles(req)
if err != nil {
return nil, err
}
// Convert to response
files = _i.chatScheduleFileMapper.ToResponseList(fileEntities)
return
}
// GetChatScheduleFileByID - Get a specific chat schedule file
func (_i *chatScheduleFileService) GetChatScheduleFileByID(authToken string, id uint) (file *response.ChatScheduleFileResponse, err error) {
userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
if userInfo == nil {
return nil, errors.New("user not found")
}
userID := userInfo.ID
// Get file from repository
fileEntity, err := _i.chatScheduleFileRepository.GetChatScheduleFileByID(id)
if err != nil {
return nil, err
}
// Check if user has access to the chat schedule
isParticipant, err := _i.chatScheduleRepository.CheckUserInChatSchedule(userID, fileEntity.ChatScheduleID)
if err != nil {
return nil, err
}
if !isParticipant {
return nil, errors.New("user is not a participant in this chat session")
}
// Convert to response
file = _i.chatScheduleFileMapper.ToResponse(fileEntity)
return
}
// UpdateChatScheduleFile - Update a chat schedule file
func (_i *chatScheduleFileService) UpdateChatScheduleFile(authToken string, id uint, req request.ChatScheduleFileUpdateRequest) (err error) {
userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
if userInfo == nil {
return errors.New("user not found")
}
userID := userInfo.ID
// Get existing file to check access
existingFile, err := _i.chatScheduleFileRepository.GetChatScheduleFileByID(id)
if err != nil {
return err
}
// Check if user has access to the chat schedule
isParticipant, err := _i.chatScheduleRepository.CheckUserInChatSchedule(userID, existingFile.ChatScheduleID)
if err != nil {
return err
}
if !isParticipant {
return errors.New("user is not a participant in this chat session")
}
// Convert request to entity
file := _i.chatScheduleFileMapper.ToUpdateEntity(req)
// Update file
err = _i.chatScheduleFileRepository.UpdateChatScheduleFile(id, file)
return
}
// DeleteChatScheduleFile - Delete a chat schedule file
func (_i *chatScheduleFileService) DeleteChatScheduleFile(authToken string, id uint) (err error) {
userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
if userInfo == nil {
return errors.New("user not found")
}
userID := userInfo.ID
// Get existing file to check access
existingFile, err := _i.chatScheduleFileRepository.GetChatScheduleFileByID(id)
if err != nil {
return err
}
// Check if user has access to the chat schedule
isParticipant, err := _i.chatScheduleRepository.CheckUserInChatSchedule(userID, existingFile.ChatScheduleID)
if err != nil {
return err
}
if !isParticipant {
return errors.New("user is not a participant in this chat session")
}
// Delete file
err = _i.chatScheduleFileRepository.DeleteChatScheduleFile(id)
return
}
// Viewer - View chat schedule file
func (_i *chatScheduleFileService) Viewer(c *fiber.Ctx) error {
filename := c.Params("filename")
// Find file by filename
fileEntity, err := _i.chatScheduleFileRepository.GetChatScheduleFileByFilename(filename)
if err != nil {
return err
}
ctx := context.Background()
bucketName := _i.MinioStorage.Cfg.ObjectStorage.MinioStorage.BucketName
objectName := fileEntity.FilePath
_i.Log.Info().Str("timestamp", time.Now().
Format(time.RFC3339)).Str("Service:Resource", "ChatScheduleFile::Viewer").
Interface("data", objectName).Msg("")
// Create minio connection
minioClient, err := _i.MinioStorage.ConnectMinio()
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": true,
"msg": err.Error(),
})
}
fileContent, err := minioClient.GetObject(ctx, bucketName, objectName, minio.GetObjectOptions{})
if err != nil {
return err
}
defer fileContent.Close()
// Determine Content-Type based on file extension
contentType := mime.TypeByExtension("." + getFileExtension(objectName))
if contentType == "" {
contentType = "application/octet-stream" // fallback if no MIME type matches
}
c.Set("Content-Type", contentType)
if _, err := io.Copy(c.Response().BodyWriter(), fileContent); err != nil {
return err
}
return nil
}
// getFileExtension - Extract file extension from filename
func getFileExtension(filename string) string {
// split file name
parts := strings.Split(filename, ".")
// if no extension, return empty string
if len(parts) == 1 || (len(parts) == 2 && parts[0] == "") {
return ""
}
// get last extension
return parts[len(parts)-1]
}