2026-01-14 01:29:35 +00:00
|
|
|
package service
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
"fmt"
|
2026-02-10 04:02:55 +00:00
|
|
|
"io"
|
2026-01-14 01:29:35 +00:00
|
|
|
"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)
|
2026-01-15 06:59:03 +00:00
|
|
|
UpdateDocumentId(id uint, documentId int) (data *response.KnowledgeBaseResponse, err error)
|
|
|
|
|
|
2026-01-14 01:29:35 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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,
|
2026-02-10 04:02:55 +00:00
|
|
|
folder string,
|
2026-01-14 01:29:35 +00:00
|
|
|
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(
|
2026-02-10 04:02:55 +00:00
|
|
|
"knowledge-base/%s/%s/%s",
|
2026-01-14 01:29:35 +00:00
|
|
|
agentId,
|
2026-02-10 04:02:55 +00:00
|
|
|
folder,
|
2026-01-14 01:29:35 +00:00
|
|
|
newFilename,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
_, err = minioClient.PutObject(
|
|
|
|
|
ctx,
|
|
|
|
|
bucketName,
|
|
|
|
|
objectName,
|
|
|
|
|
src,
|
|
|
|
|
fileHeader.Size,
|
2026-02-10 04:02:55 +00:00
|
|
|
minio.PutObjectOptions{},
|
2026-01-14 01:29:35 +00:00
|
|
|
)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-10 04:02:55 +00:00
|
|
|
return &newFilename, nil
|
2026-01-14 01:29:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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]
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-10 04:02:55 +00:00
|
|
|
func (s *KnowledgeBaseService) Viewer(c *fiber.Ctx) (err error) {
|
2026-01-14 01:29:35 +00:00
|
|
|
filename := c.Params("filename")
|
|
|
|
|
|
2026-02-10 04:02:55 +00:00
|
|
|
result, folder, err := s.Repo.FindByFilename(filename)
|
2026-01-14 01:29:35 +00:00
|
|
|
if err != nil {
|
2026-02-10 04:02:55 +00:00
|
|
|
return err
|
2026-01-14 01:29:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ctx := context.Background()
|
|
|
|
|
bucketName := s.MinioStorage.Cfg.ObjectStorage.MinioStorage.BucketName
|
|
|
|
|
|
2026-02-10 04:02:55 +00:00
|
|
|
objectName := fmt.Sprintf("knowledge-base/%s/%s/%s", *result.AgentId, folder, filename)
|
2026-01-14 01:29:35 +00:00
|
|
|
|
2026-02-10 04:02:55 +00:00
|
|
|
s.Log.Info().
|
|
|
|
|
Str("timestamp", time.Now().Format(time.RFC3339)).
|
|
|
|
|
Str("Service:Resource", "KnowledgeBase:Viewer").
|
|
|
|
|
Interface("objectName", objectName).
|
|
|
|
|
Msg("")
|
2026-01-14 01:29:35 +00:00
|
|
|
|
2026-02-10 04:02:55 +00:00
|
|
|
minioClient, err := s.MinioStorage.ConnectMinio()
|
2026-01-14 01:29:35 +00:00
|
|
|
if err != nil {
|
2026-02-10 04:02:55 +00:00
|
|
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
|
|
|
|
"error": true,
|
|
|
|
|
"msg": err.Error(),
|
|
|
|
|
})
|
2026-01-14 01:29:35 +00:00
|
|
|
}
|
|
|
|
|
|
2026-02-10 04:02:55 +00:00
|
|
|
fileContent, err := minioClient.GetObject(ctx, bucketName, objectName, minio.GetObjectOptions{})
|
2026-01-14 01:29:35 +00:00
|
|
|
if err != nil {
|
2026-02-10 04:02:55 +00:00
|
|
|
return err
|
2026-01-14 01:29:35 +00:00
|
|
|
}
|
2026-02-10 04:02:55 +00:00
|
|
|
defer fileContent.Close()
|
2026-01-14 01:29:35 +00:00
|
|
|
|
2026-02-10 04:02:55 +00:00
|
|
|
contentType := mime.TypeByExtension("." + getFileExtension(objectName))
|
2026-01-14 01:29:35 +00:00
|
|
|
if contentType == "" {
|
2026-02-10 04:02:55 +00:00
|
|
|
contentType = "application/octet-stream"
|
2026-01-14 01:29:35 +00:00
|
|
|
}
|
2026-02-09 08:44:31 +00:00
|
|
|
c.Set("Content-Type", contentType)
|
2026-01-14 01:29:35 +00:00
|
|
|
|
2026-02-10 04:02:55 +00:00
|
|
|
if _, err := io.Copy(c.Response().BodyWriter(), fileContent); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
2026-02-09 08:44:31 +00:00
|
|
|
|
2026-02-10 04:02:55 +00:00
|
|
|
return nil
|
|
|
|
|
}
|
2026-02-09 08:44:31 +00:00
|
|
|
|
2026-01-14 01:29:35 +00:00
|
|
|
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
|
|
|
|
|
|
2026-02-10 04:02:55 +00:00
|
|
|
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
|
|
|
|
|
}
|
2026-01-14 01:29:35 +00:00
|
|
|
|
2026-02-10 04:02:55 +00:00
|
|
|
entity := req.ToEntity()
|
2026-01-14 01:29:35 +00:00
|
|
|
|
2026-02-10 04:02:55 +00:00
|
|
|
viewerBase := s.Cfg.App.Domain + "/knowledge-base/viewer/"
|
2026-02-09 08:44:31 +00:00
|
|
|
|
2026-02-10 04:02:55 +00:00
|
|
|
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
|
|
|
|
|
}
|
2026-01-14 01:29:35 +00:00
|
|
|
|
|
|
|
|
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()
|
|
|
|
|
|
2026-02-09 00:25:27 +00:00
|
|
|
return s.Repo.Delete(id)
|
2026-01-14 01:29:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
}
|
2026-01-15 06:59:03 +00:00
|
|
|
|
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
|