diff --git a/app/database/entity/knowledge_base.entity.go b/app/database/entity/knowledge_base.entity.go new file mode 100644 index 0000000..a6e0933 --- /dev/null +++ b/app/database/entity/knowledge_base.entity.go @@ -0,0 +1,25 @@ +package entity + +import ( + "time" +) + +type KnowledgeBase struct { + ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"` + + AgentId *string `json:"agent_id" gorm:"type:varchar"` + AgentName *string `json:"agent_name" gorm:"type:varchar"` + CreatedById uint `json:"created_by_id" gorm:"type:int4;not null"` + Status int `json:"status" gorm:"type:int4;default:0"` // 0=wating,1=approved,2=reject + + Title *string `json:"title" gorm:"type:varchar"` + + FileJournalUrl *string `json:"file_journal_url" gorm:"type:varchar"` + FileAudioUrl *string `json:"file_audio_url" gorm:"type:varchar"` + FileVideoUrl *string `json:"file_video_url" gorm:"type:varchar"` + + IsActive bool `json:"is_active" gorm:"type:bool;default:true"` + + CreatedAt time.Time `json:"created_at" gorm:"default:now()"` + UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"` +} diff --git a/app/module/knowledge_base/controller/controller.go b/app/module/knowledge_base/controller/controller.go new file mode 100644 index 0000000..f0008c1 --- /dev/null +++ b/app/module/knowledge_base/controller/controller.go @@ -0,0 +1,17 @@ +package controller + +import ( + knowledgeBaseSvc "narasi-ahli-be/app/module/knowledge_base/service" +) + +type Controller struct { + KnowledgeBase KnowledgeBaseControllerInterface +} + +func NewController( + knowledgeBaseService knowledgeBaseSvc.KnowledgeBaseServiceInterface, +) *Controller { + return &Controller{ + KnowledgeBase: NewKnowledgeBaseController(knowledgeBaseService), + } +} diff --git a/app/module/knowledge_base/controller/knowledge_base.controller.go b/app/module/knowledge_base/controller/knowledge_base.controller.go new file mode 100644 index 0000000..4be232a --- /dev/null +++ b/app/module/knowledge_base/controller/knowledge_base.controller.go @@ -0,0 +1,241 @@ +package controller + +import ( + "narasi-ahli-be/app/module/knowledge_base/request" + "narasi-ahli-be/app/module/knowledge_base/service" + "narasi-ahli-be/utils/paginator" + utilRes "narasi-ahli-be/utils/response" + utilVal "narasi-ahli-be/utils/validator" + "strconv" + + "github.com/gofiber/fiber/v2" +) + +type KnowledgeBaseController struct { + KnowledgeBaseService service.KnowledgeBaseServiceInterface +} + +type KnowledgeBaseControllerInterface interface { + All(c *fiber.Ctx) error + Show(c *fiber.Ctx) error + Create(c *fiber.Ctx) error + Update(c *fiber.Ctx) error + Delete(c *fiber.Ctx) error + Viewer(c *fiber.Ctx) error + UpdateStatus(c *fiber.Ctx) error + + +} + +func NewKnowledgeBaseController(svc service.KnowledgeBaseServiceInterface) KnowledgeBaseControllerInterface { + return &KnowledgeBaseController{ + KnowledgeBaseService: svc, + } +} + +// @Summary Get all KnowledgeBase +// @Description API for getting all KnowledgeBase +// @Tags Knowledge Base +// @Security Bearer +// @Param agentId query string false "Agent ID" +// @Param title query string false "Search title" +// @Param status query int false "Status (0=draft,1=published,2=archived)" +// @Param createdById query int false "Created By ID" +// @Param isActive query bool false "Is active" +// @Param page query int false "Page" +// @Param limit query int false "Limit" +// @Success 200 {object} response.Response +// @Router /knowledge-base [get] +func (ctl *KnowledgeBaseController) All(c *fiber.Ctx) error { + paginate, err := paginator.Paginate(c) + if err != nil { + return err + } + + reqContext := request.KnowledgeBaseQueryRequestContext{ + AgentId: c.Query("agentId"), + Title: c.Query("title"), + Status: c.Query("status"), + IsActive: c.Query("isActive"), + CreatedById: c.Query("createdById"), + } + + req := reqContext.ToParamRequest() + req.Pagination = paginate + + data, paging, err := ctl.KnowledgeBaseService.All(req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"KnowledgeBase list successfully retrieved"}, + Data: data, + Meta: paging, + }) +} + +// @Summary Get one KnowledgeBase +// @Description API for getting one KnowledgeBase +// @Tags Knowledge Base +// @Security Bearer +// @Param id path int true "KnowledgeBase ID" +// @Success 200 {object} response.Response +// @Router /knowledge-base/{id} [get] +func (ctl *KnowledgeBaseController) Show(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + data, err := ctl.KnowledgeBaseService.Show(uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"KnowledgeBase successfully retrieved"}, + Data: data, + }) +} + +// @Summary Create KnowledgeBase +// @Description API for creating KnowledgeBase with upload file +// @Tags Knowledge Base +// @Security Bearer +// @Accept multipart/form-data +// @Produce json +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// +// @Param agentId formData string true "Agent ID" +// @Param agentName formData string true "Agent Name" +// @Param createdById formData int true "Created By ID" +// @Param status formData int true "Status (integer)" +// @Param title formData string true "Title" +// +// @Param fileJournal formData file false "Upload Journal File" +// @Param fileAudio formData file false "Upload Audio File" +// @Param fileVideo formData file false "Upload Video File" +// +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /knowledge-base [post] +func (ctl *KnowledgeBaseController) Create(c *fiber.Ctx) error { + data, err := ctl.KnowledgeBaseService.Create(c) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"KnowledgeBase successfully created"}, + Data: data, + }) +} + +// Viewer KnowledgeBase +// @Summary Viewer KnowledgeBase +// @Description API for Viewer KnowledgeBase +// @Tags Knowledge Base +// @Security Bearer +// @Param filename path string true "KnowledgeBase File Name" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /knowledge-base/viewer/{filename} [get] +func (ctl *KnowledgeBaseController) Viewer(c *fiber.Ctx) error { + return ctl.KnowledgeBaseService.Viewer(c) +} + +// @Summary Update KnowledgeBase +// @Description API for updating KnowledgeBase +// @Tags Knowledge Base +// @Security Bearer +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param payload body request.KnowledgeBaseUpdateRequest true "Required payload" +// @Param id path int true "KnowledgeBase ID" +// @Success 200 {object} response.Response +// @Router /knowledge-base/{id} [put] +func (ctl *KnowledgeBaseController) Update(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + req := new(request.KnowledgeBaseUpdateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + data, err := ctl.KnowledgeBaseService.Update(uint(id), *req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"KnowledgeBase successfully updated"}, + Data: data, + }) +} + +// @Summary Delete KnowledgeBase +// @Description API for deleting KnowledgeBase (soft delete) +// @Tags Knowledge Base +// @Security Bearer +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param id path int true "KnowledgeBase ID" +// @Success 200 {object} response.Response +// @Router /knowledge-base/{id} [delete] +func (ctl *KnowledgeBaseController) Delete(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + err = ctl.KnowledgeBaseService.Delete(uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"KnowledgeBase successfully deleted"}, + }) +} + +// @Summary Update KnowledgeBase Status +// @Description API untuk update status KnowledgeBase +// @Tags Knowledge Base +// @Security Bearer +// @Param id path int true "KnowledgeBase ID" +// @Param payload body request.KnowledgeBaseUpdateStatusRequest true "Required payload" +// @Success 200 {object} response.Response +// @Router /knowledge-base/{id}/status [patch] +func (ctl *KnowledgeBaseController) UpdateStatus(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + req := new(request.KnowledgeBaseUpdateStatusRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + data, err := ctl.KnowledgeBaseService.UpdateStatus(uint(id), req.Status) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"KnowledgeBase status successfully updated"}, + Data: data, + }) +} \ No newline at end of file diff --git a/app/module/knowledge_base/knowledge_base.go b/app/module/knowledge_base/knowledge_base.go new file mode 100644 index 0000000..3e1cdfb --- /dev/null +++ b/app/module/knowledge_base/knowledge_base.go @@ -0,0 +1,56 @@ +package knowledge_base + +import ( + "narasi-ahli-be/app/module/knowledge_base/controller" + "narasi-ahli-be/app/module/knowledge_base/repository" + "narasi-ahli-be/app/module/knowledge_base/service" + + "github.com/gofiber/fiber/v2" + "go.uber.org/fx" +) + +// struct of KnowledgeBaseRouter +type KnowledgeBaseRouter struct { + App fiber.Router + Controller *controller.Controller +} + +// register bulky of KnowledgeBase module +var NewKnowledgeBaseModule = fx.Options( + // register repository of KnowledgeBase module + fx.Provide(repository.NewKnowledgeBaseRepository), + + // register service of KnowledgeBase module + fx.Provide(service.NewKnowledgeBaseService), + + // register controller of KnowledgeBase module + fx.Provide(controller.NewController), + + // register router of KnowledgeBase module + fx.Provide(NewKnowledgeBaseRouter), +) + +// init KnowledgeBaseRouter +func NewKnowledgeBaseRouter(fiber *fiber.App, controller *controller.Controller) *KnowledgeBaseRouter { + return &KnowledgeBaseRouter{ + App: fiber, + Controller: controller, + } +} + +// register routes of KnowledgeBase module +func (r *KnowledgeBaseRouter) RegisterKnowledgeBaseRoutes() { + kbController := r.Controller.KnowledgeBase + + r.App.Route("/knowledge-base", func(router fiber.Router) { + router.Get("/", kbController.All) + router.Get("/:id", kbController.Show) + router.Post("/", kbController.Create) + router.Put("/:id", kbController.Update) + router.Delete("/:id", kbController.Delete) + router.Get("/viewer/:filename", kbController.Viewer) + router.Patch("/:id/status", kbController.UpdateStatus) + + + }) +} diff --git a/app/module/knowledge_base/mapper/knowledge_base.mapper.go b/app/module/knowledge_base/mapper/knowledge_base.mapper.go new file mode 100644 index 0000000..1b7d6da --- /dev/null +++ b/app/module/knowledge_base/mapper/knowledge_base.mapper.go @@ -0,0 +1,31 @@ +package mapper + +import ( + "narasi-ahli-be/app/database/entity" + res "narasi-ahli-be/app/module/knowledge_base/response" +) + +func KnowledgeBaseResponseMapper(kb *entity.KnowledgeBase, host string) (out *res.KnowledgeBaseResponse) { + if kb == nil { + return nil + } + + out = &res.KnowledgeBaseResponse{ + ID: kb.ID, + AgentId: kb.AgentId, + AgentName: kb.AgentName, + CreatedById: kb.CreatedById, + Title: kb.Title, + Status: kb.Status, + + FileJournalUrl: kb.FileJournalUrl, + FileAudioUrl: kb.FileAudioUrl, + FileVideoUrl: kb.FileVideoUrl, + + IsActive: kb.IsActive, + CreatedAt: kb.CreatedAt, + UpdatedAt: kb.UpdatedAt, + } + + return out +} diff --git a/app/module/knowledge_base/repository/knowledge_base.repository.go b/app/module/knowledge_base/repository/knowledge_base.repository.go new file mode 100644 index 0000000..c5b1590 --- /dev/null +++ b/app/module/knowledge_base/repository/knowledge_base.repository.go @@ -0,0 +1,146 @@ +package repository + +import ( + "fmt" + "narasi-ahli-be/app/database" + "narasi-ahli-be/app/database/entity" + "narasi-ahli-be/app/module/knowledge_base/request" + "narasi-ahli-be/utils/paginator" + utilSvc "narasi-ahli-be/utils/service" + "strings" +) + +type KnowledgeBaseRepository struct { + DB *database.Database +} + +// interface +type KnowledgeBaseRepositoryInterface interface { + GetAll(req request.KnowledgeBaseQueryRequest) (data []*entity.KnowledgeBase, paging paginator.Pagination, err error) + FindOne(id uint) (data *entity.KnowledgeBase, err error) + FindByFilename(filename string) (data *entity.KnowledgeBase, fileType string, err error) + + Create(data *entity.KnowledgeBase) (err error) + Update(id uint, data *entity.KnowledgeBase) (err error) + Delete(id uint) (err error) +} + +func NewKnowledgeBaseRepository(db *database.Database) KnowledgeBaseRepositoryInterface { + return &KnowledgeBaseRepository{ + DB: db, + } +} + +func (r *KnowledgeBaseRepository) GetAll(req request.KnowledgeBaseQueryRequest) (data []*entity.KnowledgeBase, paging paginator.Pagination, err error) { + var count int64 + + query := r.DB.DB.Model(&entity.KnowledgeBase{}) + + query = query.Where("is_active = ?", true) + + if req.AgentId != nil && *req.AgentId != "" { + query = query.Where("agent_id = ?", *req.AgentId) + } + + if req.CreatedById != nil && *req.CreatedById > 0 { + query = query.Where("created_by_id = ?", *req.CreatedById) + } + + if req.Title != nil && strings.TrimSpace(*req.Title) != "" { + title := strings.ToLower(strings.TrimSpace(*req.Title)) + query = query.Where("LOWER(title) LIKE ?", "%"+title+"%") + } + + if req.Status != nil { + query = query.Where("status = ?", *req.Status) + } + + if req.IsActive != nil { + query = query.Where("is_active = ?", *req.IsActive) + } + + query.Count(&count) + + if req.Pagination != nil && req.Pagination.SortBy != "" { + direction := "ASC" + if strings.ToLower(req.Pagination.Sort) == "desc" { + direction = "DESC" + } + query = query.Order(fmt.Sprintf("%s %s", req.Pagination.SortBy, direction)) + } else { + query = query.Order("created_at DESC") + } + + if req.Pagination == nil { + req.Pagination = &paginator.Pagination{} + } + req.Pagination.Count = count + req.Pagination = paginator.Paging(req.Pagination) + + err = query. + Offset(req.Pagination.Offset). + Limit(req.Pagination.Limit). + Find(&data).Error + + if err != nil { + return nil, paging, err + } + + paging = *req.Pagination + return data, paging, nil +} + +func (r *KnowledgeBaseRepository) FindOne(id uint) (data *entity.KnowledgeBase, err error) { + query := r.DB.DB.Model(&entity.KnowledgeBase{}) + + if err := query.Where("id = ? AND is_active = ?", id, true).First(&data).Error; err != nil { + return nil, err + } + + return data, nil +} + +func (r *KnowledgeBaseRepository) Create(data *entity.KnowledgeBase) (err error) { + return r.DB.DB.Create(data).Error +} + +func (r *KnowledgeBaseRepository) Update(id uint, data *entity.KnowledgeBase) (err error) { + updateMap, err := utilSvc.StructToMap(data) + if err != nil { + return err + } + + return r.DB.DB.Model(&entity.KnowledgeBase{}). + Where("id = ?", id). + Updates(updateMap). + Error +} + +func (r *KnowledgeBaseRepository) Delete(id uint) (err error) { + return r.DB.DB.Delete(&entity.KnowledgeBase{}, id).Error +} + +func (r *KnowledgeBaseRepository) FindByFilename(filename string) (data *entity.KnowledgeBase, fileType string, err error) { + query := r.DB.DB.Model(&entity.KnowledgeBase{}).Where("is_active = ?", true) + + // cari berdasarkan url yang mengandung filename + like := "%" + filename + + // journal + var kb entity.KnowledgeBase + if err := query.Where("file_journal_url LIKE ?", like).First(&kb).Error; err == nil { + return &kb, "journal", nil + } + + // audio + if err := query.Where("file_audio_url LIKE ?", like).First(&kb).Error; err == nil { + return &kb, "audio", nil + } + + // video + if err := query.Where("file_video_url LIKE ?", like).First(&kb).Error; err == nil { + return &kb, "video", nil + } + + return nil, "", fmt.Errorf("file not found") +} diff --git a/app/module/knowledge_base/request/knowledge_base.request.go b/app/module/knowledge_base/request/knowledge_base.request.go new file mode 100644 index 0000000..d842183 --- /dev/null +++ b/app/module/knowledge_base/request/knowledge_base.request.go @@ -0,0 +1,125 @@ +package request + +import ( + "narasi-ahli-be/app/database/entity" + "narasi-ahli-be/utils/paginator" + "strconv" + "time" +) + +type KnowledgeBaseGeneric interface { + ToEntity() *entity.KnowledgeBase +} + +type KnowledgeBaseQueryRequest struct { + AgentId *string `json:"agentId"` + Title *string `json:"title"` + Status *int `json:"status"` + IsActive *bool `json:"isActive"` + CreatedById *uint `json:"createdById"` + Pagination *paginator.Pagination `json:"pagination"` +} + +type KnowledgeBaseUpdateStatusRequest struct { + Status int `json:"status" validate:"required"` +} + +type KnowledgeBaseCreateRequest struct { + AgentId *string `form:"agentId" validate:"required"` + AgentName *string `form:"agentName" validate:"required"` + CreatedById uint `form:"createdById" validate:"required"` + Title *string `form:"title" validate:"required"` + Status int `form:"status" validate:"required"` +} + +func (req KnowledgeBaseCreateRequest) ToEntity() *entity.KnowledgeBase { + kb := &entity.KnowledgeBase{ + AgentId: req.AgentId, + AgentName: req.AgentName, + CreatedById: req.CreatedById, + Title: req.Title, + Status: req.Status, + + } + + return kb +} + +type KnowledgeBaseUpdateRequest struct { + ID uint `json:"id" validate:"required"` + + AgentId *string `json:"agentId" validate:"required"` + AgentName *string `json:"agentName" validate:"required"` + Title *string `json:"title" validate:"required"` + Status int `json:"status" validate:"required"` + + FileJournalUrl *string `json:"fileJournalUrl"` + FileAudioUrl *string `json:"fileAudioUrl"` + FileVideoUrl *string `json:"fileVideoUrl"` + + IsActive *bool `json:"isActive"` +} + +func (req KnowledgeBaseUpdateRequest) ToEntity() *entity.KnowledgeBase { + kb := &entity.KnowledgeBase{ + ID: req.ID, + AgentId: req.AgentId, + AgentName: req.AgentName, + Title: req.Title, + Status: req.Status, + FileJournalUrl: req.FileJournalUrl, + FileAudioUrl: req.FileAudioUrl, + FileVideoUrl: req.FileVideoUrl, + UpdatedAt: time.Now(), + } + + if req.IsActive != nil { + kb.IsActive = *req.IsActive + } + + return kb +} + +type KnowledgeBaseQueryRequestContext struct { + AgentId string `json:"agentId"` + Title string `json:"title"` + Status string `json:"status"` + IsActive string `json:"isActive"` + CreatedById string `json:"createdById"` +} + +func (req KnowledgeBaseQueryRequestContext) ToParamRequest() KnowledgeBaseQueryRequest { + var request KnowledgeBaseQueryRequest + + if agentId := req.AgentId; agentId != "" { + request.AgentId = &agentId + } + + if title := req.Title; title != "" { + request.Title = &title + } + + if statusStr := req.Status; statusStr != "" { + status, err := strconv.Atoi(statusStr) + if err == nil { + request.Status = &status + } + } + + if isActiveStr := req.IsActive; isActiveStr != "" { + isActive, err := strconv.ParseBool(isActiveStr) + if err == nil { + request.IsActive = &isActive + } + } + + if createdByIdStr := req.CreatedById; createdByIdStr != "" { + id64, err := strconv.ParseUint(createdByIdStr, 10, 32) + if err == nil { + uid := uint(id64) + request.CreatedById = &uid + } + } + + return request +} diff --git a/app/module/knowledge_base/response/knowledge_base.response.go b/app/module/knowledge_base/response/knowledge_base.response.go new file mode 100644 index 0000000..fff3892 --- /dev/null +++ b/app/module/knowledge_base/response/knowledge_base.response.go @@ -0,0 +1,22 @@ +package response + +import "time" + +type KnowledgeBaseResponse struct { + ID uint `json:"id"` + + AgentId *string `json:"agentId"` + AgentName *string `json:"agentName"` + CreatedById uint `json:"createdById"` + + Title *string `json:"title"` + Status int `json:"status"` + + FileJournalUrl *string `json:"fileJournalUrl"` + FileAudioUrl *string `json:"fileAudioUrl"` + FileVideoUrl *string `json:"fileVideoUrl"` + + IsActive bool `json:"isActive"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` +} diff --git a/app/module/knowledge_base/service/async_uploader.service.go b/app/module/knowledge_base/service/async_uploader.service.go new file mode 100644 index 0000000..84474bd --- /dev/null +++ b/app/module/knowledge_base/service/async_uploader.service.go @@ -0,0 +1,69 @@ +package service + +import ( + "context" + "io" + "time" + + "github.com/minio/minio-go/v7" + "github.com/rs/zerolog" +) + +// AsyncUploader menangani proses upload secara asynchronous +type UploadService interface { + UploadFile(ctx context.Context, minioClient *minio.Client, uploadID string, reader io.Reader, bucketName string, objectName string, size int64, contentType string) error +} + +type uploadService struct { + uploadManager UploadManager + Log zerolog.Logger +} + +func NewUploadService(uploadManager UploadManager, log zerolog.Logger) UploadService { + return &uploadService{ + uploadManager: uploadManager, + Log: log, + } +} + +func (u *uploadService) UploadFile(ctx context.Context, minioClient *minio.Client, uploadID string, reader io.Reader, bucketName string, objectName string, size int64, contentType string) error { + status := &UploadStatus{ + FileName: objectName, + Size: size, + Progress: 0, + Status: "uploading", + ObjectName: objectName, + BucketName: bucketName, + StartTime: time.Now(), + } + + u.uploadManager.Add(uploadID, status) + + u.Log.Info().Str("timestamp", time.Now(). + Format(time.RFC3339)).Str("Service:Resource", "UploadService::UploadFile"). + Interface("add status", status).Msg("") + + // Upload ke Minio + _, err := minioClient.PutObject( + ctx, + bucketName, + objectName, + reader, + size, + minio.PutObjectOptions{ + ContentType: contentType, + PartSize: 10 * 1024 * 1024, // 10MB part size + }, + ) + + if err != nil { + u.uploadManager.UpdateStatus(uploadID, "error", err) + + u.Log.Info().Str("timestamp", time.Now(). + Format(time.RFC3339)).Str("Service:Resource", "UploadService::UploadFile"). + Interface("error when upload", err).Msg("") + } + + u.uploadManager.UpdateStatus(uploadID, "completed", nil) + return nil +} \ No newline at end of file diff --git a/app/module/knowledge_base/service/knowledge_base.service.go b/app/module/knowledge_base/service/knowledge_base.service.go new file mode 100644 index 0000000..47f56ac --- /dev/null +++ b/app/module/knowledge_base/service/knowledge_base.service.go @@ -0,0 +1,337 @@ +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) + + +} + +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 +} diff --git a/app/module/knowledge_base/service/upload_manager.service.go b/app/module/knowledge_base/service/upload_manager.service.go new file mode 100644 index 0000000..f02ec96 --- /dev/null +++ b/app/module/knowledge_base/service/upload_manager.service.go @@ -0,0 +1,71 @@ +package service + +import ( + "sync" + "time" +) + +type UploadStatus struct { + FileName string `json:"fileName"` + Size int64 `json:"size"` + Progress int `json:"progress"` + Status string `json:"status"` + ObjectName string `json:"objectName"` + BucketName string `json:"bucketName"` + StartTime time.Time `json:"startTime"` + Error string `json:"error,omitempty"` +} + +type UploadManager interface { + Add(uploadID string, status *UploadStatus) + UpdateProgress(uploadID string, progress int) + UpdateStatus(uploadID string, status string, err error) + Get(uploadID string) (*UploadStatus, bool) +} + +type uploadManager struct { + uploads map[string]*UploadStatus + mutex sync.RWMutex +} + +func NewUploadManager() UploadManager { + return &uploadManager{ + uploads: make(map[string]*UploadStatus), + } +} + +// Add menambahkan status upload baru +func (um *uploadManager) Add(uploadID string, status *UploadStatus) { + um.mutex.Lock() + defer um.mutex.Unlock() + um.uploads[uploadID] = status +} + +// UpdateProgress memperbarui progress upload +func (um *uploadManager) UpdateProgress(uploadID string, progress int) { + um.mutex.Lock() + defer um.mutex.Unlock() + if status, exists := um.uploads[uploadID]; exists { + status.Progress = progress + } +} + +// UpdateStatus memperbarui status upload +func (um *uploadManager) UpdateStatus(uploadID string, status string, err error) { + um.mutex.Lock() + defer um.mutex.Unlock() + if upload, exists := um.uploads[uploadID]; exists { + upload.Status = status + if err != nil { + upload.Error = err.Error() + } + } +} + +// Get mendapatkan status upload berdasarkan ID +func (um *uploadManager) Get(uploadID string) (*UploadStatus, bool) { + um.mutex.RLock() + defer um.mutex.RUnlock() + status, exists := um.uploads[uploadID] + return status, exists +} diff --git a/app/router/api.go b/app/router/api.go index b6290d3..319fc3f 100644 --- a/app/router/api.go +++ b/app/router/api.go @@ -18,6 +18,7 @@ import ( "narasi-ahli-be/app/module/ebooks" "narasi-ahli-be/app/module/education_history" "narasi-ahli-be/app/module/feedbacks" + "narasi-ahli-be/app/module/knowledge_base" "narasi-ahli-be/app/module/magazine_files" "narasi-ahli-be/app/module/magazines" "narasi-ahli-be/app/module/master_menus" @@ -44,6 +45,7 @@ type Router struct { ActivityLogsRouter *activity_logs.ActivityLogsRouter AdvertisementRouter *advertisement.AdvertisementRouter AIChatRouter *ai_chat.AIChatRouter + KnowledgeBaseRouter *knowledge_base.KnowledgeBaseRouter ArticleCategoriesRouter *article_categories.ArticleCategoriesRouter ArticleCategoryDetailsRouter *article_category_details.ArticleCategoryDetailsRouter ArticleFilesRouter *article_files.ArticleFilesRouter @@ -80,6 +82,7 @@ func NewRouter( activityLogsRouter *activity_logs.ActivityLogsRouter, advertisementRouter *advertisement.AdvertisementRouter, aiChatRouter *ai_chat.AIChatRouter, + knowledgeBaseRouter *knowledge_base.KnowledgeBaseRouter, articleCategoriesRouter *article_categories.ArticleCategoriesRouter, articleCategoryDetailsRouter *article_category_details.ArticleCategoryDetailsRouter, articleFilesRouter *article_files.ArticleFilesRouter, @@ -119,6 +122,7 @@ func NewRouter( ArticleCommentsRouter: articleCommentsRouter, ArticleApprovalsRouter: articleApprovalsRouter, ArticlesRouter: articlesRouter, + KnowledgeBaseRouter: knowledgeBaseRouter, ChatRouter: chatRouter, CitiesRouter: citiesRouter, CustomStaticPagesRouter: customStaticPagesRouter, @@ -162,6 +166,7 @@ func (r *Router) Register() { r.ArticleApprovalsRouter.RegisterArticleApprovalsRoutes() r.ArticlesRouter.RegisterArticlesRoutes() r.ArticleCommentsRouter.RegisterArticleCommentsRoutes() + r.KnowledgeBaseRouter.RegisterKnowledgeBaseRoutes() r.ChatRouter.RegisterChatRoutes() r.CitiesRouter.RegisterCitiesRoutes() r.CustomStaticPagesRouter.RegisterCustomStaticPagesRoutes() diff --git a/docs/swagger/docs.go b/docs/swagger/docs.go index 6f94d76..aba017a 100644 --- a/docs/swagger/docs.go +++ b/docs/swagger/docs.go @@ -10256,6 +10256,379 @@ const docTemplate = `{ } } }, + "/knowledge-base": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all KnowledgeBase", + "tags": [ + "Knowledge Base" + ], + "summary": "Get all KnowledgeBase", + "parameters": [ + { + "type": "string", + "description": "Agent ID", + "name": "agentId", + "in": "query" + }, + { + "type": "string", + "description": "Search title", + "name": "title", + "in": "query" + }, + { + "type": "integer", + "description": "Status (0=draft,1=published,2=archived)", + "name": "status", + "in": "query" + }, + { + "type": "integer", + "description": "Created By ID", + "name": "createdById", + "in": "query" + }, + { + "type": "boolean", + "description": "Is active", + "name": "isActive", + "in": "query" + }, + { + "type": "integer", + "description": "Page", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "Limit", + "name": "limit", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for creating KnowledgeBase with upload file", + "consumes": [ + "multipart/form-data" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Knowledge Base" + ], + "summary": "Create KnowledgeBase", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "string", + "description": "Agent ID", + "name": "agentId", + "in": "formData", + "required": true + }, + { + "type": "string", + "description": "Agent Name", + "name": "agentName", + "in": "formData", + "required": true + }, + { + "type": "integer", + "description": "Created By ID", + "name": "createdById", + "in": "formData", + "required": true + }, + { + "type": "integer", + "description": "Status (integer)", + "name": "status", + "in": "formData", + "required": true + }, + { + "type": "string", + "description": "Title", + "name": "title", + "in": "formData", + "required": true + }, + { + "type": "file", + "description": "Upload Journal File", + "name": "fileJournal", + "in": "formData" + }, + { + "type": "file", + "description": "Upload Audio File", + "name": "fileAudio", + "in": "formData" + }, + { + "type": "file", + "description": "Upload Video File", + "name": "fileVideo", + "in": "formData" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/knowledge-base/viewer/{filename}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for Viewer KnowledgeBase", + "tags": [ + "Knowledge Base" + ], + "summary": "Viewer KnowledgeBase", + "parameters": [ + { + "type": "string", + "description": "KnowledgeBase File Name", + "name": "filename", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/knowledge-base/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one KnowledgeBase", + "tags": [ + "Knowledge Base" + ], + "summary": "Get one KnowledgeBase", + "parameters": [ + { + "type": "integer", + "description": "KnowledgeBase ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for updating KnowledgeBase", + "tags": [ + "Knowledge Base" + ], + "summary": "Update KnowledgeBase", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.KnowledgeBaseUpdateRequest" + } + }, + { + "type": "integer", + "description": "KnowledgeBase ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for deleting KnowledgeBase (soft delete)", + "tags": [ + "Knowledge Base" + ], + "summary": "Delete KnowledgeBase", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "KnowledgeBase ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/knowledge-base/{id}/status": { + "patch": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API untuk update status KnowledgeBase", + "tags": [ + "Knowledge Base" + ], + "summary": "Update KnowledgeBase Status", + "parameters": [ + { + "type": "integer", + "description": "KnowledgeBase ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.KnowledgeBaseUpdateStatusRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, "/magazine-files": { "get": { "security": [ @@ -16630,6 +17003,56 @@ const docTemplate = `{ } } }, + "request.KnowledgeBaseUpdateRequest": { + "type": "object", + "required": [ + "agentId", + "agentName", + "id", + "status", + "title" + ], + "properties": { + "agentId": { + "type": "string" + }, + "agentName": { + "type": "string" + }, + "fileAudioUrl": { + "type": "string" + }, + "fileJournalUrl": { + "type": "string" + }, + "fileVideoUrl": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "isActive": { + "type": "boolean" + }, + "status": { + "type": "integer" + }, + "title": { + "type": "string" + } + } + }, + "request.KnowledgeBaseUpdateStatusRequest": { + "type": "object", + "required": [ + "status" + ], + "properties": { + "status": { + "type": "integer" + } + } + }, "request.MagazinesCreateRequest": { "type": "object", "required": [ diff --git a/docs/swagger/swagger.json b/docs/swagger/swagger.json index 924c693..3ee56b3 100644 --- a/docs/swagger/swagger.json +++ b/docs/swagger/swagger.json @@ -10245,6 +10245,379 @@ } } }, + "/knowledge-base": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all KnowledgeBase", + "tags": [ + "Knowledge Base" + ], + "summary": "Get all KnowledgeBase", + "parameters": [ + { + "type": "string", + "description": "Agent ID", + "name": "agentId", + "in": "query" + }, + { + "type": "string", + "description": "Search title", + "name": "title", + "in": "query" + }, + { + "type": "integer", + "description": "Status (0=draft,1=published,2=archived)", + "name": "status", + "in": "query" + }, + { + "type": "integer", + "description": "Created By ID", + "name": "createdById", + "in": "query" + }, + { + "type": "boolean", + "description": "Is active", + "name": "isActive", + "in": "query" + }, + { + "type": "integer", + "description": "Page", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "Limit", + "name": "limit", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for creating KnowledgeBase with upload file", + "consumes": [ + "multipart/form-data" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Knowledge Base" + ], + "summary": "Create KnowledgeBase", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "string", + "description": "Agent ID", + "name": "agentId", + "in": "formData", + "required": true + }, + { + "type": "string", + "description": "Agent Name", + "name": "agentName", + "in": "formData", + "required": true + }, + { + "type": "integer", + "description": "Created By ID", + "name": "createdById", + "in": "formData", + "required": true + }, + { + "type": "integer", + "description": "Status (integer)", + "name": "status", + "in": "formData", + "required": true + }, + { + "type": "string", + "description": "Title", + "name": "title", + "in": "formData", + "required": true + }, + { + "type": "file", + "description": "Upload Journal File", + "name": "fileJournal", + "in": "formData" + }, + { + "type": "file", + "description": "Upload Audio File", + "name": "fileAudio", + "in": "formData" + }, + { + "type": "file", + "description": "Upload Video File", + "name": "fileVideo", + "in": "formData" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/knowledge-base/viewer/{filename}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for Viewer KnowledgeBase", + "tags": [ + "Knowledge Base" + ], + "summary": "Viewer KnowledgeBase", + "parameters": [ + { + "type": "string", + "description": "KnowledgeBase File Name", + "name": "filename", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/knowledge-base/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one KnowledgeBase", + "tags": [ + "Knowledge Base" + ], + "summary": "Get one KnowledgeBase", + "parameters": [ + { + "type": "integer", + "description": "KnowledgeBase ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for updating KnowledgeBase", + "tags": [ + "Knowledge Base" + ], + "summary": "Update KnowledgeBase", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.KnowledgeBaseUpdateRequest" + } + }, + { + "type": "integer", + "description": "KnowledgeBase ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for deleting KnowledgeBase (soft delete)", + "tags": [ + "Knowledge Base" + ], + "summary": "Delete KnowledgeBase", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "KnowledgeBase ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/knowledge-base/{id}/status": { + "patch": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API untuk update status KnowledgeBase", + "tags": [ + "Knowledge Base" + ], + "summary": "Update KnowledgeBase Status", + "parameters": [ + { + "type": "integer", + "description": "KnowledgeBase ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.KnowledgeBaseUpdateStatusRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, "/magazine-files": { "get": { "security": [ @@ -16619,6 +16992,56 @@ } } }, + "request.KnowledgeBaseUpdateRequest": { + "type": "object", + "required": [ + "agentId", + "agentName", + "id", + "status", + "title" + ], + "properties": { + "agentId": { + "type": "string" + }, + "agentName": { + "type": "string" + }, + "fileAudioUrl": { + "type": "string" + }, + "fileJournalUrl": { + "type": "string" + }, + "fileVideoUrl": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "isActive": { + "type": "boolean" + }, + "status": { + "type": "integer" + }, + "title": { + "type": "string" + } + } + }, + "request.KnowledgeBaseUpdateStatusRequest": { + "type": "object", + "required": [ + "status" + ], + "properties": { + "status": { + "type": "integer" + } + } + }, "request.MagazinesCreateRequest": { "type": "object", "required": [ diff --git a/docs/swagger/swagger.yaml b/docs/swagger/swagger.yaml index 45f150f..b5223c2 100644 --- a/docs/swagger/swagger.yaml +++ b/docs/swagger/swagger.yaml @@ -812,6 +812,40 @@ definitions: - id - message type: object + request.KnowledgeBaseUpdateRequest: + properties: + agentId: + type: string + agentName: + type: string + fileAudioUrl: + type: string + fileJournalUrl: + type: string + fileVideoUrl: + type: string + id: + type: integer + isActive: + type: boolean + status: + type: integer + title: + type: string + required: + - agentId + - agentName + - id + - status + - title + type: object + request.KnowledgeBaseUpdateStatusRequest: + properties: + status: + type: integer + required: + - status + type: object request.MagazinesCreateRequest: properties: createdById: @@ -7930,6 +7964,246 @@ paths: summary: FeedbackMonthlyStats Feedbacks tags: - Feedbacks + /knowledge-base: + get: + description: API for getting all KnowledgeBase + parameters: + - description: Agent ID + in: query + name: agentId + type: string + - description: Search title + in: query + name: title + type: string + - description: Status (0=draft,1=published,2=archived) + in: query + name: status + type: integer + - description: Created By ID + in: query + name: createdById + type: integer + - description: Is active + in: query + name: isActive + type: boolean + - description: Page + in: query + name: page + type: integer + - description: Limit + in: query + name: limit + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + security: + - Bearer: [] + summary: Get all KnowledgeBase + tags: + - Knowledge Base + post: + consumes: + - multipart/form-data + description: API for creating KnowledgeBase with upload file + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - description: Agent ID + in: formData + name: agentId + required: true + type: string + - description: Agent Name + in: formData + name: agentName + required: true + type: string + - description: Created By ID + in: formData + name: createdById + required: true + type: integer + - description: Status (integer) + in: formData + name: status + required: true + type: integer + - description: Title + in: formData + name: title + required: true + type: string + - description: Upload Journal File + in: formData + name: fileJournal + type: file + - description: Upload Audio File + in: formData + name: fileAudio + type: file + - description: Upload Video File + in: formData + name: fileVideo + type: file + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Create KnowledgeBase + tags: + - Knowledge Base + /knowledge-base/{id}: + delete: + description: API for deleting KnowledgeBase (soft delete) + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - description: KnowledgeBase ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + security: + - Bearer: [] + summary: Delete KnowledgeBase + tags: + - Knowledge Base + get: + description: API for getting one KnowledgeBase + parameters: + - description: KnowledgeBase ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + security: + - Bearer: [] + summary: Get one KnowledgeBase + tags: + - Knowledge Base + put: + description: API for updating KnowledgeBase + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: '#/definitions/request.KnowledgeBaseUpdateRequest' + - description: KnowledgeBase ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + security: + - Bearer: [] + summary: Update KnowledgeBase + tags: + - Knowledge Base + /knowledge-base/{id}/status: + patch: + description: API untuk update status KnowledgeBase + parameters: + - description: KnowledgeBase ID + in: path + name: id + required: true + type: integer + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: '#/definitions/request.KnowledgeBaseUpdateStatusRequest' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + security: + - Bearer: [] + summary: Update KnowledgeBase Status + tags: + - Knowledge Base + /knowledge-base/viewer/{filename}: + get: + description: API for Viewer KnowledgeBase + parameters: + - description: KnowledgeBase File Name + in: path + name: filename + required: true + type: string + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Viewer KnowledgeBase + tags: + - Knowledge Base /magazine-files: get: description: API for getting all MagazineFiles diff --git a/main.go b/main.go index 27a0a2a..57f8c4b 100644 --- a/main.go +++ b/main.go @@ -20,6 +20,7 @@ import ( "narasi-ahli-be/app/module/ebooks" "narasi-ahli-be/app/module/education_history" "narasi-ahli-be/app/module/feedbacks" + "narasi-ahli-be/app/module/knowledge_base" "narasi-ahli-be/app/module/magazine_files" "narasi-ahli-be/app/module/magazines" "narasi-ahli-be/app/module/master_menus" @@ -70,6 +71,7 @@ func main() { activity_logs.NewActivityLogsModule, advertisement.NewAdvertisementModule, ai_chat.NewAIChatModule, + knowledge_base.NewKnowledgeBaseModule, article_categories.NewArticleCategoriesModule, article_category_details.NewArticleCategoryDetailsModule, article_files.NewArticleFilesModule,