feat: add chat schedule and the files
This commit is contained in:
parent
9659007a06
commit
94b0345f2b
|
|
@ -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"`
|
||||
}
|
||||
|
|
@ -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"`
|
||||
}
|
||||
|
|
@ -124,6 +124,8 @@ func Models() []interface{} {
|
|||
entity.ChatMessages{},
|
||||
entity.ChatParticipants{},
|
||||
entity.ChatSessions{},
|
||||
entity.ChatSchedules{},
|
||||
entity.ChatScheduleFiles{},
|
||||
entity.AIChatSessions{},
|
||||
entity.AIChatMessages{},
|
||||
entity.AIChatLogs{},
|
||||
|
|
|
|||
|
|
@ -19,12 +19,18 @@ type ChatRouter struct {
|
|||
var NewChatModule = fx.Options(
|
||||
// register repository of Chat module
|
||||
fx.Provide(repository.NewChatRepository),
|
||||
fx.Provide(repository.NewChatScheduleRepository),
|
||||
fx.Provide(repository.NewChatScheduleFileRepository),
|
||||
|
||||
// register service of Chat module
|
||||
fx.Provide(service.NewChatService),
|
||||
fx.Provide(service.NewChatScheduleService),
|
||||
fx.Provide(service.NewChatScheduleFileService),
|
||||
|
||||
// register controller of Chat module
|
||||
fx.Provide(controller.NewController),
|
||||
fx.Provide(controller.NewChatScheduleController),
|
||||
fx.Provide(controller.NewChatScheduleFileController),
|
||||
|
||||
// register router of Chat module
|
||||
fx.Provide(NewChatRouter),
|
||||
|
|
@ -42,6 +48,8 @@ func NewChatRouter(fiber *fiber.App, controller *controller.Controller) *ChatRou
|
|||
func (_i *ChatRouter) RegisterChatRoutes() {
|
||||
// define controllers
|
||||
chatController := _i.Controller.Chat
|
||||
chatScheduleController := _i.Controller.ChatSchedule
|
||||
chatScheduleFileController := _i.Controller.ChatScheduleFile
|
||||
|
||||
// define routes
|
||||
_i.App.Route("/chat", func(router fiber.Router) {
|
||||
|
|
@ -66,5 +74,27 @@ func (_i *ChatRouter) RegisterChatRoutes() {
|
|||
messageRouter.Put("/:id", chatController.UpdateChatMessage)
|
||||
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)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"},
|
||||
})
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -3,11 +3,15 @@ package controller
|
|||
import "narasi-ahli-be/app/module/chat/service"
|
||||
|
||||
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{
|
||||
Chat: NewChatController(ChatService),
|
||||
Chat: NewChatController(ChatService),
|
||||
ChatSchedule: NewChatScheduleController(ChatScheduleService),
|
||||
ChatScheduleFile: NewChatScheduleFileController(ChatScheduleFileService),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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"`
|
||||
}
|
||||
|
|
@ -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"`
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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]
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
1055
docs/swagger/docs.go
1055
docs/swagger/docs.go
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -407,6 +407,100 @@ definitions:
|
|||
required:
|
||||
- message
|
||||
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:
|
||||
properties:
|
||||
name:
|
||||
|
|
@ -4358,6 +4452,608 @@ paths:
|
|||
summary: Update chat message
|
||||
tags:
|
||||
- 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:
|
||||
get:
|
||||
description: API for getting all chat sessions for authenticated user
|
||||
|
|
|
|||
Loading…
Reference in New Issue