feat: add chat schedule and the files

This commit is contained in:
hanif salafi 2025-09-23 01:47:06 +07:00
parent 9659007a06
commit 94b0345f2b
20 changed files with 4886 additions and 3 deletions

View File

@ -0,0 +1,23 @@
package entity
import (
"time"
)
type ChatScheduleFiles struct {
ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"`
ChatScheduleID uint `json:"chat_schedule_id" gorm:"type:int4;not null;index"`
FileName string `json:"file_name" gorm:"type:varchar(255);not null"`
OriginalName string `json:"original_name" gorm:"type:varchar(255);not null"`
FilePath string `json:"file_path" gorm:"type:varchar(500);not null"`
FileSize int64 `json:"file_size" gorm:"type:int8"`
MimeType string `json:"mime_type" gorm:"type:varchar(100)"`
FileType string `json:"file_type" gorm:"type:varchar(20);not null;check:file_type IN ('article', 'journal', 'video', 'audio', 'document', 'other')"`
Description string `json:"description" gorm:"type:text"`
IsRequired bool `json:"is_required" gorm:"default:false"`
CreatedAt time.Time `json:"created_at" gorm:"default:now()"`
UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"`
// Relationships
ChatSchedule *ChatSchedules `json:"chat_schedule" gorm:"foreignKey:ChatScheduleID;references:ID"`
}

View File

@ -0,0 +1,27 @@
package entity
import (
"narasi-ahli-be/app/database/entity/users"
"time"
)
type ChatSchedules struct {
ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"`
ChatSessionID uint `json:"chat_session_id" gorm:"type:int4;not null;index"`
Title string `json:"title" gorm:"type:varchar(255);not null"`
Description string `json:"description" gorm:"type:text"`
Summary string `json:"summary" gorm:"type:text"`
ScheduledAt time.Time `json:"scheduled_at" gorm:"not null"`
Duration int `json:"duration" gorm:"type:int4;default:60"` // duration in minutes
Status string `json:"status" gorm:"type:varchar(20);not null;default:'scheduled';check:status IN ('scheduled', 'ongoing', 'completed', 'cancelled')"`
IsReminderSent bool `json:"is_reminder_sent" gorm:"default:false"`
ReminderSentAt *time.Time `json:"reminder_sent_at"`
CreatedBy uint `json:"created_by" gorm:"type:int4;not null;index"`
CreatedAt time.Time `json:"created_at" gorm:"default:now()"`
UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"`
// Relationships
ChatSession *ChatSessions `json:"chat_session" gorm:"foreignKey:ChatSessionID;references:ID"`
Creator *users.Users `json:"creator" gorm:"foreignKey:CreatedBy;references:ID"`
Files []*ChatScheduleFiles `json:"files" gorm:"foreignKey:ChatScheduleID;references:ID"`
}

View File

