fix:toml, yaml, temp upload file

This commit is contained in:
Rama Priyanto 2025-12-18 07:38:32 +07:00
parent 38a72b74c6
commit 52b6432430
19 changed files with 2568 additions and 8 deletions

View File

@ -13,10 +13,10 @@ stages:
build-2:
stage: build-image
image: docker/compose:latest
image: docker/compose:25.0.3-cli
services:
- name: docker:dind
command: [ "--insecure-registry=103.82.242.92:8900" ]
- name: docker:25.0.3-dind
command: ["--insecure-registry=103.82.242.92:8900"]
script:
- docker login -u $DEPLOY_USERNAME -p $DEPLOY_TOKEN 103.82.242.92:8900
- docker-compose build
@ -30,4 +30,4 @@ deploy:
services:
- docker:dind
script:
- curl --user $JENKINS_USER:$JENKINS_PWD http://38.47.180.165:8080/job/autodeploy-narasiahli-be/build?token=autodeploynarasiahli
- curl --user $JENKINS_USER:$JENKINS_PWD http://103.31.38.120:8080/job/autodeploy-narasiahli-be/build?token=autodeploynarasiahli

View File

@ -0,0 +1,22 @@
package entity
import (
"time"
)
type AiChatFiles struct {
ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"`
MessageId uint `json:"message_id" gorm:"type:int4"`
UploadID *string `json:"upload_id" gorm:"type:varchar"`
Type string `json:"type" gorm:"type:varchar"`
FilePath *string `json:"file_path" gorm:"type:varchar"`
FileUrl *string `json:"file_url" gorm:"type:varchar"`
FileName *string `json:"file_name" gorm:"type:varchar"`
FileThumbnail *string `json:"file_thumbnail" gorm:"type:varchar"`
FileAlt *string `json:"file_alt" gorm:"type:varchar"`
Size *string `json:"size" gorm:"type:varchar"`
FileType *string `json:"file_type" 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()"`
}

View File

