package service import ( "context" "fmt" "io" "mime" "mime/multipart" "narasi-ahli-be/app/module/knowledge_base/mapper" "narasi-ahli-be/app/module/knowledge_base/repository" "narasi-ahli-be/app/module/knowledge_base/request" "narasi-ahli-be/app/module/knowledge_base/response" config "narasi-ahli-be/config/config" minioStorage "narasi-ahli-be/config/config" "narasi-ahli-be/utils/paginator" "path/filepath" "strings" "time" "github.com/gofiber/fiber/v2" "github.com/minio/minio-go/v7" "github.com/rs/zerolog" ) type KnowledgeBaseService struct { Repo repository.KnowledgeBaseRepositoryInterface Log zerolog.Logger Cfg *config.Config MinioStorage *minioStorage.MinioStorage } type KnowledgeBaseServiceInterface interface { All(req request.KnowledgeBaseQueryRequest) (data []*response.KnowledgeBaseResponse, paging paginator.Pagination, err error) Show(id uint) (data *response.KnowledgeBaseResponse, err error) Create(c *fiber.Ctx) (data *response.KnowledgeBaseResponse, err error) Update(id uint, req request.KnowledgeBaseUpdateRequest) (data *response.KnowledgeBaseResponse, err error) Delete(id uint) error Viewer(c *fiber.Ctx) error UpdateStatus(id uint, status int) (data *response.KnowledgeBaseResponse, err error) UpdateDocumentId(id uint, documentId int) (data *response.KnowledgeBaseResponse, err error) } func NewKnowledgeBaseService( repo repository.KnowledgeBaseRepositoryInterface, log zerolog.Logger, cfg *config.Config, minioStorage *minioStorage.MinioStorage, ) KnowledgeBaseServiceInterface { return &KnowledgeBaseService{ Repo: repo, Log: log, Cfg: cfg, MinioStorage: minioStorage, } } func (s *KnowledgeBaseService) uploadFileToMinio( ctx context.Context, minioClient *minio.Client, bucketName string, agentId string, folder string, fileHeader *multipart.FileHeader, ) (*string, error) { if fileHeader == nil { return nil, nil } src, err := fileHeader.Open() if err != nil { return nil, err } defer src.Close() filename := filepath.Base(fileHeader.Filename) filename = strings.ReplaceAll(filename, " ", "") ext := filepath.Ext(filename) filenameWithoutExt := strings.TrimSuffix(filename, ext) now := time.Now() newFilename := fmt.Sprintf("%s_%d%s", filenameWithoutExt, now.UnixNano(), ext) objectName := fmt.Sprintf( "knowledge-base/%s/%s/%s", agentId, folder, newFilename, ) _, err = minioClient.PutObject( ctx, bucketName, objectName, src, fileHeader.Size, minio.PutObjectOptions{}, ) if err != nil { return nil, err } return &newFilename, nil } func (s *KnowledgeBaseService) All(req request.KnowledgeBaseQueryRequest) (data []*response.KnowledgeBaseResponse, paging paginator.Pagination, err error) { results, paging, err := s.Repo.GetAll(req) if err != nil { return nil, paging, err } host := s.Cfg.App.Domain for _, item := range results { data = append(data, mapper.KnowledgeBaseResponseMapper(item, host)) } return data, paging, nil } func getFileExtension(filename string) string { parts := strings.Split(filename, ".") if len(parts) == 1 || (len(parts) == 2 && parts[0] == "") { return "" } return parts[len(parts)-1] } func (s *KnowledgeBaseService) Viewer(c *fiber.Ctx) (err error) { filename := c.Params("filename") result, folder, err := s.Repo.FindByFilename(filename) if err != nil { return err } ctx := context.Background() bucketName := s.MinioStorage.Cfg.ObjectStorage.MinioStorage.BucketName objectName := fmt.Sprintf("knowledge-base/%s/%s/%s", *result.AgentId, folder, filename) s.Log.Info(). Str("timestamp", time.Now().Format(time.RFC3339)). Str("Service:Resource", "KnowledgeBase:Viewer"). Interface("objectName", objectName). Msg("") minioClient, err := s.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() contentType := mime.TypeByExtension("." + getFileExtension(objectName)) if contentType == "" { contentType = "application/octet-stream" } c.Set("Content-Type", contentType) if _, err := io.Copy(c.Response().BodyWriter(), fileContent); err != nil { return err } return nil } func (s *KnowledgeBaseService) Show(id uint) (data *response.KnowledgeBaseResponse, err error) { result, err := s.Repo.FindOne(id) if err != nil { return nil, err } host := s.Cfg.App.Domain return mapper.KnowledgeBaseResponseMapper(result, host), nil } func (s *KnowledgeBaseService) Create(c *fiber.Ctx) (data *response.KnowledgeBaseResponse, err error) { req := new(request.KnowledgeBaseCreateRequest) if err := c.BodyParser(req); err != nil { return nil, err } if req.AgentId == nil || *req.AgentId == "" { return nil, fiber.NewError(fiber.StatusBadRequest, "agentId is required") } if req.AgentName == nil || *req.AgentName == "" { return nil, fiber.NewError(fiber.StatusBadRequest, "agentName is required") } if req.Title == nil || *req.Title == "" { return nil, fiber.NewError(fiber.StatusBadRequest, "title is required") } fileJournal, _ := c.FormFile("fileJournal") fileAudio, _ := c.FormFile("fileAudio") fileVideo, _ := c.FormFile("fileVideo") minioClient, err := s.MinioStorage.ConnectMinio() if err != nil { return nil, err } ctx := context.Background() bucketName := s.MinioStorage.Cfg.ObjectStorage.MinioStorage.BucketName journalFilename, err := s.uploadFileToMinio(ctx, minioClient, bucketName, *req.AgentId, "journal", fileJournal) if err != nil { return nil, err } audioFilename, err := s.uploadFileToMinio(ctx, minioClient, bucketName, *req.AgentId, "audio", fileAudio) if err != nil { return nil, err } videoFilename, err := s.uploadFileToMinio(ctx, minioClient, bucketName, *req.AgentId, "video", fileVideo) if err != nil { return nil, err } entity := req.ToEntity() viewerBase := s.Cfg.App.Domain + "/knowledge-base/viewer/" if journalFilename != nil { journalUrl := viewerBase + *journalFilename entity.FileJournalUrl = &journalUrl } if audioFilename != nil { audioUrl := viewerBase + *audioFilename entity.FileAudioUrl = &audioUrl } if videoFilename != nil { videoUrl := viewerBase + *videoFilename entity.FileVideoUrl = &videoUrl } if req.Status < 0 { entity.Status = 0 } err = s.Repo.Create(entity) if err != nil { return nil, err } result, err := s.Repo.FindOne(entity.ID) if err != nil { return nil, err } host := s.Cfg.App.Domain return mapper.KnowledgeBaseResponseMapper(result, host), nil } func (s *KnowledgeBaseService) Update(id uint, req request.KnowledgeBaseUpdateRequest) (data *response.KnowledgeBaseResponse, err error) { old, err := s.Repo.FindOne(id) if err != nil { return nil, err } old.AgentId = req.AgentId old.AgentName = req.AgentName old.Title = req.Title old.Status = req.Status old.FileJournalUrl = req.FileJournalUrl old.FileAudioUrl = req.FileAudioUrl old.FileVideoUrl = req.FileVideoUrl if req.IsActive != nil { old.IsActive = *req.IsActive } old.UpdatedAt = time.Now() err = s.Repo.Update(id, old) if err != nil { return nil, err } updated, err := s.Repo.FindOne(id) if err != nil { return nil, err } host := s.Cfg.App.Domain return mapper.KnowledgeBaseResponseMapper(updated, host), nil } func (s *KnowledgeBaseService) Delete(id uint) error { result, err := s.Repo.FindOne(id) if err != nil { return err } result.IsActive = false result.UpdatedAt = time.Now() return s.Repo.Update(id, result) } func (s *KnowledgeBaseService) UpdateStatus(id uint, status int) (data *response.KnowledgeBaseResponse, err error) { old, err := s.Repo.FindOne(id) if err != nil { return nil, err } old.Status = status old.UpdatedAt = time.Now() err = s.Repo.Update(id, old) if err != nil { return nil, err } updated, err := s.Repo.FindOne(id) if err != nil { return nil, err } host := s.Cfg.App.Domain return mapper.KnowledgeBaseResponseMapper(updated, host), nil } func (s *KnowledgeBaseService) UpdateDocumentId(id uint, documentId int) (data *response.KnowledgeBaseResponse, err error) { // cek data exist _, err = s.Repo.FindOne(id) if err != nil { return nil, err } err = s.Repo.UpdateDocumentId(id, documentId) if err != nil { return nil, err } updated, err := s.Repo.FindOne(id) if err != nil { return nil, err } host := s.Cfg.App.Domain return mapper.KnowledgeBaseResponseMapper(updated, host), nil }