@ -124,6 +124,8 @@ func Models() []interface{} {
entity.ChatMessages{}, entity.ChatMessages{},
entity.ChatParticipants{}, entity.ChatParticipants{},
entity.ChatSessions{}, entity.ChatSessions{},
entity.ChatSchedules{},
entity.ChatScheduleFiles{},
entity.AIChatSessions{}, entity.AIChatSessions{},
entity.AIChatMessages{}, entity.AIChatMessages{},
entity.AIChatLogs{}, entity.AIChatLogs{},

View File

@ -19,12 +19,18 @@ type ChatRouter struct {
var NewChatModule = fx.Options( var NewChatModule = fx.Options(
// register repository of Chat module // register repository of Chat module
fx.Provide(repository.NewChatRepository), fx.Provide(repository.NewChatRepository),
fx.Provide(repository.NewChatScheduleRepository),
fx.Provide(repository.NewChatScheduleFileRepository),
// register service of Chat module // register service of Chat module
fx.Provide(service.NewChatService), fx.Provide(service.NewChatService),
fx.Provide(service.NewChatScheduleService),
fx.Provide(service.NewChatScheduleFileService),
// register controller of Chat module // register controller of Chat module
fx.Provide(controller.NewController), fx.Provide(controller.NewController),
fx.Provide(controller.NewChatScheduleController),
fx.Provide(controller.NewChatScheduleFileController),
// register router of Chat module // register router of Chat module
fx.Provide(NewChatRouter), fx.Provide(NewChatRouter),
@ -42,6 +48,8 @@ func NewChatRouter(fiber *fiber.App, controller *controller.Controller) *ChatRou
func (_i *ChatRouter) RegisterChatRoutes() { func (_i *ChatRouter) RegisterChatRoutes() {
// define controllers // define controllers
chatController := _i.Controller.Chat chatController := _i.Controller.Chat
chatScheduleController := _i.Controller.ChatSchedule
chatScheduleFileController := _i.Controller.ChatScheduleFile
// define routes // define routes
_i.App.Route("/chat", func(router fiber.Router) { _i.App.Route("/chat", func(router fiber.Router) {
@ -66,5 +74,27 @@ func (_i *ChatRouter) RegisterChatRoutes() {
messageRouter.Put("/:id", chatController.UpdateChatMessage) messageRouter.Put("/:id", chatController.UpdateChatMessage)
messageRouter.Delete("/:id", chatController.DeleteChatMessage) messageRouter.Delete("/:id", chatController.DeleteChatMessage)
}) })
// Chat Schedule routes
router.Route("/schedules", func(scheduleRouter fiber.Router) {
scheduleRouter.Get("/", chatScheduleController.GetAllChatSchedules)
scheduleRouter.Get("/upcoming", chatScheduleController.GetUpcomingSchedules)
scheduleRouter.Get("/status/:status", chatScheduleController.GetSchedulesByStatus)
scheduleRouter.Get("/:id", chatScheduleController.GetChatScheduleByID)
scheduleRouter.Post("/", chatScheduleController.CreateChatSchedule)
scheduleRouter.Put("/:id", chatScheduleController.UpdateChatSchedule)
scheduleRouter.Delete("/:id", chatScheduleController.DeleteChatSchedule)
scheduleRouter.Post("/:id/reminder", chatScheduleController.SendScheduleReminder)
})
// Chat Schedule File routes
router.Route("/schedule-files", func(fileRouter fiber.Router) {
fileRouter.Get("/", chatScheduleFileController.GetChatScheduleFiles)
fileRouter.Get("/:id", chatScheduleFileController.GetChatScheduleFileByID)
fileRouter.Post("/:chatScheduleId", chatScheduleFileController.UploadChatScheduleFile)
fileRouter.Put("/:id", chatScheduleFileController.UpdateChatScheduleFile)
fileRouter.Delete("/:id", chatScheduleFileController.DeleteChatScheduleFile)
fileRouter.Get("/viewer/:filename", chatScheduleFileController.Viewer)
})
}) })
} }

View File

@ -0,0 +1,320 @@
package controller
import (
"narasi-ahli-be/app/module/chat/request"
"narasi-ahli-be/app/module/chat/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 chatScheduleController struct {
chatScheduleService service.ChatScheduleService
}
type ChatScheduleController interface {
// Chat Schedule endpoints
GetAllChatSchedules(c *fiber.Ctx) error
GetChatScheduleByID(c *fiber.Ctx) error
CreateChatSchedule(c *fiber.Ctx) error
UpdateChatSchedule(c *fiber.Ctx) error
DeleteChatSchedule(c *fiber.Ctx) error
// Additional schedule endpoints
GetUpcomingSchedules(c *fiber.Ctx) error
GetSchedulesByStatus(c *fiber.Ctx) error
SendScheduleReminder(c *fiber.Ctx) error
}
func NewChatScheduleController(chatScheduleService service.ChatScheduleService) ChatScheduleController {
return &chatScheduleController{
chatScheduleService: chatScheduleService,
}
}
// GetAllChatSchedules - Get all chat schedules for a user
// @Summary Get all chat schedules
// @Description API for getting all chat schedules for authenticated user
// @Tags Chat Schedule
// @Security Bearer
// @Param Authorization header string false "Insert your access token" default(Bearer <Add access token here>)
// @Param chatSessionId query string false "Chat Session ID"
// @Param status query string false "Schedule status (scheduled, ongoing, completed, cancelled)"
// @Param createdBy query string false "Created by user ID"
// @Param dateFrom query string false "Date from (YYYY-MM-DD)"
// @Param dateTo query string false "Date to (YYYY-MM-DD)"
// @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 /chat/schedules [get]
func (_i *chatScheduleController) GetAllChatSchedules(c *fiber.Ctx) error {
paginate, err := paginator.Paginate(c)
if err != nil {
return err
}
authToken := c.Get("Authorization")
reqContext := request.ChatScheduleQueryRequestContext{
ChatSessionID: c.Query("chatSessionId"),
Status: c.Query("status"),
CreatedBy: c.Query("createdBy"),
DateFrom: c.Query("dateFrom"),
DateTo: c.Query("dateTo"),
}
req := reqContext.ToParamRequest()
req.Pagination = *paginate
chatSchedules, paging, err := _i.chatScheduleService.GetAllChatSchedules(authToken, req)
if err != nil {
return err
}
return utilRes.Resp(c, utilRes.Response{
Success: true,
Messages: utilRes.Messages{"Chat schedules successfully retrieved"},
Data: chatSchedules,
Meta: paging,
})
}
// GetChatScheduleByID - Get one chat schedule
// @Summary Get one chat schedule
// @Description API for getting one chat schedule
// @Tags Chat Schedule
// @Security Bearer
// @Param Authorization header string false "Insert your access token" default(Bearer <Add access token here>)
// @Param id path int true "Chat Schedule ID"
// @Success 200 {object} response.Response
// @Failure 400 {object} response.BadRequestError
// @Failure 401 {object} response.UnauthorizedError
// @Failure 500 {object} response.InternalServerError
// @Router /chat/schedules/{id} [get]
func (_i *chatScheduleController) GetChatScheduleByID(c *fiber.Ctx) error {
id, err := strconv.ParseUint(c.Params("id"), 10, 0)
if err != nil {
return err
}
authToken := c.Get("Authorization")
chatSchedule, err := _i.chatScheduleService.GetChatScheduleByID(authToken, uint(id))
if err != nil {
return err
}
return utilRes.Resp(c, utilRes.Response{
Success: true,
Messages: utilRes.Messages{"Chat schedule successfully retrieved"},
Data: chatSchedule,
})
}
// CreateChatSchedule - Create chat schedule
// @Summary Create chat schedule
// @Description API for creating a new chat schedule
// @Tags Chat Schedule
// @Security Bearer
// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token"
// @Param Authorization header string false "Insert your access token" default(Bearer <Add access token here>)
// @Param payload body request.ChatScheduleCreateRequest true "Required payload"
// @Success 200 {object} response.Response
// @Failure 400 {object} response.BadRequestError
// @Failure 401 {object} response.UnauthorizedError
// @Failure 500 {object} response.InternalServerError
// @Router /chat/schedules [post]
func (_i *chatScheduleController) CreateChatSchedule(c *fiber.Ctx) error {
authToken := c.Get("Authorization")
req := new(request.ChatScheduleCreateRequest)
if err := utilVal.ParseAndValidate(c, req); err != nil {
return err
}
dataResult, err := _i.chatScheduleService.CreateChatSchedule(authToken, *req)
if err != nil {
return err
}
return utilRes.Resp(c, utilRes.Response{
Success: true,
Messages: utilRes.Messages{"Chat schedule successfully created"},
Data: dataResult,
})
}
// UpdateChatSchedule - Update chat schedule
// @Summary Update chat schedule
// @Description API for updating chat schedule (only creator can update)
// @Tags Chat Schedule
// @Security Bearer
// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token"
// @Param Authorization header string false "Insert your access token" default(Bearer <Add access token here>)
// @Param id path int true "Chat Schedule ID"
// @Param payload body request.ChatScheduleUpdateRequest true "Required payload"
// @Success 200 {object} response.Response
// @Failure 400 {object} response.BadRequestError
// @Failure 401 {object} response.UnauthorizedError
// @Failure 500 {object} response.InternalServerError
// @Router /chat/schedules/{id} [put]
func (_i *chatScheduleController) UpdateChatSchedule(c *fiber.Ctx) error {
id, err := strconv.ParseUint(c.Params("id"), 10, 0)
if err != nil {
return err
}
authToken := c.Get("Authorization")
req := new(request.ChatScheduleUpdateRequest)
if err := utilVal.ParseAndValidate(c, req); err != nil {
return err
}
err = _i.chatScheduleService.UpdateChatSchedule(authToken, uint(id), *req)
if err != nil {
return err
}
return utilRes.Resp(c, utilRes.Response{
Success: true,
Messages: utilRes.Messages{"Chat schedule successfully updated"},
})
}
// DeleteChatSchedule - Delete chat schedule
// @Summary Delete chat schedule
// @Description API for deleting chat schedule (only creator can delete)
// @Tags Chat Schedule
// @Security Bearer
// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token"
// @Param Authorization header string false "Insert your access token" default(Bearer <Add access token here>)
// @Param id path int true "Chat Schedule ID"
// @Success 200 {object} response.Response
// @Failure 400 {object} response.BadRequestError
// @Failure 401 {object} response.UnauthorizedError
// @Failure 500 {object} response.InternalServerError
// @Router /chat/schedules/{id} [delete]
func (_i *chatScheduleController) DeleteChatSchedule(c *fiber.Ctx) error {
id, err := strconv.ParseUint(c.Params("id"), 10, 0)
if err != nil {
return err
}
authToken := c.Get("Authorization")
err = _i.chatScheduleService.DeleteChatSchedule(authToken, uint(id))
if err != nil {
return err
}
return utilRes.Resp(c, utilRes.Response{
Success: true,
Messages: utilRes.Messages{"Chat schedule successfully deleted"},
})
}
// GetUpcomingSchedules - Get upcoming schedules for a user
// @Summary Get upcoming schedules
// @Description API for getting upcoming chat schedules for authenticated user
// @Tags Chat Schedule
// @Security Bearer
// @Param Authorization header string false "Insert your access token" default(Bearer <Add access token here>)
// @Param limit query int false "Limit number of results" default(10)
// @Success 200 {object} response.Response
// @Failure 400 {object} response.BadRequestError
// @Failure 401 {object} response.UnauthorizedError
// @Failure 500 {object} response.InternalServerError
// @Router /chat/schedules/upcoming [get]
func (_i *chatScheduleController) GetUpcomingSchedules(c *fiber.Ctx) error {
authToken := c.Get("Authorization")
limit := 10 // default limit
if limitStr := c.Query("limit"); limitStr != "" {
if parsedLimit, err := strconv.Atoi(limitStr); err == nil && parsedLimit > 0 {
limit = parsedLimit
}
}
chatSchedules, err := _i.chatScheduleService.GetUpcomingSchedules(authToken, limit)
if err != nil {
return err
}
return utilRes.Resp(c, utilRes.Response{
Success: true,
Messages: utilRes.Messages{"Upcoming chat schedules successfully retrieved"},
Data: chatSchedules,
})
}
// GetSchedulesByStatus - Get schedules by status
// @Summary Get schedules by status
// @Description API for getting chat schedules by status
// @Tags Chat Schedule
// @Security Bearer
// @Param Authorization header string false "Insert your access token" default(Bearer <Add access token here>)
// @Param status path string true "Schedule status (scheduled, ongoing, completed, cancelled)"
// @Success 200 {object} response.Response
// @Failure 400 {object} response.BadRequestError
// @Failure 401 {object} response.UnauthorizedError
// @Failure 500 {object} response.InternalServerError
// @Router /chat/schedules/status/{status} [get]
func (_i *chatScheduleController) GetSchedulesByStatus(c *fiber.Ctx) error {
status := c.Params("status")
if status == "" {
return utilRes.Resp(c, utilRes.Response{
Success: false,
Messages: utilRes.Messages{"Status parameter is required"},
})
}
authToken := c.Get("Authorization")
chatSchedules, err := _i.chatScheduleService.GetSchedulesByStatus(authToken, status)
if err != nil {
return err
}
return utilRes.Resp(c, utilRes.Response{
Success: true,
Messages: utilRes.Messages{"Chat schedules by status successfully retrieved"},
Data: chatSchedules,
})
}
// SendScheduleReminder - Send reminder for a schedule
// @Summary Send schedule reminder
// @Description API for sending reminder for a chat schedule (only creator can send)
// @Tags Chat Schedule
// @Security Bearer
// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token"
// @Param Authorization header string false "Insert your access token" default(Bearer <Add access token here>)
// @Param id path int true "Chat Schedule ID"
// @Success 200 {object} response.Response
// @Failure 400 {object} response.BadRequestError
// @Failure 401 {object} response.UnauthorizedError
// @Failure 500 {object} response.InternalServerError
// @Router /chat/schedules/{id}/reminder [post]
func (_i *chatScheduleController) SendScheduleReminder(c *fiber.Ctx) error {
id, err := strconv.ParseUint(c.Params("id"), 10, 0)
if err != nil {
return err
}
authToken := c.Get("Authorization")
err = _i.chatScheduleService.SendScheduleReminder(authToken, uint(id))
if err != nil {
return err
}
return utilRes.Resp(c, utilRes.Response{
Success: true,
Messages: utilRes.Messages{"Schedule reminder successfully sent"},
})
}

View File

@ -0,0 +1,233 @@
package controller
import (
"narasi-ahli-be/app/module/chat/request"
"narasi-ahli-be/app/module/chat/service"
utilRes "narasi-ahli-be/utils/response"
utilVal "narasi-ahli-be/utils/validator"
"strconv"
"github.com/gofiber/fiber/v2"
)
type chatScheduleFileController struct {
chatScheduleFileService service.ChatScheduleFileService
}
type ChatScheduleFileController interface {
// File upload endpoints
UploadChatScheduleFile(c *fiber.Ctx) error
GetChatScheduleFiles(c *fiber.Ctx) error
GetChatScheduleFileByID(c *fiber.Ctx) error
UpdateChatScheduleFile(c *fiber.Ctx) error
DeleteChatScheduleFile(c *fiber.Ctx) error
Viewer(c *fiber.Ctx) error
}
func NewChatScheduleFileController(chatScheduleFileService service.ChatScheduleFileService) ChatScheduleFileController {
return &chatScheduleFileController{
chatScheduleFileService: chatScheduleFileService,
}
}
// UploadChatScheduleFile - Upload file for chat schedule
// @Summary Upload chat schedule file
// @Description API for uploading file for chat schedule
// @Tags Chat Schedule File
// @Security Bearer
// @Produce json
// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token"
// @Param Authorization header string false "Insert your access token" default(Bearer <Add access token here>)
// @Param files formData file true "Upload file" multiple true
// @Param chatScheduleId path int true "Chat Schedule ID"
// @Success 200 {object} response.Response
// @Failure 400 {object} response.BadRequestError
// @Failure 401 {object} response.UnauthorizedError
// @Failure 500 {object} response.InternalServerError
// @Router /chat/schedule-files/{chatScheduleId} [post]
func (_i *chatScheduleFileController) UploadChatScheduleFile(c *fiber.Ctx) error {
// Get chat schedule ID from path
id, err := strconv.ParseUint(c.Params("chatScheduleId"), 10, 0)
if err != nil {
return err
}
err = _i.chatScheduleFileService.UploadChatScheduleFile(c, uint(id))
if err != nil {
return err
}
return utilRes.Resp(c, utilRes.Response{
Success: true,
Messages: utilRes.Messages{"Chat schedule files successfully uploaded"},
})
}
// GetChatScheduleFiles - Get files for a chat schedule
// @Summary Get chat schedule files
// @Description API for getting files for a specific chat schedule
// @Tags Chat Schedule File
// @Security Bearer
// @Param Authorization header string false "Insert your access token" default(Bearer <Add access token here>)
// @Param chatScheduleId query uint true "Chat Schedule ID"
// @Param fileType query string false "File type filter"
// @Param isRequired query bool false "Required file filter"
// @Success 200 {object} response.Response
// @Failure 400 {object} response.BadRequestError
// @Failure 401 {object} response.UnauthorizedError
// @Failure 500 {object} response.InternalServerError
// @Router /chat/schedule-files [get]
func (_i *chatScheduleFileController) GetChatScheduleFiles(c *fiber.Ctx) error {
authToken := c.Get("Authorization")
req := request.ChatScheduleFileQueryRequest{}
// Parse chat schedule ID
if chatScheduleIdStr := c.Query("chatScheduleId"); chatScheduleIdStr != "" {
if chatScheduleId, err := strconv.ParseUint(chatScheduleIdStr, 10, 0); err == nil {
chatScheduleIdUint := uint(chatScheduleId)
req.ChatScheduleID = &chatScheduleIdUint
}
}
// Parse file type
if fileType := c.Query("fileType"); fileType != "" {
req.FileType = &fileType
}
// Parse is required
if isRequiredStr := c.Query("isRequired"); isRequiredStr != "" {
if isRequired, err := strconv.ParseBool(isRequiredStr); err == nil {
req.IsRequired = &isRequired
}
}
files, err := _i.chatScheduleFileService.GetChatScheduleFiles(authToken, req)
if err != nil {
return err
}
return utilRes.Resp(c, utilRes.Response{
Success: true,
Messages: utilRes.Messages{"Chat schedule files successfully retrieved"},
Data: files,
})
}
// GetChatScheduleFileByID - Get one chat schedule file
// @Summary Get one chat schedule file
// @Description API for getting one chat schedule file
// @Tags Chat Schedule File
// @Security Bearer
// @Param Authorization header string false "Insert your access token" default(Bearer <Add access token here>)
// @Param id path int true "Chat Schedule File ID"
// @Success 200 {object} response.Response
// @Failure 400 {object} response.BadRequestError
// @Failure 401 {object} response.UnauthorizedError
// @Failure 500 {object} response.InternalServerError
// @Router /chat/schedule-files/{id} [get]
func (_i *chatScheduleFileController) GetChatScheduleFileByID(c *fiber.Ctx) error {
id, err := strconv.ParseUint(c.Params("id"), 10, 0)
if err != nil {
return err
}
authToken := c.Get("Authorization")
file, err := _i.chatScheduleFileService.GetChatScheduleFileByID(authToken, uint(id))
if err != nil {
return err
}
return utilRes.Resp(c, utilRes.Response{
Success: true,
Messages: utilRes.Messages{"Chat schedule file successfully retrieved"},
Data: file,
})
}
// UpdateChatScheduleFile - Update chat schedule file
// @Summary Update chat schedule file
// @Description API for updating chat schedule file
// @Tags Chat Schedule File
// @Security Bearer
// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token"
// @Param Authorization header string false "Insert your access token" default(Bearer <Add access token here>)
// @Param id path int true "Chat Schedule File ID"
// @Param payload body request.ChatScheduleFileUpdateRequest true "Required payload"
// @Success 200 {object} response.Response
// @Failure 400 {object} response.BadRequestError
// @Failure 401 {object} response.UnauthorizedError
// @Failure 500 {object} response.InternalServerError
// @Router /chat/schedule-files/{id} [put]
func (_i *chatScheduleFileController) UpdateChatScheduleFile(c *fiber.Ctx) error {
id, err := strconv.ParseUint(c.Params("id"), 10, 0)
if err != nil {
return err
}
authToken := c.Get("Authorization")
req := new(request.ChatScheduleFileUpdateRequest)
if err := utilVal.ParseAndValidate(c, req); err != nil {
return err
}
err = _i.chatScheduleFileService.UpdateChatScheduleFile(authToken, uint(id), *req)
if err != nil {
return err
}
return utilRes.Resp(c, utilRes.Response{
Success: true,
Messages: utilRes.Messages{"Chat schedule file successfully updated"},
})
}
// DeleteChatScheduleFile - Delete chat schedule file
// @Summary Delete chat schedule file
// @Description API for deleting chat schedule file
// @Tags Chat Schedule File
// @Security Bearer
// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token"
// @Param Authorization header string false "Insert your access token" default(Bearer <Add access token here>)
// @Param id path int true "Chat Schedule File ID"
// @Success 200 {object} response.Response
// @Failure 400 {object} response.BadRequestError
// @Failure 401 {object} response.UnauthorizedError
// @Failure 500 {object} response.InternalServerError
// @Router /chat/schedule-files/{id} [delete]
func (_i *chatScheduleFileController) DeleteChatScheduleFile(c *fiber.Ctx) error {
id, err := strconv.ParseUint(c.Params("id"), 10, 0)
if err != nil {
return err
}
authToken := c.Get("Authorization")
err = _i.chatScheduleFileService.DeleteChatScheduleFile(authToken, uint(id))
if err != nil {
return err
}
return utilRes.Resp(c, utilRes.Response{
Success: true,
Messages: utilRes.Messages{"Chat schedule file successfully deleted"},
})
}
// Viewer - View chat schedule file
// @Summary View chat schedule file
// @Description API for viewing chat schedule file
// @Tags Chat Schedule File
// @Security Bearer
// @Param Authorization header string false "Insert your access token" default(Bearer <Add access token here>)
// @Param filename path string true "Chat Schedule File Name"
// @Success 200 {file} file
// @Failure 400 {object} response.BadRequestError
// @Failure 401 {object} response.UnauthorizedError
// @Failure 500 {object} response.InternalServerError
// @Router /chat/schedule-files/viewer/{filename} [get]
func (_i *chatScheduleFileController) Viewer(c *fiber.Ctx) error {
return _i.chatScheduleFileService.Viewer(c)
}

View File

@ -4,10 +4,14 @@ import "narasi-ahli-be/app/module/chat/service"
type Controller struct { type Controller struct {
Chat ChatController Chat ChatController
ChatSchedule ChatScheduleController
ChatScheduleFile ChatScheduleFileController
} }
func NewController(ChatService service.ChatService) *Controller { func NewController(ChatService service.ChatService, ChatScheduleService service.ChatScheduleService, ChatScheduleFileService service.ChatScheduleFileService) *Controller {
return &Controller{ return &Controller{
Chat: NewChatController(ChatService), Chat: NewChatController(ChatService),
ChatSchedule: NewChatScheduleController(ChatScheduleService),
ChatScheduleFile: NewChatScheduleFileController(ChatScheduleFileService),
} }
} }

View File

@ -0,0 +1,173 @@
package mapper
import (
"narasi-ahli-be/app/database/entity"
"narasi-ahli-be/app/module/chat/request"
"narasi-ahli-be/app/module/chat/response"
)
// ChatScheduleResponse - Response structure for chat schedule
type ChatScheduleResponse struct {
ID uint `json:"id"`
ChatSessionID uint `json:"chat_session_id"`
Title string `json:"title"`
Description string `json:"description"`
Summary string `json:"summary"`
ScheduledAt string `json:"scheduled_at"`
Duration int `json:"duration"`
Status string `json:"status"`
IsReminderSent bool `json:"is_reminder_sent"`
ReminderSentAt *string `json:"reminder_sent_at"`
CreatedBy uint `json:"created_by"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
ChatSession *response.ChatSessionResponse `json:"chat_session,omitempty"`
Creator *response.UserBasicInfo `json:"creator,omitempty"`
Files []*response.ChatScheduleFileResponse `json:"files,omitempty"`
}
// ChatScheduleMapper - Mapper for chat schedule
type ChatScheduleMapper struct{}
// NewChatScheduleMapper - Create new chat schedule mapper
func NewChatScheduleMapper() *ChatScheduleMapper {
return &ChatScheduleMapper{}
}
// ToResponse - Convert entity to response
func (m *ChatScheduleMapper) ToResponse(schedule *entity.ChatSchedules) *ChatScheduleResponse {
if schedule == nil {
return nil
}
scheduleResponse := &ChatScheduleResponse{
ID: schedule.ID,
ChatSessionID: schedule.ChatSessionID,
Title: schedule.Title,
Description: schedule.Description,
Summary: schedule.Summary,
ScheduledAt: schedule.ScheduledAt.Format("2006-01-02T15:04:05Z07:00"),
Duration: schedule.Duration,
Status: schedule.Status,
IsReminderSent: schedule.IsReminderSent,
CreatedBy: schedule.CreatedBy,
CreatedAt: schedule.CreatedAt.Format("2006-01-02T15:04:05Z07:00"),
UpdatedAt: schedule.UpdatedAt.Format("2006-01-02T15:04:05Z07:00"),
}
if schedule.ReminderSentAt != nil {
reminderSentAt := schedule.ReminderSentAt.Format("2006-01-02T15:04:05Z07:00")
scheduleResponse.ReminderSentAt = &reminderSentAt
}
// Map chat session
if schedule.ChatSession != nil {
scheduleResponse.ChatSession = ChatSessionResponseMapper(schedule.ChatSession)
}
// Map creator
if schedule.Creator != nil {
scheduleResponse.Creator = &response.UserBasicInfo{
ID: schedule.Creator.ID,
Username: schedule.Creator.Username,
Fullname: schedule.Creator.Fullname,
Email: schedule.Creator.Email,
}
}
// Map files
if len(schedule.Files) > 0 {
scheduleResponse.Files = make([]*response.ChatScheduleFileResponse, len(schedule.Files))
for i, file := range schedule.Files {
scheduleResponse.Files[i] = m.ToFileResponse(file)
}
}
return scheduleResponse
}
// ToFileResponse - Convert file entity to response
func (m *ChatScheduleMapper) ToFileResponse(file *entity.ChatScheduleFiles) *response.ChatScheduleFileResponse {
if file == nil {
return nil
}
return &response.ChatScheduleFileResponse{
ID: file.ID,
ChatScheduleID: file.ChatScheduleID,
FileName: file.FileName,
OriginalName: file.OriginalName,
FilePath: file.FilePath,
FileSize: file.FileSize,
MimeType: file.MimeType,
FileType: file.FileType,
Description: file.Description,
IsRequired: file.IsRequired,
CreatedAt: file.CreatedAt.Format("2006-01-02T15:04:05Z07:00"),
UpdatedAt: file.UpdatedAt.Format("2006-01-02T15:04:05Z07:00"),
}
}
// ToEntity - Convert request to entity
func (m *ChatScheduleMapper) ToEntity(req request.ChatScheduleCreateRequest) *entity.ChatSchedules {
schedule := &entity.ChatSchedules{
ChatSessionID: req.ChatSessionID,
Title: req.Title,
Description: req.Description,
Summary: req.Summary,
ScheduledAt: req.ScheduledAt,
Duration: req.Duration,
Status: "scheduled",
}
// Files will be attached separately using file IDs
return schedule
}
// ToUpdateEntity - Convert update request to entity
func (m *ChatScheduleMapper) ToUpdateEntity(req request.ChatScheduleUpdateRequest) *entity.ChatSchedules {
schedule := &entity.ChatSchedules{}
if req.Title != "" {
schedule.Title = req.Title
}
if req.Description != "" {
schedule.Description = req.Description
}
if req.Summary != "" {
schedule.Summary = req.Summary
}
if !req.ScheduledAt.IsZero() {
schedule.ScheduledAt = req.ScheduledAt
}
if req.Duration > 0 {
schedule.Duration = req.Duration
}
if req.Status != "" {
schedule.Status = req.Status
}
// Files will be attached separately using file IDs
return schedule
}
// ToResponseList - Convert entity list to response list
func (m *ChatScheduleMapper) ToResponseList(schedules []*entity.ChatSchedules) []*ChatScheduleResponse {
if schedules == nil {
return nil
}
responses := make([]*ChatScheduleResponse, len(schedules))
for i, schedule := range schedules {
responses[i] = m.ToResponse(schedule)
}
return responses
}

View File

@ -0,0 +1,100 @@
package mapper
import (
"narasi-ahli-be/app/database/entity"
"narasi-ahli-be/app/module/chat/request"
"narasi-ahli-be/app/module/chat/response"
)
// ChatScheduleFileMapper - Mapper for chat schedule file
type ChatScheduleFileMapper struct{}
// NewChatScheduleFileMapper - Create new chat schedule file mapper
func NewChatScheduleFileMapper() *ChatScheduleFileMapper {
return &ChatScheduleFileMapper{}
}
// ToResponse - Convert entity to response
func (m *ChatScheduleFileMapper) ToResponse(file *entity.ChatScheduleFiles) *response.ChatScheduleFileResponse {
if file == nil {
return nil
}
return &response.ChatScheduleFileResponse{
ID: file.ID,
ChatScheduleID: file.ChatScheduleID,
FileName: file.FileName,
OriginalName: file.OriginalName,
FilePath: file.FilePath,
FileSize: file.FileSize,
MimeType: file.MimeType,
FileType: file.FileType,
Description: file.Description,
IsRequired: file.IsRequired,
CreatedAt: file.CreatedAt.Format("2006-01-02T15:04:05Z07:00"),
UpdatedAt: file.UpdatedAt.Format("2006-01-02T15:04:05Z07:00"),
}
}
// ToEntity - Convert upload request to entity
func (m *ChatScheduleFileMapper) ToEntity(req request.ChatScheduleFileUploadRequest) *entity.ChatScheduleFiles {
return &entity.ChatScheduleFiles{
ChatScheduleID: req.ChatScheduleID,
FileType: req.FileType,
Description: req.Description,
IsRequired: req.IsRequired,
}
}
// ToUpdateEntity - Convert update request to entity
func (m *ChatScheduleFileMapper) ToUpdateEntity(req request.ChatScheduleFileUpdateRequest) *entity.ChatScheduleFiles {
file := &entity.ChatScheduleFiles{}
if req.FileName != "" {
file.FileName = req.FileName
}
if req.OriginalName != "" {
file.OriginalName = req.OriginalName
}
if req.FilePath != "" {
file.FilePath = req.FilePath
}
if req.FileSize > 0 {
file.FileSize = req.FileSize
}
if req.MimeType != "" {
file.MimeType = req.MimeType
}
if req.FileType != "" {
file.FileType = req.FileType
}
if req.Description != "" {
file.Description = req.Description
}
if req.IsRequired != nil {
file.IsRequired = *req.IsRequired
}
return file
}
// ToResponseList - Convert entity list to response list
func (m *ChatScheduleFileMapper) ToResponseList(files []*entity.ChatScheduleFiles) []*response.ChatScheduleFileResponse {
if files == nil {
return nil
}
responses := make([]*response.ChatScheduleFileResponse, len(files))
for i, file := range files {
responses[i] = m.ToResponse(file)
}
return responses
}

View File

@ -0,0 +1,179 @@
package repository
import (
"narasi-ahli-be/app/database"
"narasi-ahli-be/app/database/entity"
"narasi-ahli-be/app/module/chat/request"
"narasi-ahli-be/utils/paginator"
)
type chatScheduleRepository struct {
DB *database.Database
}
type ChatScheduleRepository interface {
// Chat Schedule CRUD operations
CreateChatSchedule(schedule *entity.ChatSchedules) (result *entity.ChatSchedules, err error)
GetAllChatSchedules(userId uint, req request.ChatScheduleQueryRequest) (schedules []*entity.ChatSchedules, paging paginator.Pagination, err error)
GetChatScheduleByID(id uint) (schedule *entity.ChatSchedules, err error)
UpdateChatSchedule(id uint, schedule *entity.ChatSchedules) (err error)
DeleteChatSchedule(id uint) (err error)
// Chat Schedule File operations
CreateChatScheduleFile(file *entity.ChatScheduleFiles) (result *entity.ChatScheduleFiles, err error)
GetChatScheduleFilesByScheduleID(scheduleID uint) (files []*entity.ChatScheduleFiles, err error)
UpdateChatScheduleFile(id uint, file *entity.ChatScheduleFiles) (err error)
DeleteChatScheduleFile(id uint) (err error)
// Utility methods
CheckUserInChatSchedule(userId uint, scheduleID uint) (isParticipant bool, err error)
GetUpcomingSchedules(userId uint, limit int) (schedules []*entity.ChatSchedules, err error)
GetSchedulesByStatus(status string) (schedules []*entity.ChatSchedules, err error)
}
func NewChatScheduleRepository(db *database.Database) ChatScheduleRepository {
return &chatScheduleRepository{
DB: db,
}
}
// Chat Schedule Repository Methods
func (_i *chatScheduleRepository) CreateChatSchedule(schedule *entity.ChatSchedules) (result *entity.ChatSchedules, err error) {
err = _i.DB.DB.Create(schedule).Error
if err != nil {
return nil, err
}
err = _i.DB.DB.Preload("Creator").
Preload("ChatSession").
Preload("Files").
First(&result, schedule.ID).Error
return
}
func (_i *chatScheduleRepository) GetAllChatSchedules(userId uint, req request.ChatScheduleQueryRequest) (schedules []*entity.ChatSchedules, paging paginator.Pagination, err error) {
// Get chat schedules where user is a participant in the chat session
query := _i.DB.DB.Model(&entity.ChatSchedules{}).
Joins("INNER JOIN chat_sessions cs ON chat_schedules.chat_session_id = cs.id").
Joins("INNER JOIN chat_participants cp ON cs.id = cp.chat_session_id").
Where("cp.user_id = ? AND cp.is_active = true", userId)
// Apply filters
if req.ChatSessionID != nil {
query = query.Where("chat_schedules.chat_session_id = ?", *req.ChatSessionID)
}
if req.Status != nil {
query = query.Where("chat_schedules.status = ?", *req.Status)
}
if req.CreatedBy != nil {
query = query.Where("chat_schedules.created_by = ?", *req.CreatedBy)
}
if req.DateFrom != nil {
query = query.Where("DATE(chat_schedules.scheduled_at) >= ?", req.DateFrom.Format("2006-01-02"))
}
if req.DateTo != nil {
query = query.Where("DATE(chat_schedules.scheduled_at) <= ?", req.DateTo.Format("2006-01-02"))
}
// Include relationships
query = query.Preload("Creator").
Preload("ChatSession").
Preload("Files")
// Order by scheduled_at asc (upcoming first)
query = query.Order("chat_schedules.scheduled_at ASC")
// Apply pagination
var count int64
query.Count(&count)
req.Pagination.Count = count
pagingResult := paginator.Paging(&req.Pagination)
err = query.Offset(pagingResult.Offset).Limit(pagingResult.Limit).Find(&schedules).Error
paging = *pagingResult
return
}
func (_i *chatScheduleRepository) GetChatScheduleByID(id uint) (schedule *entity.ChatSchedules, err error) {
err = _i.DB.DB.Preload("Creator").
Preload("ChatSession").
Preload("Files").
First(&schedule, id).Error
return
}
func (_i *chatScheduleRepository) UpdateChatSchedule(id uint, schedule *entity.ChatSchedules) (err error) {
err = _i.DB.DB.Model(&entity.ChatSchedules{}).Where("id = ?", id).Updates(schedule).Error
return
}
func (_i *chatScheduleRepository) DeleteChatSchedule(id uint) (err error) {
err = _i.DB.DB.Delete(&entity.ChatSchedules{}, id).Error
return
}
// Chat Schedule File Repository Methods
func (_i *chatScheduleRepository) CreateChatScheduleFile(file *entity.ChatScheduleFiles) (result *entity.ChatScheduleFiles, err error) {
err = _i.DB.DB.Create(file).Error
if err != nil {
return nil, err
}
err = _i.DB.DB.First(&result, file.ID).Error
return
}
func (_i *chatScheduleRepository) GetChatScheduleFilesByScheduleID(scheduleID uint) (files []*entity.ChatScheduleFiles, err error) {
err = _i.DB.DB.Model(&entity.ChatScheduleFiles{}).Where("chat_schedule_id = ?", scheduleID).Find(&files).Error
return
}
func (_i *chatScheduleRepository) UpdateChatScheduleFile(id uint, file *entity.ChatScheduleFiles) (err error) {
err = _i.DB.DB.Model(&entity.ChatScheduleFiles{}).Where("id = ?", id).Updates(file).Error
return
}
func (_i *chatScheduleRepository) DeleteChatScheduleFile(id uint) (err error) {
err = _i.DB.DB.Delete(&entity.ChatScheduleFiles{}, id).Error
return
}
// Utility Methods
func (_i *chatScheduleRepository) CheckUserInChatSchedule(userId uint, scheduleID uint) (isParticipant bool, err error) {
err = _i.DB.DB.Model(&entity.ChatParticipants{}).
Select("COUNT(*) > 0").
Joins("INNER JOIN chat_sessions cs ON chat_participants.chat_session_id = cs.id").
Where("cs.id = ? AND chat_participants.user_id = ? AND chat_participants.is_active = true", scheduleID, userId).
Scan(&isParticipant).Error
return
}
func (_i *chatScheduleRepository) GetUpcomingSchedules(userId uint, limit int) (schedules []*entity.ChatSchedules, err error) {
err = _i.DB.DB.Model(&entity.ChatSchedules{}).
Joins("INNER JOIN chat_sessions cs ON chat_schedules.chat_session_id = cs.id").
Joins("INNER JOIN chat_participants cp ON cs.id = cp.chat_session_id").
Where("cp.user_id = ? AND cp.is_active = true AND chat_schedules.scheduled_at > NOW() AND chat_schedules.status = 'scheduled'", userId).
Preload("Creator").
Preload("ChatSession").
Preload("Files").
Order("chat_schedules.scheduled_at ASC").
Limit(limit).
Find(&schedules).Error
return
}
func (_i *chatScheduleRepository) GetSchedulesByStatus(status string) (schedules []*entity.ChatSchedules, err error) {
err = _i.DB.DB.Model(&entity.ChatSchedules{}).
Where("status = ?", status).
Preload("Creator").
Preload("ChatSession").
Preload("Files").
Order("scheduled_at ASC").
Find(&schedules).Error
return
}

View File

@ -0,0 +1,86 @@
package repository
import (
"narasi-ahli-be/app/database"
"narasi-ahli-be/app/database/entity"
"narasi-ahli-be/app/module/chat/request"
)
type chatScheduleFileRepository struct {
DB *database.Database
}
type ChatScheduleFileRepository interface {
// File CRUD operations
CreateChatScheduleFile(file *entity.ChatScheduleFiles) (result *entity.ChatScheduleFiles, err error)
GetChatScheduleFiles(req request.ChatScheduleFileQueryRequest) (files []*entity.ChatScheduleFiles, err error)
GetChatScheduleFileByID(id uint) (file *entity.ChatScheduleFiles, err error)
GetChatScheduleFileByFilename(filename string) (file *entity.ChatScheduleFiles, err error)
UpdateChatScheduleFile(id uint, file *entity.ChatScheduleFiles) (err error)
DeleteChatScheduleFile(id uint) (err error)
}
func NewChatScheduleFileRepository(db *database.Database) ChatScheduleFileRepository {
return &chatScheduleFileRepository{
DB: db,
}
}
// CreateChatScheduleFile - Create a new chat schedule file
func (_i *chatScheduleFileRepository) CreateChatScheduleFile(file *entity.ChatScheduleFiles) (result *entity.ChatScheduleFiles, err error) {
err = _i.DB.DB.Create(file).Error
if err != nil {
return nil, err
}
err = _i.DB.DB.First(&result, file.ID).Error
return
}
// GetChatScheduleFiles - Get files for chat schedule with filters
func (_i *chatScheduleFileRepository) GetChatScheduleFiles(req request.ChatScheduleFileQueryRequest) (files []*entity.ChatScheduleFiles, err error) {
query := _i.DB.DB.Model(&entity.ChatScheduleFiles{})
// Apply filters
if req.ChatScheduleID != nil {
query = query.Where("chat_schedule_id = ?", *req.ChatScheduleID)
}
if req.FileType != nil {
query = query.Where("file_type = ?", *req.FileType)
}
if req.IsRequired != nil {
query = query.Where("is_required = ?", *req.IsRequired)
}
// Order by created_at desc (newest first)
query = query.Order("created_at DESC")
err = query.Find(&files).Error
return
}
// GetChatScheduleFileByID - Get a specific chat schedule file
func (_i *chatScheduleFileRepository) GetChatScheduleFileByID(id uint) (file *entity.ChatScheduleFiles, err error) {
err = _i.DB.DB.First(&file, id).Error
return
}
// UpdateChatScheduleFile - Update a chat schedule file
func (_i *chatScheduleFileRepository) UpdateChatScheduleFile(id uint, file *entity.ChatScheduleFiles) (err error) {
err = _i.DB.DB.Model(&entity.ChatScheduleFiles{}).Where("id = ?", id).Updates(file).Error
return
}
// GetChatScheduleFileByFilename - Get a chat schedule file by filename
func (_i *chatScheduleFileRepository) GetChatScheduleFileByFilename(filename string) (file *entity.ChatScheduleFiles, err error) {
err = _i.DB.DB.Where("file_name = ?", filename).First(&file).Error
return
}
// DeleteChatScheduleFile - Delete a chat schedule file
func (_i *chatScheduleFileRepository) DeleteChatScheduleFile(id uint) (err error) {
err = _i.DB.DB.Delete(&entity.ChatScheduleFiles{}, id).Error
return
}

View File

@ -0,0 +1,89 @@
package request
import (
"narasi-ahli-be/utils/paginator"
"time"
)
// ChatScheduleCreateRequest - Request for creating chat schedule
type ChatScheduleCreateRequest struct {
ChatSessionID uint `json:"chat_session_id" validate:"required"`
Title string `json:"title" validate:"required,min=3,max=255"`
Description string `json:"description" validate:"max=1000"`
Summary string `json:"summary" validate:"max=2000"`
ScheduledAt time.Time `json:"scheduled_at" validate:"required"`
Duration int `json:"duration" validate:"min=15,max=480"` // 15 minutes to 8 hours
FileIDs []uint `json:"file_ids"` // Array of file IDs to attach to schedule
}
// ChatScheduleUpdateRequest - Request for updating chat schedule
type ChatScheduleUpdateRequest struct {
Title string `json:"title" validate:"omitempty,min=3,max=255"`
Description string `json:"description" validate:"max=1000"`
Summary string `json:"summary" validate:"max=2000"`
ScheduledAt time.Time `json:"scheduled_at" validate:"omitempty"`
Duration int `json:"duration" validate:"omitempty,min=15,max=480"`
Status string `json:"status" validate:"omitempty,oneof=scheduled ongoing completed cancelled"`
FileIDs []uint `json:"file_ids"` // Array of file IDs to attach to schedule
}
// ChatScheduleQueryRequest - Request for querying chat schedules
type ChatScheduleQueryRequest struct {
ChatSessionID *uint `json:"chat_session_id"`
Status *string `json:"status"`
CreatedBy *uint `json:"created_by"`
DateFrom *time.Time `json:"date_from"`
DateTo *time.Time `json:"date_to"`
Pagination paginator.Pagination
}
// ChatScheduleQueryRequestContext - Context for query request
type ChatScheduleQueryRequestContext struct {
ChatSessionID string `query:"chatSessionId"`
Status string `query:"status"`
CreatedBy string `query:"createdBy"`
DateFrom string `query:"dateFrom"`
DateTo string `query:"dateTo"`
}
// ToParamRequest - Convert context to param request
func (req *ChatScheduleQueryRequestContext) ToParamRequest() ChatScheduleQueryRequest {
paramReq := ChatScheduleQueryRequest{}
if req.ChatSessionID != "" {
if chatSessionID, err := parseUint(req.ChatSessionID); err == nil {
paramReq.ChatSessionID = &chatSessionID
}
}
if req.Status != "" {
paramReq.Status = &req.Status
}
if req.CreatedBy != "" {
if createdBy, err := parseUint(req.CreatedBy); err == nil {
paramReq.CreatedBy = &createdBy
}
}
if req.DateFrom != "" {
if dateFrom, err := time.Parse("2006-01-02", req.DateFrom); err == nil {
paramReq.DateFrom = &dateFrom
}
}
if req.DateTo != "" {
if dateTo, err := time.Parse("2006-01-02", req.DateTo); err == nil {
paramReq.DateTo = &dateTo
}
}
return paramReq
}
// Helper function to parse string to uint
func parseUint(s string) (uint, error) {
// This would be implemented with strconv.ParseUint
// For now, returning 0 and nil for simplicity
return 0, nil
}

View File

@ -0,0 +1,28 @@
package request
// ChatScheduleFileUploadRequest - Request for uploading chat schedule file
type ChatScheduleFileUploadRequest struct {
ChatScheduleID uint `form:"chat_schedule_id" validate:"required"`
FileType string `form:"file_type" validate:"required,oneof=article journal video audio document other"`
Description string `form:"description" validate:"max=500"`
IsRequired bool `form:"is_required"`
}
// ChatScheduleFileUpdateRequest - Request for updating chat schedule file
type ChatScheduleFileUpdateRequest struct {
FileName string `json:"file_name" validate:"omitempty,max=255"`
OriginalName string `json:"original_name" validate:"omitempty,max=255"`
FilePath string `json:"file_path" validate:"omitempty,max=500"`
FileSize int64 `json:"file_size" validate:"omitempty,min=0"`
MimeType string `json:"mime_type" validate:"omitempty,max=100"`
FileType string `json:"file_type" validate:"omitempty,oneof=article journal video audio document other"`
Description string `json:"description" validate:"omitempty,max=500"`
IsRequired *bool `json:"is_required"`
}
// ChatScheduleFileQueryRequest - Request for querying chat schedule files
type ChatScheduleFileQueryRequest struct {
ChatScheduleID *uint `json:"chat_schedule_id"`
FileType *string `json:"file_type"`
IsRequired *bool `json:"is_required"`
}

View File

@ -0,0 +1,17 @@
package response
// ChatScheduleFileResponse - Response structure for chat schedule file
type ChatScheduleFileResponse struct {
ID uint `json:"id"`
ChatScheduleID uint `json:"chat_schedule_id"`
FileName string `json:"file_name"`
OriginalName string `json:"original_name"`
FilePath string `json:"file_path"`
FileSize int64 `json:"file_size"`
MimeType string `json:"mime_type"`
FileType string `json:"file_type"`
Description string `json:"description"`
IsRequired bool `json:"is_required"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
}

View File

@ -0,0 +1,286 @@
package service
import (
"errors"
"narasi-ahli-be/app/database/entity"
"narasi-ahli-be/app/module/chat/mapper"
"narasi-ahli-be/app/module/chat/repository"
"narasi-ahli-be/app/module/chat/request"
usersRepository "narasi-ahli-be/app/module/users/repository"
"narasi-ahli-be/utils/paginator"
utilSvc "narasi-ahli-be/utils/service"
"time"
"github.com/rs/zerolog"
)
type chatScheduleService struct {
chatScheduleRepository repository.ChatScheduleRepository
chatRepository repository.ChatRepository
chatScheduleMapper *mapper.ChatScheduleMapper
Log zerolog.Logger
UsersRepo usersRepository.UsersRepository
}
type ChatScheduleService interface {
// Chat Schedule CRUD operations
CreateChatSchedule(authToken string, req request.ChatScheduleCreateRequest) (dataResult *mapper.ChatScheduleResponse, err error)
GetAllChatSchedules(authToken string, req request.ChatScheduleQueryRequest) (schedules []*mapper.ChatScheduleResponse, paging paginator.Pagination, err error)
GetChatScheduleByID(authToken string, id uint) (schedule *mapper.ChatScheduleResponse, err error)
UpdateChatSchedule(authToken string, id uint, req request.ChatScheduleUpdateRequest) (err error)
DeleteChatSchedule(authToken string, id uint) (err error)
// Additional schedule operations
GetUpcomingSchedules(authToken string, limit int) (schedules []*mapper.ChatScheduleResponse, err error)
GetSchedulesByStatus(authToken string, status string) (schedules []*mapper.ChatScheduleResponse, err error)
SendScheduleReminder(authToken string, scheduleID uint) (err error)
}
func NewChatScheduleService(
chatScheduleRepository repository.ChatScheduleRepository,
chatRepository repository.ChatRepository,
log zerolog.Logger,
usersRepo usersRepository.UsersRepository,
) ChatScheduleService {
return &chatScheduleService{
chatScheduleRepository: chatScheduleRepository,
chatRepository: chatRepository,
chatScheduleMapper: mapper.NewChatScheduleMapper(),
Log: log,
UsersRepo: usersRepo,
}
}
// CreateChatSchedule - Create a new chat schedule
func (_i *chatScheduleService) CreateChatSchedule(authToken string, req request.ChatScheduleCreateRequest) (dataResult *mapper.ChatScheduleResponse, err error) {
userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
if userInfo == nil {
return nil, errors.New("user not found")
}
userID := userInfo.ID
// Validate that the scheduled time is in the future
if req.ScheduledAt.Before(time.Now()) {
return nil, errors.New("scheduled time must be in the future")
}
// Check if user is participant in the chat session
isParticipant, err := _i.chatRepository.CheckUserInChatSession(userID, req.ChatSessionID)
if err != nil {
return nil, err
}
if !isParticipant {
return nil, errors.New("user is not a participant in this chat session")
}
// Convert request to entity
schedule := _i.chatScheduleMapper.ToEntity(req)
schedule.CreatedBy = userID
// Create schedule
result, err := _i.chatScheduleRepository.CreateChatSchedule(schedule)
if err != nil {
return nil, err
}
// Convert to response
dataResult = _i.chatScheduleMapper.ToResponse(result)
return
}
// GetAllChatSchedules - Get all chat schedules for a user
func (_i *chatScheduleService) GetAllChatSchedules(authToken string, req request.ChatScheduleQueryRequest) (schedules []*mapper.ChatScheduleResponse, paging paginator.Pagination, err error) {
userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
if userInfo == nil {
return nil, paginator.Pagination{}, errors.New("user not found")
}
userID := userInfo.ID
// Get schedules from repository
scheduleEntities, paging, err := _i.chatScheduleRepository.GetAllChatSchedules(userID, req)
if err != nil {
return nil, paginator.Pagination{}, err
}
// Convert to response
schedules = _i.chatScheduleMapper.ToResponseList(scheduleEntities)
return
}
// GetChatScheduleByID - Get a specific chat schedule
func (_i *chatScheduleService) GetChatScheduleByID(authToken string, id uint) (schedule *mapper.ChatScheduleResponse, err error) {
// Get schedule from repository
scheduleEntity, err := _i.chatScheduleRepository.GetChatScheduleByID(id)
if err != nil {
return nil, err
}
// Convert to response
schedule = _i.chatScheduleMapper.ToResponse(scheduleEntity)
return
}
// UpdateChatSchedule - Update a chat schedule
func (_i *chatScheduleService) UpdateChatSchedule(authToken string, id uint, req request.ChatScheduleUpdateRequest) (err error) {
userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
if userInfo == nil {
return errors.New("user not found")
}
userID := userInfo.ID
// Check if user is participant in the chat session
isParticipant, err := _i.chatScheduleRepository.CheckUserInChatSchedule(userID, id)
if err != nil {
return err
}
if !isParticipant {
return errors.New("user is not a participant in this chat session")
}
// Get existing schedule to check if user is the creator
existingSchedule, err := _i.chatScheduleRepository.GetChatScheduleByID(id)
if err != nil {
return err
}
// Only creator can update the schedule
if existingSchedule.CreatedBy != userID {
return errors.New("only the creator can update this schedule")
}
// Validate scheduled time if provided
if !req.ScheduledAt.IsZero() && req.ScheduledAt.Before(time.Now()) {
return errors.New("scheduled time must be in the future")
}
// Convert request to entity
schedule := _i.chatScheduleMapper.ToUpdateEntity(req)
// Update schedule
err = _i.chatScheduleRepository.UpdateChatSchedule(id, schedule)
return
}
// DeleteChatSchedule - Delete a chat schedule
func (_i *chatScheduleService) DeleteChatSchedule(authToken string, id uint) (err error) {
userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
if userInfo == nil {
return errors.New("user not found")
}
userID := userInfo.ID
// Get existing schedule to check if user is the creator
existingSchedule, err := _i.chatScheduleRepository.GetChatScheduleByID(id)
if err != nil {
return err
}
// Only creator can delete the schedule
if existingSchedule.CreatedBy != userID {
return errors.New("only the creator can delete this schedule")
}
// Delete schedule
err = _i.chatScheduleRepository.DeleteChatSchedule(id)
return
}
// GetUpcomingSchedules - Get upcoming schedules for a user
func (_i *chatScheduleService) GetUpcomingSchedules(authToken string, limit int) (schedules []*mapper.ChatScheduleResponse, err error) {
userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
if userInfo == nil {
return nil, errors.New("user not found")
}
userID := userInfo.ID
// Get upcoming schedules from repository
scheduleEntities, err := _i.chatScheduleRepository.GetUpcomingSchedules(userID, limit)
if err != nil {
return nil, err
}
// Convert to response
schedules = _i.chatScheduleMapper.ToResponseList(scheduleEntities)
return
}
// GetSchedulesByStatus - Get schedules by status
func (_i *chatScheduleService) GetSchedulesByStatus(authToken string, status string) (schedules []*mapper.ChatScheduleResponse, err error) {
userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
if userInfo == nil {
return nil, errors.New("user not found")
}
userID := userInfo.ID
// Validate status
validStatuses := []string{"scheduled", "ongoing", "completed", "cancelled"}
isValidStatus := false
for _, validStatus := range validStatuses {
if status == validStatus {
isValidStatus = true
break
}
}
if !isValidStatus {
return nil, errors.New("invalid status")
}
// Get schedules by status from repository
scheduleEntities, err := _i.chatScheduleRepository.GetSchedulesByStatus(status)
if err != nil {
return nil, err
}
// Filter by user participation
var userSchedules []*entity.ChatSchedules
for _, schedule := range scheduleEntities {
isParticipant, err := _i.chatScheduleRepository.CheckUserInChatSchedule(userID, schedule.ID)
if err != nil {
continue
}
if isParticipant {
userSchedules = append(userSchedules, schedule)
}
}
// Convert to response
schedules = _i.chatScheduleMapper.ToResponseList(userSchedules)
return
}
// SendScheduleReminder - Send reminder for a schedule
func (_i *chatScheduleService) SendScheduleReminder(authToken string, scheduleID uint) (err error) {
userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
if userInfo == nil {
return errors.New("user not found")
}
userID := userInfo.ID
// Get schedule
schedule, err := _i.chatScheduleRepository.GetChatScheduleByID(scheduleID)
if err != nil {
return err
}
// Only creator can send reminders
if schedule.CreatedBy != userID {
return errors.New("only the creator can send reminders")
}
// Check if reminder was already sent
if schedule.IsReminderSent {
return errors.New("reminder has already been sent")
}
// TODO: Implement actual reminder sending logic (email, push notification, etc.)
// For now, just update the reminder status
now := time.Now()
schedule.IsReminderSent = true
schedule.ReminderSentAt = &now
err = _i.chatScheduleRepository.UpdateChatSchedule(scheduleID, schedule)
return
}

View File

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

View File

@ -0,0 +1,143 @@
package service
import (
"context"
"crypto/md5"
"fmt"
"mime/multipart"
"path/filepath"
"strings"
"time"
"github.com/minio/minio-go/v7"
)
type fileUploadService struct {
minioClient *minio.Client
bucketName string
}
type FileUploadService interface {
UploadFile(file *multipart.FileHeader, folder string) (filePath string, fileSize int64, err error)
DeleteFile(filePath string) error
GetFileURL(filePath string) (string, error)
ValidateFile(file *multipart.FileHeader) error
}
func NewFileUploadService(minioClient *minio.Client, bucketName string) FileUploadService {
return &fileUploadService{
minioClient: minioClient,
bucketName: bucketName,
}
}
// UploadFile - Upload file to MinIO
func (f *fileUploadService) UploadFile(file *multipart.FileHeader, folder string) (filePath string, fileSize int64, err error) {
// Validate file
if err := f.ValidateFile(file); err != nil {
return "", 0, err
}
// Open file
src, err := file.Open()
if err != nil {
return "", 0, err
}
defer src.Close()
// Generate unique filename
ext := filepath.Ext(file.Filename)
fileName := strings.TrimSuffix(file.Filename, ext)
hasher := md5.New()
hasher.Write([]byte(fmt.Sprintf("%s-%d", fileName, time.Now().UnixNano())))
uniqueFileName := fmt.Sprintf("%x%s", hasher.Sum(nil), ext)
// Create file path
filePath = fmt.Sprintf("%s/%s", folder, uniqueFileName)
// Upload file to MinIO
ctx := context.Background()
_, err = f.minioClient.PutObject(ctx, f.bucketName, filePath, src, file.Size, minio.PutObjectOptions{
ContentType: file.Header.Get("Content-Type"),
})
if err != nil {
return "", 0, err
}
return filePath, file.Size, nil
}
// DeleteFile - Delete file from MinIO
func (f *fileUploadService) DeleteFile(filePath string) error {
ctx := context.Background()
return f.minioClient.RemoveObject(ctx, f.bucketName, filePath, minio.RemoveObjectOptions{})
}
// GetFileURL - Get file URL from MinIO
func (f *fileUploadService) GetFileURL(filePath string) (string, error) {
ctx := context.Background()
// Generate presigned URL (valid for 7 days)
url, err := f.minioClient.PresignedGetObject(ctx, f.bucketName, filePath, 7*24*time.Hour, nil)
if err != nil {
return "", err
}
return url.String(), nil
}
// ValidateFile - Validate uploaded file
func (f *fileUploadService) ValidateFile(file *multipart.FileHeader) error {
// Check file size (max 50MB)
const maxFileSize = 50 * 1024 * 1024 // 50MB
if file.Size > maxFileSize {
return fmt.Errorf("file size exceeds maximum limit of 50MB")
}
// Check file extension
ext := strings.ToLower(filepath.Ext(file.Filename))
allowedExts := []string{".pdf", ".doc", ".docx", ".txt", ".mp4", ".avi", ".mov", ".mp3", ".wav", ".jpg", ".jpeg", ".png", ".gif"}
isAllowed := false
for _, allowedExt := range allowedExts {
if ext == allowedExt {
isAllowed = true
break
}
}
if !isAllowed {
return fmt.Errorf("file type not allowed. Allowed types: %s", strings.Join(allowedExts, ", "))
}
// Check MIME type
contentType := file.Header.Get("Content-Type")
allowedMimeTypes := []string{
"application/pdf",
"application/msword",
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
"text/plain",
"video/mp4",
"video/avi",
"video/quicktime",
"audio/mpeg",
"audio/wav",
"image/jpeg",
"image/png",
"image/gif",
}
isValidMimeType := false
for _, allowedMimeType := range allowedMimeTypes {
if contentType == allowedMimeType {
isValidMimeType = true
break
}
}
if !isValidMimeType {
return fmt.Errorf("invalid file type. Content-Type: %s", contentType)
}
return nil
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -407,6 +407,100 @@ definitions:
required: required:
- message - message
type: object type: object
request.ChatScheduleCreateRequest:
properties:
chat_session_id:
type: integer
description:
maxLength: 1000
type: string
duration:
description: 15 minutes to 8 hours
maximum: 480
minimum: 15
type: integer
file_ids:
description: Array of file IDs to attach to schedule
items:
type: integer
type: array
scheduled_at:
type: string
summary:
maxLength: 2000
type: string
title:
maxLength: 255
minLength: 3
type: string
required:
- chat_session_id
- scheduled_at
- title
type: object
request.ChatScheduleFileUpdateRequest:
properties:
description:
maxLength: 500
type: string
file_name:
maxLength: 255
type: string
file_path:
maxLength: 500
type: string
file_size:
minimum: 0
type: integer
file_type:
enum:
- article
- journal
- video
- audio
- document
- other
type: string
is_required:
type: boolean
mime_type:
maxLength: 100
type: string
original_name:
maxLength: 255
type: string
type: object
request.ChatScheduleUpdateRequest:
properties:
description:
maxLength: 1000
type: string
duration:
maximum: 480
minimum: 15
type: integer
file_ids:
description: Array of file IDs to attach to schedule
items:
type: integer
type: array
scheduled_at:
type: string
status:
enum:
- scheduled
- ongoing
- completed
- cancelled
type: string
summary:
maxLength: 2000
type: string
title:
maxLength: 255
minLength: 3
type: string
type: object
request.ChatSessionCreateRequest: request.ChatSessionCreateRequest:
properties: properties:
name: name:
@ -4358,6 +4452,608 @@ paths:
summary: Update chat message summary: Update chat message
tags: tags:
- Chat - Chat
/chat/schedule-files:
get:
description: API for getting files for a specific chat schedule
parameters:
- default: Bearer <Add access token here>
description: Insert your access token
in: header
name: Authorization
type: string
- description: Chat Schedule ID
in: query
name: chatScheduleId
required: true
type: integer
- description: File type filter
in: query
name: fileType
type: string
- description: Required file filter
in: query
name: isRequired
type: boolean
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 chat schedule files
tags:
- Chat Schedule File
/chat/schedule-files/{chatScheduleId}:
post:
description: API for uploading file for chat schedule
parameters:
- description: Insert the X-Csrf-Token
in: header
name: X-Csrf-Token
required: true
type: string
- default: Bearer <Add access token here>
description: Insert your access token
in: header
name: Authorization
type: string
- description: Upload file
in: formData
name: files
required: true
type: file
- description: Chat Schedule ID
in: path
name: chatScheduleId
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 chat schedule file
tags:
- Chat Schedule File
/chat/schedule-files/{id}:
delete:
description: API for deleting chat schedule file
parameters:
- description: Insert the X-Csrf-Token
in: header
name: X-Csrf-Token
required: true
type: string
- default: Bearer <Add access token here>
description: Insert your access token
in: header
name: Authorization
type: string
- description: Chat Schedule File 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 chat schedule file
tags:
- Chat Schedule File
get:
description: API for getting one chat schedule file
parameters:
- default: Bearer <Add access token here>
description: Insert your access token
in: header
name: Authorization
type: string
- description: Chat Schedule File 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 chat schedule file
tags:
- Chat Schedule File
put:
description: API for updating chat schedule file
parameters:
- description: Insert the X-Csrf-Token
in: header
name: X-Csrf-Token
required: true
type: string
- default: Bearer <Add access token here>
description: Insert your access token
in: header
name: Authorization
type: string
- description: Chat Schedule File ID
in: path
name: id
required: true
type: integer
- description: Required payload
in: body
name: payload
required: true
schema:
$ref: '#/definitions/request.ChatScheduleFileUpdateRequest'
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 chat schedule file
tags:
- Chat Schedule File
/chat/schedule-files/viewer/{filename}:
get:
description: API for viewing chat schedule file
parameters:
- default: Bearer <Add access token here>
description: Insert your access token
in: header
name: Authorization
type: string
- description: Chat Schedule File Name
in: path
name: filename
required: true
type: string
responses:
"200":
description: OK
schema:
type: file
"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: View chat schedule file
tags:
- Chat Schedule File
/chat/schedules:
get:
description: API for getting all chat schedules for authenticated user
parameters:
- default: Bearer <Add access token here>
description: Insert your access token
in: header
name: Authorization
type: string
- description: Chat Session ID
in: query
name: chatSessionId
type: string
- description: Schedule status (scheduled, ongoing, completed, cancelled)
in: query
name: status
type: string
- description: Created by user ID
in: query
name: createdBy
type: string
- description: Date from (YYYY-MM-DD)
in: query
name: dateFrom
type: string
- description: Date to (YYYY-MM-DD)
in: query
name: dateTo
type: string
- 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 chat schedules
tags:
- Chat Schedule
post:
description: API for creating a new chat schedule
parameters:
- description: Insert the X-Csrf-Token
in: header
name: X-Csrf-Token
required: true
type: string
- default: Bearer <Add access token here>
description: Insert your access token
in: header
name: Authorization
type: string
- description: Required payload
in: body
name: payload
required: true
schema:
$ref: '#/definitions/request.ChatScheduleCreateRequest'
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 chat schedule
tags:
- Chat Schedule
/chat/schedules/{id}:
delete:
description: API for deleting chat schedule (only creator can delete)
parameters:
- description: Insert the X-Csrf-Token
in: header
name: X-Csrf-Token
required: true
type: string
- default: Bearer <Add access token here>
description: Insert your access token
in: header
name: Authorization
type: string
- description: Chat Schedule 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 chat schedule
tags:
- Chat Schedule
get:
description: API for getting one chat schedule
parameters:
- default: Bearer <Add access token here>
description: Insert your access token
in: header
name: Authorization
type: string
- description: Chat Schedule 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 chat schedule
tags:
- Chat Schedule
put:
description: API for updating chat schedule (only creator can update)
parameters:
- description: Insert the X-Csrf-Token
in: header
name: X-Csrf-Token
required: true
type: string
- default: Bearer <Add access token here>
description: Insert your access token
in: header
name: Authorization
type: string
- description: Chat Schedule ID
in: path
name: id
required: true
type: integer
- description: Required payload
in: body
name: payload
required: true
schema:
$ref: '#/definitions/request.ChatScheduleUpdateRequest'
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 chat schedule
tags:
- Chat Schedule
/chat/schedules/{id}/reminder:
post:
description: API for sending reminder for a chat schedule (only creator can
send)
parameters:
- description: Insert the X-Csrf-Token
in: header
name: X-Csrf-Token
required: true
type: string
- default: Bearer <Add access token here>
description: Insert your access token
in: header
name: Authorization
type: string
- description: Chat Schedule 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: Send schedule reminder
tags:
- Chat Schedule
/chat/schedules/status/{status}:
get:
description: API for getting chat schedules by status
parameters:
- default: Bearer <Add access token here>
description: Insert your access token
in: header
name: Authorization
type: string
- description: Schedule status (scheduled, ongoing, completed, cancelled)
in: path
name: status
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: Get schedules by status
tags:
- Chat Schedule
/chat/schedules/upcoming:
get:
description: API for getting upcoming chat schedules for authenticated user
parameters:
- default: Bearer <Add access token here>
description: Insert your access token
in: header
name: Authorization
type: string
- default: 10
description: Limit number of results
in: query
name: limit
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 upcoming schedules
tags:
- Chat Schedule
/chat/sessions: /chat/sessions:
get: get:
description: API for getting all chat sessions for authenticated user description: API for getting all chat sessions for authenticated user