@ -129,6 +129,7 @@ func Models() []interface{} {
entity.AIChatSessions{},
entity.AIChatMessages{},
entity.AIChatLogs{},
entity.AiChatFiles{},
// Ebook entities
entity.Ebooks{},

View File

@ -0,0 +1,59 @@
package ai_chat_files
import (
"narasi-ahli-be/app/module/ai_chat_files/controller"
"narasi-ahli-be/app/module/ai_chat_files/repository"
"narasi-ahli-be/app/module/ai_chat_files/service"
"github.com/gofiber/fiber/v2"
"go.uber.org/fx"
)
// struct of ArticleFilesRouter
type AiChatFilesRouter struct {
App fiber.Router
Controller *controller.Controller
}
// register bulky of AiChatFiles module
var NewAiChatFilesModule = fx.Options(
// register repository of AiChatFiles module
fx.Provide(repository.NewAiChatFilesRepository),
// register service of AiChatFiles module
fx.Provide(service.NewAiChatFilesService),
fx.Provide(service.NewUploadService),
fx.Provide(service.NewUploadManager),
// register controller of AiChatFiles module
fx.Provide(controller.NewController),
// register router of AiChatFiles module
fx.Provide(NewAiChatFilesRouter),
)
// init AiChatFilesRouter
func NewAiChatFilesRouter(fiber *fiber.App, controller *controller.Controller) *AiChatFilesRouter {
return &AiChatFilesRouter{
App: fiber,
Controller: controller,
}
}
// register routes of AiChatFiles module
func (_i *AiChatFilesRouter) RegisterAiChatFilesRoutes() {
// define controllers
aiChatFilesController := _i.Controller.AiChatFiles
// define routes
_i.App.Route("/ai-chat-files", func(router fiber.Router) {
router.Get("/", aiChatFilesController.All)
router.Get("/:id", aiChatFilesController.Show)
router.Post("/:messageId", aiChatFilesController.Save)
router.Put("/:id", aiChatFilesController.Update)
router.Delete("/:id", aiChatFilesController.Delete)
router.Get("/viewer/:filename", aiChatFilesController.Viewer)
router.Get("/upload-status/:uploadId", aiChatFilesController.GetUploadStatus)
})
}

View File

@ -0,0 +1,239 @@
package controller
import (
"fmt"
"narasi-ahli-be/app/module/ai_chat_files/request"
"narasi-ahli-be/app/module/ai_chat_files/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 aiChatFilesController struct {
aiChatFilesService service.AiChatFilesService
}
type AiChatFilesController interface {
All(c *fiber.Ctx) error
Show(c *fiber.Ctx) error
Save(c *fiber.Ctx) error
Update(c *fiber.Ctx) error
Delete(c *fiber.Ctx) error
Viewer(c *fiber.Ctx) error
GetUploadStatus(c *fiber.Ctx) error
}
func NewAiChatFilesController(aiChatFilesService service.AiChatFilesService) AiChatFilesController {
return &aiChatFilesController{
aiChatFilesService: aiChatFilesService,
}
}
// All AIChatFiles
// @Summary Get all AiChatFiles
// @Description API for getting all AiChatFiles
// @Tags AiChat Files
// @Security Bearer
// @Param req query request.AiChatFilesQueryRequest false "query parameters"
// @Param req query paginator.Pagination false "pagination parameters"
// @Success 200 {object} response.Response
// @Failure 400 {object} response.BadRequestError
// @Failure 401 {object} response.UnauthorizedError
// @Failure 500 {object} response.InternalServerError
// @Router /ai-chat-files [get]
func (_i *aiChatFilesController) All(c *fiber.Ctx) error {
// Get from context
paginate, err := paginator.Paginate(c)
if err != nil {
return err
}
reqContext := request.AiChatFilesQueryRequestContext{
MessageId: c.Query("messageId"),
FileName: c.Query("fileName"),
}
req := reqContext.ToParamRequest()
req.Pagination = paginate
aiChatFilesData, paging, err := _i.aiChatFilesService.All(req)
if err != nil {
return err
}
return utilRes.Resp(c, utilRes.Response{
Success: true,
Messages: utilRes.Messages{"AiChatFiles list successfully retrieved"},
Data: aiChatFilesData,
Meta: paging,
})
}
// Show AiChatFiles
// @Summary Get one AiChatFiles
// @Description API for getting one AiChatFiles
// @Tags AiChat Files
// @Security Bearer
// @Param id path int true "AiChatFiles ID"
// @Success 200 {object} response.Response
// @Failure 400 {object} response.BadRequestError
// @Failure 401 {object} response.UnauthorizedError
// @Failure 500 {object} response.InternalServerError
// @Router /ai-chat-files/{id} [get]
func (_i *aiChatFilesController) Show(c *fiber.Ctx) error {
// Get from context
id, err := strconv.ParseUint(c.Params("id"), 10, 0)
if err != nil {
return err
}
aiChatFilesData, err := _i.aiChatFilesService.Show(uint(id))
if err != nil {
return err
}
return utilRes.Resp(c, utilRes.Response{
Success: true,
Messages: utilRes.Messages{"AiChatFiles successfully retrieved"},
Data: aiChatFilesData,
})
}
// Save AiChatFiles
// @Summary Upload AiChatFiles
// @Description API for create AiChatFiles
// @Tags AiChat Files
// @Security Bearer
// @Produce json
// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token"
// @Param files formData file true "Upload file" multiple true
// @Param aiChatId path int true "AiChat ID"
// @Success 200 {object} response.Response
// @Failure 400 {object} response.BadRequestError
// @Failure 401 {object} response.UnauthorizedError
// @Failure 500 {object} response.InternalServerError
// @Router /ai-chat-files/{messageId} [post]
func (_i *aiChatFilesController) Save(c *fiber.Ctx) error {
// Get from context
id, err := strconv.ParseUint(c.Params("messageId"), 10, 0)
if err != nil {
return err
}
err = _i.aiChatFilesService.Save(c, uint(id))
if err != nil {
return err
}
return utilRes.Resp(c, utilRes.Response{
Success: true,
Messages: utilRes.Messages{"AiChatFiles successfully upload"},
})
}
// Update AiChatFiles
// @Summary Update AiChatFiles
// @Description API for update AiChatFiles
// @Tags AiChat Files
// @Security Bearer
// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token"
// @Param payload body request.AiChatFilesUpdateRequest true "Required payload"
// @Param id path int true "AiChatFiles ID"
// @Success 200 {object} response.Response
// @Failure 400 {object} response.BadRequestError
// @Failure 401 {object} response.UnauthorizedError
// @Failure 500 {object} response.InternalServerError
// @Router /aiChat-files/{id} [put]
func (_i *aiChatFilesController) Update(c *fiber.Ctx) error {
// Get from context
id, err := strconv.ParseUint(c.Params("id"), 10, 0)
if err != nil {
return err
}
req := new(request.AiChatFilesUpdateRequest)
if err := utilVal.ParseAndValidate(c, req); err != nil {
return err
}
err = _i.aiChatFilesService.Update(uint(id), *req)
if err != nil {
return err
}
return utilRes.Resp(c, utilRes.Response{
Success: true,
Messages: utilRes.Messages{"AiChatFiles successfully updated"},
})
}
// Delete AiChatFiles
// @Summary Delete AiChatFiles
// @Description API for delete AiChatFiles
// @Tags AiChat Files
// @Security Bearer
// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token"
// @Param id path int true "AiChatFiles ID"
// @Success 200 {object} response.Response
// @Failure 400 {object} response.BadRequestError
// @Failure 401 {object} response.UnauthorizedError
// @Failure 500 {object} response.InternalServerError
// @Router /aiChat-files/{id} [delete]
func (_i *aiChatFilesController) Delete(c *fiber.Ctx) error {
// Get from context
id, err := strconv.ParseUint(c.Params("id"), 10, 0)
if err != nil {
return err
}
err = _i.aiChatFilesService.Delete(uint(id))
if err != nil {
return err
}
return utilRes.Resp(c, utilRes.Response{
Success: true,
Messages: utilRes.Messages{"AiChatFiles successfully deleted"},
})
}
// Viewer AiChatFiles
// @Summary Viewer AiChatFiles
// @Description API for Viewer AiChatFiles
// @Tags AiChat Files
// @Security Bearer
// @Param filename path string true "AiChat File Name"
// @Success 200 {object} response.Response
// @Failure 400 {object} response.BadRequestError
// @Failure 401 {object} response.UnauthorizedError
// @Failure 500 {object} response.InternalServerError
// @Router /aiChat-files/viewer/{filename} [get]
func (_i *aiChatFilesController) Viewer(c *fiber.Ctx) error {
// Get from context
return _i.aiChatFilesService.Viewer(c)
}
// GetUploadStatus AiChatFiles
// @Summary GetUploadStatus AiChatFiles
// @Description API for GetUploadStatus AiChatFiles
// @Tags AiChat Files
// @Security Bearer
// @Param uploadId path string true "Upload ID of AiChatFiles"
// @Success 200 {object} response.Response
// @Failure 400 {object} response.BadRequestError
// @Failure 401 {object} response.UnauthorizedError
// @Failure 500 {object} response.InternalServerError
// @Router /aiChat-files/upload-status/{uploadId} [get]
func (_i *aiChatFilesController) GetUploadStatus(c *fiber.Ctx) error {
progress, _ := _i.aiChatFilesService.GetUploadStatus(c)
progressMessage := fmt.Sprintf("Upload Progress: %d%%", progress)
return utilRes.Resp(c, utilRes.Response{
Success: true,
Messages: utilRes.Messages{"Upload Status Retrieve"},
Data: progressMessage,
})
}

View File

@ -0,0 +1,13 @@
package controller
import "narasi-ahli-be/app/module/ai_chat_files/service"
type Controller struct {
AiChatFiles AiChatFilesController
}
func NewController(AiChatFilesService service.AiChatFilesService) *Controller {
return &Controller{
AiChatFiles: NewAiChatFilesController(AiChatFilesService),
}
}

View File

@ -0,0 +1,29 @@
package mapper
import (
"narasi-ahli-be/app/database/entity"
res "narasi-ahli-be/app/module/ai_chat_files/response"
)
func AiChatFilesResponseMapper(aiChatFilesReq *entity.AiChatFiles, host string) (aiChatFilesRes *res.AiChatFilesResponse) {
fileUrl := host + "/ai-chat-files/viewer/"
if aiChatFilesReq.FileName != nil {
fileUrl += *aiChatFilesReq.FileName
}
if aiChatFilesReq != nil {
aiChatFilesRes = &res.AiChatFilesResponse{
ID: aiChatFilesReq.ID,
MessageId: aiChatFilesReq.MessageId,
FilePath: aiChatFilesReq.FilePath,
FileUrl: &fileUrl,
FileName: aiChatFilesReq.FileName,
FileAlt: aiChatFilesReq.FileAlt,
Size: aiChatFilesReq.Size,
IsActive: aiChatFilesReq.IsActive,
CreatedAt: aiChatFilesReq.CreatedAt,
UpdatedAt: aiChatFilesReq.UpdatedAt,
}
}
return aiChatFilesRes
}

View File

@ -0,0 +1,123 @@
package repository
import (
"fmt"
"narasi-ahli-be/app/database"
"narasi-ahli-be/app/database/entity"
"narasi-ahli-be/app/module/ai_chat_files/request"
"narasi-ahli-be/utils/paginator"
utilSvc "narasi-ahli-be/utils/service"
"strings"
)
type aiChatFilesRepository struct {
DB *database.Database
}
// AiChatFilesRepository define interface of IAiChatFilesRepository
type AiChatFilesRepository interface {
GetAll(req request.AiChatFilesQueryRequest) (aiChatFiles []*entity.AiChatFiles, paging paginator.Pagination, err error)
FindOne(id uint) (aiChatFiles *entity.AiChatFiles, err error)
FindByAiChat(messageId uint) (aiChatFiles []*entity.AiChatFiles, err error)
FindByFilename(filename string) (aiChatFiles *entity.AiChatFiles, err error)
Create(aiChatFiles *entity.AiChatFiles) (err error)
Update(id uint, aiChatFiles *entity.AiChatFiles) (err error)
Delete(id uint) (err error)
}
func NewAiChatFilesRepository(db *database.Database) AiChatFilesRepository {
return &aiChatFilesRepository{
DB: db,
}
}
// implement interface of IAiChatFilesRepository
func (_i *aiChatFilesRepository) GetAll(req request.AiChatFilesQueryRequest) (aiChatFiles []*entity.AiChatFiles, paging paginator.Pagination, err error) {
var count int64
query := _i.DB.DB.Model(&entity.AiChatFiles{})
query = query.Where("is_active = ?", true)
if req.MessageId != nil {
query = query.Where("message_id = ?", req.MessageId)
}
if req.FileName != nil && *req.FileName != "" {
fileName := strings.ToLower(*req.FileName)
query = query.Where("LOWER(file_name) LIKE ?", "%"+strings.ToLower(fileName)+"%")
}
if req.IsPublish != nil {
query = query.Where("is_publish = ?", req.IsPublish)
}
if req.StatusId != nil {
query = query.Where("status_id = ?", req.StatusId)
}
query.Count(&count)
if req.Pagination.SortBy != "" {
direction := "ASC"
if req.Pagination.Sort == "desc" {
direction = "DESC"
}
query.Order(fmt.Sprintf("%s %s", req.Pagination.SortBy, direction))
}
req.Pagination.Count = count
req.Pagination = paginator.Paging(req.Pagination)
err = query.Offset(req.Pagination.Offset).Limit(req.Pagination.Limit).Find(&aiChatFiles).Error
if err != nil {
return
}
paging = *req.Pagination
return
}
func (_i *aiChatFilesRepository) FindOne(id uint) (aiChatFiles *entity.AiChatFiles, err error) {
query := _i.DB.DB
if err := query.First(&aiChatFiles, id).Error; err != nil {
return nil, err
}
return aiChatFiles, nil
}
func (_i *aiChatFilesRepository) FindByAiChat(messageId uint) (aiChatFiles []*entity.AiChatFiles, err error) {
query := _i.DB.DB.Where("message_id = ?", messageId)
if err := query.Find(&aiChatFiles).Error; err != nil {
return nil, err
}
return aiChatFiles, nil
}
func (_i *aiChatFilesRepository) FindByFilename(aiChatFilename string) (aiChatFiles *entity.AiChatFiles, err error) {
query := _i.DB.DB.Where("file_name = ?", aiChatFilename)
if err := query.First(&aiChatFiles).Error; err != nil {
return nil, err
}
return aiChatFiles, nil
}
func (_i *aiChatFilesRepository) Create(aiChatFiles *entity.AiChatFiles) (err error) {
return _i.DB.DB.Create(aiChatFiles).Error
}
func (_i *aiChatFilesRepository) Update(id uint, aiChatFiles *entity.AiChatFiles) (err error) {
aiChatFilesMap, err := utilSvc.StructToMap(aiChatFiles)
if err != nil {
return err
}
return _i.DB.DB.Model(&entity.AiChatFiles{}).
Where(&entity.AiChatFiles{ID: id}).
Updates(aiChatFilesMap).Error
}
func (_i *aiChatFilesRepository) Delete(id uint) error {
return _i.DB.DB.Delete(&entity.AiChatFiles{}, id).Error
}

View File

@ -0,0 +1,112 @@
package request
import (
"narasi-ahli-be/app/database/entity"
"narasi-ahli-be/utils/paginator"
"strconv"
"time"
)
type AiChatFilesGeneric interface {
ToEntity()
}
type AiChatFilesQueryRequest struct {
MessageId *int `json:"messageId"`
FileName *string `json:"fileName"`
StatusId *int `json:"statusId"`
IsPublish *bool `json:"isPublish"`
Pagination *paginator.Pagination `json:"pagination"`
}
type AiChatFilesCreateRequest struct {
MessageId uint `json:"messageId" validate:"required"`
StatusId int `json:"statusId" validate:"required"`
UploadId *string `json:"uploadId"`
FilePath *string `json:"filePath"`
FileUrl *string `json:"fileUrl"`
FileName *string `json:"fileName"`
FileThumbnail *string `json:"fileThumbnail"`
FileAlt *string `json:"fileAlt"`
WidthPixel *string `json:"widthPixel"`
HeightPixel *string `json:"heightPixel"`
Size *string `json:"size"`
}
func (req AiChatFilesCreateRequest) ToEntity() *entity.AiChatFiles {
return &entity.AiChatFiles{
MessageId: req.MessageId,
UploadID: req.UploadId,
FilePath: req.FilePath,
FileUrl: req.FileUrl,
FileName: req.FileName,
FileThumbnail: req.FileThumbnail,
FileAlt: req.FileAlt,
Size: req.Size,
}
}
type AiChatFilesUpdateRequest struct {
ID uint `json:"id" validate:"required"`
MessageId uint `json:"messageId" validate:"required"`
StatusId int `json:"statusId" validate:"required"`
FilePath *string `json:"filePath"`
FileUrl *string `json:"fileUrl"`
FileName *string `json:"fileName"`
FileThumbnail *string `json:"fileThumbnail"`
FileAlt *string `json:"fileAlt"`
WidthPixel *string `json:"widthPixel"`
HeightPixel *string `json:"heightPixel"`
Size *string `json:"size"`
IsPublish *bool `json:"isPublish" validate:"required"`
PublishedAt *time.Time `json:"publishedAt" validate:"required"`
}
func (req AiChatFilesUpdateRequest) ToEntity() *entity.AiChatFiles {
return &entity.AiChatFiles{
ID: req.ID,
MessageId: req.MessageId,
FilePath: req.FilePath,
FileUrl: req.FileUrl,
FileName: req.FileName,
FileThumbnail: req.FileThumbnail,
FileAlt: req.FileAlt,
Size: req.Size,
UpdatedAt: time.Now(),
}
}
type AiChatFilesQueryRequestContext struct {
MessageId string `json:"messageId"`
FileName string `json:"fileName"`
StatusId string `json:"statusId"`
IsPublish string `json:"isPublish"`
}
func (req AiChatFilesQueryRequestContext) ToParamRequest() AiChatFilesQueryRequest {
var request AiChatFilesQueryRequest
if messageIdStr := req.MessageId; messageIdStr != "" {
messageId, err := strconv.Atoi(messageIdStr)
if err == nil {
request.MessageId = &messageId
}
}
if fileName := req.FileName; fileName != "" {
request.FileName = &fileName
}
if statusIdStr := req.StatusId; statusIdStr != "" {
statusId, err := strconv.Atoi(statusIdStr)
if err == nil {
request.StatusId = &statusId
}
}
if isPublishStr := req.IsPublish; isPublishStr != "" {
isPublish, err := strconv.ParseBool(isPublishStr)
if err == nil {
request.IsPublish = &isPublish
}
}
return request
}

View File

@ -0,0 +1,17 @@
package response
import "time"
type AiChatFilesResponse struct {
ID uint `json:"id"`
MessageId uint `json:"messageId"`
FilePath *string `json:"filePath"`
FileUrl *string `json:"fileUrl"`
FileName *string `json:"fileName"`
FileAlt *string `json:"fileAlt"`
FileType *string `json:"fileType"`
Size *string `json:"size"`
IsActive bool `json:"isActive"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
}

View File

@ -0,0 +1,446 @@
package service
import (
"context"
"fmt"
"io"
"log"
"math/rand"
"mime"
"mime/multipart"
"narasi-ahli-be/app/module/ai_chat_files/mapper"
"narasi-ahli-be/app/module/ai_chat_files/repository"
"narasi-ahli-be/app/module/ai_chat_files/request"
"narasi-ahli-be/app/module/ai_chat_files/response"
config "narasi-ahli-be/config/config"
minioStorage "narasi-ahli-be/config/config"
"narasi-ahli-be/utils/paginator"
"os"
"path/filepath"
"strconv"
"strings"
"sync"
"time"
"github.com/gofiber/fiber/v2"
"github.com/minio/minio-go/v7"
"github.com/rs/zerolog"
)
// AiChatFilesService
type aiChatFilesService struct {
Repo repository.AiChatFilesRepository
Log zerolog.Logger
Cfg *config.Config
MinioStorage *minioStorage.MinioStorage
}
// AiChatFilesService define interface of IAiChatFilesService
type AiChatFilesService interface {
All(req request.AiChatFilesQueryRequest) (aiChatFiles []*response.AiChatFilesResponse, paging paginator.Pagination, err error)
Show(id uint) (aiChatFiles *response.AiChatFilesResponse, err error)
Save(c *fiber.Ctx, id uint) error
SaveAsync(c *fiber.Ctx, id uint) error
Update(id uint, req request.AiChatFilesUpdateRequest) (err error)
GetUploadStatus(c *fiber.Ctx) (progress int, err error)
Delete(id uint) error
Viewer(c *fiber.Ctx) error
}
// NewAiChatFilesService init AiChatFilesService
func NewAiChatFilesService(repo repository.AiChatFilesRepository, log zerolog.Logger, cfg *config.Config, minioStorage *minioStorage.MinioStorage) AiChatFilesService {
return &aiChatFilesService{
Repo: repo,
Log: log,
Cfg: cfg,
MinioStorage: minioStorage,
}
}
var (
progressMap = make(map[string]int) // Menyimpan progress upload per UploadID
progressLock = sync.Mutex{}
)
type progressWriter struct {
uploadID string
totalSize int64
uploadedSize *int64
}
// All implement interface of AiChatFilesService
func (_i *aiChatFilesService) All(req request.AiChatFilesQueryRequest) (aiChatFiless []*response.AiChatFilesResponse, paging paginator.Pagination, err error) {
results, paging, err := _i.Repo.GetAll(req)
if err != nil {
return
}
host := _i.Cfg.App.Domain
for _, result := range results {
aiChatFiless = append(aiChatFiless, mapper.AiChatFilesResponseMapper(result, host))
}
return
}
func (_i *aiChatFilesService) Show(id uint) (aiChatFiles *response.AiChatFilesResponse, err error) {
result, err := _i.Repo.FindOne(id)
if err != nil {
return nil, err
}
host := _i.Cfg.App.Domain
return mapper.AiChatFilesResponseMapper(result, host), nil
}
func (_i *aiChatFilesService) SaveAsync(c *fiber.Ctx, id uint) (err error) {
bucketName := _i.MinioStorage.Cfg.ObjectStorage.MinioStorage.BucketName
ctx := context.Background()
form, err := c.MultipartForm()
if err != nil {
return err
}
//filess := form.File["files"]
// Create minio connection.
minioClient, err := _i.MinioStorage.ConnectMinio()
if err != nil {
// Return status 500 and minio connection error.
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", "Uploader:: top").
Interface("files", files).Msg("")
for _, fileHeader := range files {
_i.Log.Info().Str("timestamp", time.Now().
Format(time.RFC3339)).Str("Service:Resource", "Uploader:: loop").
Interface("data", fileHeader).Msg("")
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)
uploadID := strconv.Itoa(randUniqueId)
newFilenameWithoutExt := filenameWithoutExt + "_" + strconv.Itoa(randUniqueId)
newFilename := newFilenameWithoutExt + "." + extension
objectName := fmt.Sprintf("aiChats/upload/%d/%d/%s", now.Year(), now.Month(), newFilename)
fileSize := strconv.FormatInt(fileHeader.Size, 10)
fileSizeInt := fileHeader.Size
_i.Log.Info().Str("timestamp", time.Now().
Format(time.RFC3339)).Str("Service:Resource", "Uploader:: top").
Interface("Start upload", uploadID).Msg("")
req := request.AiChatFilesCreateRequest{
MessageId: id,
UploadId: &uploadID,
FilePath: &objectName,
FileName: &newFilename,
FileAlt: &filenameAlt,
Size: &fileSize,
}
err = _i.Repo.Create(req.ToEntity())
if err != nil {
return err
}
src, err := fileHeader.Open()
if err != nil {
return err
}
defer src.Close()
tempFilePath := fmt.Sprintf("/tmp/%s", newFilename)
tempFile, err := os.Create(tempFilePath)
if err != nil {
return err
}
defer tempFile.Close()
// Copy file ke direktori sementara
_, err = io.Copy(tempFile, src)
if err != nil {
return err
}
go uploadToMinIO(ctx, _i.Log, minioClient, uploadID, tempFilePath, bucketName, objectName, fileSizeInt)
}
}
_i.Log.Info().Str("timestamp", time.Now().
Format(time.RFC3339)).Str("Service:Resource", "User:All").
Interface("data", "Successfully uploaded").Msg("")
return
}
func (_i *aiChatFilesService) Save(c *fiber.Ctx, id uint) (err error) {
bucketName := _i.MinioStorage.Cfg.ObjectStorage.MinioStorage.BucketName
form, err := c.MultipartForm()
if err != nil {
return err
}
//filess := form.File["files"]
// Create minio connection.
minioClient, err := _i.MinioStorage.ConnectMinio()
if err != nil {
// Return status 500 and minio connection error.
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", "Uploader:: top").
Interface("files", files).Msg("")
for _, fileHeader := range files {
_i.Log.Info().Str("timestamp", time.Now().
Format(time.RFC3339)).Str("Service:Resource", "Uploader:: loop").
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("aiChats/upload/%d/%d/%s", now.Year(), now.Month(), newFilename)
fileSize := strconv.FormatInt(fileHeader.Size, 10)
req := request.AiChatFilesCreateRequest{
MessageId: id,
FilePath: &objectName,
FileName: &newFilename,
FileAlt: &filenameAlt,
Size: &fileSize,
}
err = _i.Repo.Create(req.ToEntity())
if err != nil {
return err
}
// Upload file ke 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", "User:All").
Interface("data", "Successfully uploaded").Msg("")
return
}
func (_i *aiChatFilesService) Update(id uint, req request.AiChatFilesUpdateRequest) (err error) {
_i.Log.Info().Interface("data", req).Msg("")
return _i.Repo.Update(id, req.ToEntity())
}
func (_i *aiChatFilesService) Delete(id uint) error {
result, err := _i.Repo.FindOne(id)
if err != nil {
return err
}
result.IsActive = false
return _i.Repo.Update(id, result)
}
func (_i *aiChatFilesService) Viewer(c *fiber.Ctx) (err error) {
filename := c.Params("filename")
result, err := _i.Repo.FindByFilename(filename)
if err != nil {
return err
}
ctx := context.Background()
bucketName := _i.MinioStorage.Cfg.ObjectStorage.MinioStorage.BucketName
objectName := *result.FilePath
_i.Log.Info().Str("timestamp", time.Now().
Format(time.RFC3339)).Str("Service:Resource", "AiChat:Uploads").
Interface("data", objectName).Msg("")
// Create minio connection.
minioClient, err := _i.MinioStorage.ConnectMinio()
if err != nil {
// Return status 500 and minio connection error.
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 {
log.Fatalln(err)
}
defer fileContent.Close()
// Tentukan Content-Type berdasarkan ekstensi file
contentType := mime.TypeByExtension("." + getFileExtension(objectName))
if contentType == "" {
contentType = "application/octet-stream" // fallback jika tidak ada tipe MIME yang cocok
}
c.Set("Content-Type", contentType)
if _, err := io.Copy(c.Response().BodyWriter(), fileContent); err != nil {
return err
}
return
}
func getFileExtension(filename string) string {
// split file name
parts := strings.Split(filename, ".")
// jika tidak ada ekstensi, kembalikan string kosong
if len(parts) == 1 || (len(parts) == 2 && parts[0] == "") {
return ""
}
// ambil ekstensi terakhir
return parts[len(parts)-1]
}
func uploadTempFile(log zerolog.Logger, fileHeader *multipart.FileHeader, filePath string) {
src, err := fileHeader.Open()
if err != nil {
log.Info().Str("timestamp", time.Now().
Format(time.RFC3339)).Str("Service:Resource", "AiChat:uploadToMinIO-0").
Interface("err", err).Msg("")
}
defer src.Close()
tempFile, err := os.Create(filePath)
if err != nil {
log.Info().Str("timestamp", time.Now().
Format(time.RFC3339)).Str("Service:Resource", "AiChat:uploadToMinIO-1").
Interface("err", err).Msg("")
}
defer tempFile.Close()
// Copy file ke direktori sementara
_, err = io.Copy(tempFile, src)
if err != nil {
log.Info().Str("timestamp", time.Now().
Format(time.RFC3339)).Str("Service:Resource", "AiChat:uploadToMinIO-2").
Interface("err", err).Msg("")
}
}
func uploadToMinIO(ctx context.Context, log zerolog.Logger, minioClient *minio.Client, uploadID, filePath, bucketName string, objectName string, fileSize int64) {
file, err := os.Open(filePath)
if err != nil {
log.Info().Str("timestamp", time.Now().
Format(time.RFC3339)).Str("Service:Resource", "AiChat:uploadToMinIO-3").
Interface("err", err).Msg("")
return
}
defer file.Close()
// Upload file ke MinIO dengan progress tracking
uploadProgress := int64(0)
reader := io.TeeReader(file, &progressWriter{uploadID: uploadID, totalSize: fileSize, uploadedSize: &uploadProgress})
_, err = minioClient.PutObject(ctx, bucketName, objectName, reader, fileSize, minio.PutObjectOptions{})
if err != nil {
log.Info().Str("timestamp", time.Now().
Format(time.RFC3339)).Str("Service:Resource", "AiChat:uploadToMinIO-4").
Interface("err", err).Msg("")
return
}
// Upload selesai, update progress menjadi 100
progressLock.Lock()
progressMap[uploadID] = 100
progressLock.Unlock()
go removeFileTemp(log, filePath)
}
func removeFileTemp(log zerolog.Logger, filePath string) {
err := os.Remove(filePath)
if err != nil {
log.Info().Str("timestamp", time.Now().
Format(time.RFC3339)).Str("Service:Resource", "AiChat:uploadToMinIO-5").
Interface("Failed to remove temporary file", err).Msg("")
} else {
log.Info().Str("timestamp", time.Now().
Format(time.RFC3339)).Str("Service:Resource", "AiChat:uploadToMinIO-6").
Interface("err", "Temporary file removed").Msg("")
}
}
func (p *progressWriter) Write(data []byte) (int, error) {
n := len(data)
progressLock.Lock()
defer progressLock.Unlock()
*p.uploadedSize += int64(n)
progress := int(float64(*p.uploadedSize) / float64(p.totalSize) * 100)
// Update progress di map
progressMap[p.uploadID] = progress
return n, nil
}
func (_i *aiChatFilesService) GetUploadStatus(c *fiber.Ctx) (progress int, err error) {
uploadID := c.Params("uploadId")
// Ambil progress dari map
progressLock.Lock()
progress, _ = progressMap[uploadID]
progressLock.Unlock()
return progress, nil
}

View File

@ -0,0 +1,139 @@
package service
import (
"context"
"github.com/minio/minio-go/v7"
"github.com/rs/zerolog"
"io"
"time"
)
// 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
}
//func (au *UploadService) UploadFile() {
// // Buat context dengan timeout
// ctx, cancel := context.WithTimeout(au.ctx, 30*time.Minute)
// defer cancel()
//
// // Buat reader dari byte slice
// reader := bytes.NewReader(au.fileData)
// pipeReader, pipeWriter := io.Pipe()
//
// au.progressMap.Store(au.uploadID, 0.0)
//
// // Start goroutine to read from reader and write to pipe
// go func() {
// defer pipeWriter.Close()
// buf := make([]byte, au.partSize)
//
// totalParts := int(reader.Size() / au.partSize)
// if reader.Size()%au.partSize != 0 {
// totalParts++
// }
//
// for i := 0; i < totalParts; i++ {
// n, err := reader.Read(buf)
// if err != nil && err != io.EOF {
// log.Println("Error reading file:", err)
// return
// }
//
// if _, err := pipeWriter.Write(buf[:n]); err != nil {
// log.Println("Error writing to pipe:", err)
// return
// }
//
// progress := float64(i+1) / float64(totalParts) * 100
// au.progressMap.Store(au.uploadID, progress)
// au.uploadManager.UpdateProgress(au.uploadID, int(progress))
// }
// }()
//
// // Upload ke Minio
// _, err := au.minioClient.PutObject(
// ctx,
// au.bucketName,
// au.objectName,
// pipeReader,
// reader.Size(),
// minio.PutObjectOptions{
// ContentType: au.contentType,
// PartSize: 10 * 1024 * 1024, // 10MB part size
// },
// )
//
// if err != nil {
// log.Println("Error uploading file:", err)
// au.progressMap.Store(au.uploadID, "error")
// return
// }
//
// fmt.Printf("Uploading process for %s", au.objectName)
//
// if err != nil {
// uploadManager.UpdateStatus(au.uploadID, "error", err)
// fmt.Printf("Upload error for %s: %v\n", au.objectName, err)
// return
// }
//
// au.progressMap.Store(au.uploadID, 100)
// au.uploadManager.UpdateProgress(au.uploadID, 100)
// au.uploadManager.UpdateStatus(au.uploadID, "completed", nil)
//}

View File

@ -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
}

View File

@ -4,6 +4,7 @@ import (
"narasi-ahli-be/app/module/activity_logs"
"narasi-ahli-be/app/module/advertisement"
"narasi-ahli-be/app/module/ai_chat"
"narasi-ahli-be/app/module/ai_chat_files"
"narasi-ahli-be/app/module/article_approvals"
"narasi-ahli-be/app/module/article_categories"
"narasi-ahli-be/app/module/article_category_details"
@ -68,6 +69,8 @@ type Router struct {
EducationHistoryRouter *education_history.EducationHistoryRouter
WorkHistoryRouter *work_history.WorkHistoryRouter
ResearchJournalsRouter *research_journals.ResearchJournalsRouter
AIChatFilesRouter *ai_chat_files.AiChatFilesRouter
}
func NewRouter(
@ -102,6 +105,7 @@ func NewRouter(
educationHistoryRouter *education_history.EducationHistoryRouter,
workHistoryRouter *work_history.WorkHistoryRouter,
researchJournalsRouter *research_journals.ResearchJournalsRouter,
aiChatFilesRouter *ai_chat_files.AiChatFilesRouter,
) *Router {
return &Router{
App: fiber,
@ -134,6 +138,7 @@ func NewRouter(
EducationHistoryRouter: educationHistoryRouter,
WorkHistoryRouter: workHistoryRouter,
ResearchJournalsRouter: researchJournalsRouter,
AIChatFilesRouter: aiChatFilesRouter,
}
}
@ -176,4 +181,6 @@ func (r *Router) Register() {
r.EducationHistoryRouter.RegisterEducationHistoryRoutes()
r.WorkHistoryRouter.RegisterWorkHistoryRoutes()
r.ResearchJournalsRouter.RegisterResearchJournalsRoutes()
r.AIChatFilesRouter.RegisterAiChatFilesRoutes()
}

View File

@ -1,7 +1,7 @@
# Configuration vars for cmd/app
[app]
name = "Fiber starter"
host = "http://38.47.180.165"
host = "http://103.31.38.120"
port = ":8800"
domain = "https://narasiahli.com/api"
external-port = ":8801"
@ -12,7 +12,7 @@ production = false
body-limit = 1048576000 # "100 * 1024 * 1024"
[db.postgres]
dsn = "postgresql://narasiahli_user:NarasiAhliDB@2025@38.47.180.165:5432/narasiahli_db" # <driver>://<username>:<password>@<host>:<port>/<database>
dsn = "postgresql://narasiahli_user:NarasiAhliDB@2025@157.10.161.198:5432/narasiahli_db" # <driver>://<username>:<password>@<host>:<port>/<database>
log-mode = "NONE"
migrate = true
seed = true
@ -24,7 +24,7 @@ level = 0 # panic -> 5, fatal -> 4, error -> 3, warn -> 2, info -> 1, debug -> 0
prettier = true
[objectstorage.miniostorage]
endpoint = "38.47.180.165:9009"
endpoint = "is3.cloudhost.id"
access-key-id = "lBtjqWidHz1ktBbduwGy"
secret-access-key = "nsedJIa2FI7SqsEVcSFqJrlP4JuFRWGLauNpzD0i"
use-ssl = false
@ -66,7 +66,7 @@ enable = true
retention = 30
[keycloak]
endpoint = "http://38.47.180.165:8008"
endpoint = "http://103.31.38.120:8008"
realm = "narasi-ahli"
client-id = "narasi-ahli-app"
client-secret = "IoU4CkzWkWmg6PrC2Ruh8d76QArb0UzJ"

View File

@ -914,6 +914,223 @@ const docTemplate = `{
}
}
},
"/ai-chat-files": {
"get": {
"security": [
{
"Bearer": []
}
],
"description": "API for getting all AiChatFiles",
"tags": [
"AiChat Files"
],
"summary": "Get all AiChatFiles",
"parameters": [
{
"type": "string",
"name": "fileName",
"in": "query"
},
{
"type": "boolean",
"name": "isPublish",
"in": "query"
},
{
"type": "integer",
"name": "messageId",
"in": "query"
},
{
"type": "integer",
"name": "statusId",
"in": "query"
},
{
"type": "integer",
"name": "count",
"in": "query"
},
{
"type": "integer",
"name": "limit",
"in": "query"
},
{
"type": "integer",
"name": "nextPage",
"in": "query"
},
{
"type": "integer",
"name": "page",
"in": "query"
},
{
"type": "integer",
"name": "previousPage",
"in": "query"
},
{
"type": "string",
"name": "sort",
"in": "query"
},
{
"type": "string",
"name": "sortBy",
"in": "query"
},
{
"type": "integer",
"name": "totalPage",
"in": "query"
}
],
"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"
}
}
}
}
},
"/ai-chat-files/{id}": {
"get": {
"security": [
{
"Bearer": []
}
],
"description": "API for getting one AiChatFiles",
"tags": [
"AiChat Files"
],
"summary": "Get one AiChatFiles",
"parameters": [
{
"type": "integer",
"description": "AiChatFiles ID",
"name": "id",
"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"
}
}
}
}
},
"/ai-chat-files/{messageId}": {
"post": {
"security": [
{
"Bearer": []
}
],
"description": "API for create AiChatFiles",
"produces": [
"application/json"
],
"tags": [
"AiChat Files"
],
"summary": "Upload AiChatFiles",
"parameters": [
{
"type": "string",
"description": "Insert the X-Csrf-Token",
"name": "X-Csrf-Token",
"in": "header",
"required": true
},
{
"type": "file",
"description": "Upload file",
"name": "files",
"in": "formData",
"required": true
},
{
"type": "integer",
"description": "AiChat ID",
"name": "aiChatId",
"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"
}
}
}
}
},
"/ai-chat/logs": {
"get": {
"security": [
@ -1700,6 +1917,223 @@ const docTemplate = `{
}
}
},
"/aiChat-files/upload-status/{uploadId}": {
"get": {
"security": [
{
"Bearer": []
}
],
"description": "API for GetUploadStatus AiChatFiles",
"tags": [
"AiChat Files"
],
"summary": "GetUploadStatus AiChatFiles",
"parameters": [
{
"type": "string",
"description": "Upload ID of AiChatFiles",
"name": "uploadId",
"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"
}
}
}
}
},
"/aiChat-files/viewer/{filename}": {
"get": {
"security": [
{
"Bearer": []
}
],
"description": "API for Viewer AiChatFiles",
"tags": [
"AiChat Files"
],
"summary": "Viewer AiChatFiles",
"parameters": [
{
"type": "string",
"description": "AiChat 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"
}
}
}
}
},
"/aiChat-files/{id}": {
"put": {
"security": [
{
"Bearer": []
}
],
"description": "API for update AiChatFiles",
"tags": [
"AiChat Files"
],
"summary": "Update AiChatFiles",
"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.AiChatFilesUpdateRequest"
}
},
{
"type": "integer",
"description": "AiChatFiles ID",
"name": "id",
"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"
}
}
}
},
"delete": {
"security": [
{
"Bearer": []
}
],
"description": "API for delete AiChatFiles",
"tags": [
"AiChat Files"
],
"summary": "Delete AiChatFiles",
"parameters": [
{
"type": "string",
"description": "Insert the X-Csrf-Token",
"name": "X-Csrf-Token",
"in": "header",
"required": true
},
{
"type": "integer",
"description": "AiChatFiles ID",
"name": "id",
"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"
}
}
}
}
},
"/article-approvals": {
"get": {
"security": [
@ -15204,6 +15638,57 @@ const docTemplate = `{
}
}
},
"request.AiChatFilesUpdateRequest": {
"type": "object",
"required": [
"id",
"isPublish",
"messageId",
"publishedAt",
"statusId"
],
"properties": {
"fileAlt": {
"type": "string"
},
"fileName": {
"type": "string"
},
"filePath": {
"type": "string"
},
"fileThumbnail": {
"type": "string"
},
"fileUrl": {
"type": "string"
},
"heightPixel": {
"type": "string"
},
"id": {
"type": "integer"
},
"isPublish": {
"type": "boolean"
},
"messageId": {
"type": "integer"
},
"publishedAt": {
"type": "string"
},
"size": {
"type": "string"
},
"statusId": {
"type": "integer"
},
"widthPixel": {
"type": "string"
}
}
},
"request.ArticleApprovalsCreateRequest": {
"type": "object",
"required": [

View File

@ -903,6 +903,223 @@
}
}
},
"/ai-chat-files": {
"get": {
"security": [
{
"Bearer": []
}
],
"description": "API for getting all AiChatFiles",
"tags": [
"AiChat Files"
],
"summary": "Get all AiChatFiles",
"parameters": [
{
"type": "string",
"name": "fileName",
"in": "query"
},
{
"type": "boolean",
"name": "isPublish",
"in": "query"
},
{
"type": "integer",
"name": "messageId",
"in": "query"
},
{
"type": "integer",
"name": "statusId",
"in": "query"
},
{
"type": "integer",
"name": "count",
"in": "query"
},
{
"type": "integer",
"name": "limit",
"in": "query"
},
{
"type": "integer",
"name": "nextPage",
"in": "query"
},
{
"type": "integer",
"name": "page",
"in": "query"
},
{
"type": "integer",
"name": "previousPage",
"in": "query"
},
{
"type": "string",
"name": "sort",
"in": "query"
},
{
"type": "string",
"name": "sortBy",
"in": "query"
},
{
"type": "integer",
"name": "totalPage",
"in": "query"
}
],
"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"
}
}
}
}
},
"/ai-chat-files/{id}": {
"get": {
"security": [
{
"Bearer": []
}
],
"description": "API for getting one AiChatFiles",
"tags": [
"AiChat Files"
],
"summary": "Get one AiChatFiles",
"parameters": [
{
"type": "integer",
"description": "AiChatFiles ID",
"name": "id",
"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"
}
}
}
}
},
"/ai-chat-files/{messageId}": {
"post": {
"security": [
{
"Bearer": []
}
],
"description": "API for create AiChatFiles",
"produces": [
"application/json"
],
"tags": [
"AiChat Files"
],
"summary": "Upload AiChatFiles",
"parameters": [
{
"type": "string",
"description": "Insert the X-Csrf-Token",
"name": "X-Csrf-Token",
"in": "header",
"required": true
},
{
"type": "file",
"description": "Upload file",
"name": "files",
"in": "formData",
"required": true
},
{
"type": "integer",
"description": "AiChat ID",
"name": "aiChatId",
"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"
}
}
}
}
},
"/ai-chat/logs": {
"get": {
"security": [
@ -1689,6 +1906,223 @@
}
}
},
"/aiChat-files/upload-status/{uploadId}": {
"get": {
"security": [
{
"Bearer": []
}
],
"description": "API for GetUploadStatus AiChatFiles",
"tags": [
"AiChat Files"
],
"summary": "GetUploadStatus AiChatFiles",
"parameters": [
{
"type": "string",
"description": "Upload ID of AiChatFiles",
"name": "uploadId",
"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"
}
}
}
}
},
"/aiChat-files/viewer/{filename}": {
"get": {
"security": [
{
"Bearer": []
}
],
"description": "API for Viewer AiChatFiles",
"tags": [
"AiChat Files"
],
"summary": "Viewer AiChatFiles",
"parameters": [
{
"type": "string",
"description": "AiChat 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"
}
}
}
}
},
"/aiChat-files/{id}": {
"put": {
"security": [
{
"Bearer": []
}
],
"description": "API for update AiChatFiles",
"tags": [
"AiChat Files"
],
"summary": "Update AiChatFiles",
"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.AiChatFilesUpdateRequest"
}
},
{
"type": "integer",
"description": "AiChatFiles ID",
"name": "id",
"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"
}
}
}
},
"delete": {
"security": [
{
"Bearer": []
}
],
"description": "API for delete AiChatFiles",
"tags": [
"AiChat Files"
],
"summary": "Delete AiChatFiles",
"parameters": [
{
"type": "string",
"description": "Insert the X-Csrf-Token",
"name": "X-Csrf-Token",
"in": "header",
"required": true
},
{
"type": "integer",
"description": "AiChatFiles ID",
"name": "id",
"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"
}
}
}
}
},
"/article-approvals": {
"get": {
"security": [
@ -15193,6 +15627,57 @@
}
}
},
"request.AiChatFilesUpdateRequest": {
"type": "object",
"required": [
"id",
"isPublish",
"messageId",
"publishedAt",
"statusId"
],
"properties": {
"fileAlt": {
"type": "string"
},
"fileName": {
"type": "string"
},
"filePath": {
"type": "string"
},
"fileThumbnail": {
"type": "string"
},
"fileUrl": {
"type": "string"
},
"heightPixel": {
"type": "string"
},
"id": {
"type": "integer"
},
"isPublish": {
"type": "boolean"
},
"messageId": {
"type": "integer"
},
"publishedAt": {
"type": "string"
},
"size": {
"type": "string"
},
"statusId": {
"type": "integer"
},
"widthPixel": {
"type": "string"
}
}
},
"request.ArticleApprovalsCreateRequest": {
"type": "object",
"required": [

View File

@ -148,6 +148,41 @@ definitions:
- redirectLink
- title
type: object
request.AiChatFilesUpdateRequest:
properties:
fileAlt:
type: string
fileName:
type: string
filePath:
type: string
fileThumbnail:
type: string
fileUrl:
type: string
heightPixel:
type: string
id:
type: integer
isPublish:
type: boolean
messageId:
type: integer
publishedAt:
type: string
size:
type: string
statusId:
type: integer
widthPixel:
type: string
required:
- id
- isPublish
- messageId
- publishedAt
- statusId
type: object
request.ArticleApprovalsCreateRequest:
properties:
articleId:
@ -1922,6 +1957,142 @@ paths:
summary: Viewer Advertisement
tags:
- Advertisement
/ai-chat-files:
get:
description: API for getting all AiChatFiles
parameters:
- in: query
name: fileName
type: string
- in: query
name: isPublish
type: boolean
- in: query
name: messageId
type: integer
- in: query
name: statusId
type: integer
- in: query
name: count
type: integer
- in: query
name: limit
type: integer
- in: query
name: nextPage
type: integer
- in: query
name: page
type: integer
- in: query
name: previousPage
type: integer
- in: query
name: sort
type: string
- in: query
name: sortBy
type: string
- in: query
name: totalPage
type: integer
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: Get all AiChatFiles
tags:
- AiChat Files
/ai-chat-files/{id}:
get:
description: API for getting one AiChatFiles
parameters:
- description: AiChatFiles ID
in: path
name: id
required: true
type: integer
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: Get one AiChatFiles
tags:
- AiChat Files
/ai-chat-files/{messageId}:
post:
description: API for create AiChatFiles
parameters:
- description: Insert the X-Csrf-Token
in: header
name: X-Csrf-Token
required: true
type: string
- description: Upload file
in: formData
name: files
required: true
type: file
- description: AiChat ID
in: path
name: aiChatId
required: true
type: integer
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: Upload AiChatFiles
tags:
- AiChat Files
/ai-chat/logs:
get:
description: API for getting all AI chat logs for authenticated user
@ -2424,6 +2595,145 @@ paths:
summary: Update AI chat message
tags:
- AI Chat
/aiChat-files/{id}:
delete:
description: API for delete AiChatFiles
parameters:
- description: Insert the X-Csrf-Token
in: header
name: X-Csrf-Token
required: true
type: string
- description: AiChatFiles ID
in: path
name: id
required: true
type: integer
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: Delete AiChatFiles
tags:
- AiChat Files
put:
description: API for update AiChatFiles
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.AiChatFilesUpdateRequest'
- description: AiChatFiles ID
in: path
name: id
required: true
type: integer
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: Update AiChatFiles
tags:
- AiChat Files
/aiChat-files/upload-status/{uploadId}:
get:
description: API for GetUploadStatus AiChatFiles
parameters:
- description: Upload ID of AiChatFiles
in: path
name: uploadId
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: GetUploadStatus AiChatFiles
tags:
- AiChat Files
/aiChat-files/viewer/{filename}:
get:
description: API for Viewer AiChatFiles
parameters:
- description: AiChat 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 AiChatFiles
tags:
- AiChat Files
/article-approvals:
get:
description: API for getting all ArticleApprovals

View File

@ -6,6 +6,7 @@ import (
"narasi-ahli-be/app/module/activity_logs"
"narasi-ahli-be/app/module/advertisement"
"narasi-ahli-be/app/module/ai_chat"
"narasi-ahli-be/app/module/ai_chat_files"
"narasi-ahli-be/app/module/article_approvals"
"narasi-ahli-be/app/module/article_categories"
"narasi-ahli-be/app/module/article_category_details"
@ -95,6 +96,7 @@ func main() {
education_history.NewEducationHistoryModule,
work_history.NewWorkHistoryModule,
research_journals.NewResearchJournalsModule,
ai_chat_files.NewAiChatFilesModule,
// start aplication
fx.Invoke(webserver.Start),