283 lines
8.7 KiB
Go
283 lines
8.7 KiB
Go
package handlers
|
|
|
|
import (
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"server/internal/middleware"
|
|
"server/internal/models"
|
|
internalWs "server/internal/websocket"
|
|
|
|
"github.com/gofiber/fiber/v2"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
func chatMessageDTO(m models.ChatMessage, staffName string) fiber.Map {
|
|
return fiber.Map{
|
|
"id": m.ID,
|
|
"staffId": m.StaffID,
|
|
"studentRkId": m.StudentRkID,
|
|
"senderRole": m.SenderRole,
|
|
"body": m.Body,
|
|
"createdAt": m.CreatedAt,
|
|
"readAt": m.ReadAt,
|
|
"staffName": staffName,
|
|
}
|
|
}
|
|
|
|
// GET /api/chat/students?q=
|
|
func SearchChatStudentsHandler(db *gorm.DB) fiber.Handler {
|
|
return func(c *fiber.Ctx) error {
|
|
q := strings.TrimSpace(c.Query("q", ""))
|
|
limit := 30
|
|
query := db.Model(&models.Student{})
|
|
if q != "" {
|
|
like := "%" + q + "%"
|
|
query = query.Where("full_name LIKE ? OR student_code LIKE ? OR email LIKE ?", like, like, like)
|
|
}
|
|
var students []models.Student
|
|
query.Order("full_name asc").Limit(limit).Find(&students)
|
|
out := make([]fiber.Map, 0, len(students))
|
|
for _, s := range students {
|
|
online := internalWs.Hub.IsStudentOnline(s.RkID)
|
|
out = append(out, fiber.Map{
|
|
"studentRkId": s.RkID,
|
|
"fullName": s.FullName,
|
|
"studentCode": s.StudentCode,
|
|
"email": s.Email,
|
|
"online": online,
|
|
})
|
|
}
|
|
return c.JSON(fiber.Map{"data": out})
|
|
}
|
|
}
|
|
|
|
// GET /api/chat/conversations
|
|
func ListChatConversationsHandler(db *gorm.DB) fiber.Handler {
|
|
return func(c *fiber.Ctx) error {
|
|
staffID := middleware.StaffIDFromCtx(c)
|
|
type row struct {
|
|
StudentRkID int64
|
|
LastAt time.Time
|
|
Unread int64
|
|
}
|
|
var rows []row
|
|
db.Raw(`
|
|
SELECT student_rk_id AS student_rk_id, MAX(created_at) AS last_at,
|
|
SUM(CASE WHEN sender_role = 'student' AND read_at IS NULL THEN 1 ELSE 0 END) AS unread
|
|
FROM chat_messages WHERE staff_id = ? GROUP BY student_rk_id ORDER BY last_at DESC LIMIT 50
|
|
`, staffID).Scan(&rows)
|
|
|
|
out := make([]fiber.Map, 0, len(rows))
|
|
for _, r := range rows {
|
|
var st models.Student
|
|
db.Where("rk_id = ?", r.StudentRkID).First(&st)
|
|
var last models.ChatMessage
|
|
db.Where("staff_id = ? AND student_rk_id = ?", staffID, r.StudentRkID).Order("id desc").First(&last)
|
|
out = append(out, fiber.Map{
|
|
"studentRkId": r.StudentRkID,
|
|
"fullName": st.FullName,
|
|
"studentCode": st.StudentCode,
|
|
"lastMessage": last.Body,
|
|
"lastAt": r.LastAt,
|
|
"unread": r.Unread,
|
|
})
|
|
}
|
|
return c.JSON(fiber.Map{"data": out})
|
|
}
|
|
}
|
|
|
|
// GET /api/chat/messages/:studentRkId
|
|
func ListChatMessagesHandler(db *gorm.DB) fiber.Handler {
|
|
return func(c *fiber.Ctx) error {
|
|
staffID := middleware.StaffIDFromCtx(c)
|
|
studentRkID, _ := strconv.ParseInt(c.Params("studentRkId"), 10, 64)
|
|
if studentRkID <= 0 {
|
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "invalid student id"})
|
|
}
|
|
var staff models.StaffAccount
|
|
_ = db.First(&staff, staffID).Error
|
|
var msgs []models.ChatMessage
|
|
db.Where("staff_id = ? AND student_rk_id = ?", staffID, studentRkID).Order("id asc").Limit(200).Find(&msgs)
|
|
now := time.Now()
|
|
db.Model(&models.ChatMessage{}).
|
|
Where("staff_id = ? AND student_rk_id = ? AND sender_role = 'student' AND read_at IS NULL", staffID, studentRkID).
|
|
Update("read_at", now)
|
|
out := make([]fiber.Map, 0, len(msgs))
|
|
for _, m := range msgs {
|
|
out = append(out, chatMessageDTO(m, staff.FullName))
|
|
}
|
|
return c.JSON(fiber.Map{"data": out})
|
|
}
|
|
}
|
|
|
|
// POST /api/chat/messages
|
|
func SendChatMessageHandler(db *gorm.DB) fiber.Handler {
|
|
return func(c *fiber.Ctx) error {
|
|
staffID := middleware.StaffIDFromCtx(c)
|
|
var req struct {
|
|
StudentRkID int64 `json:"studentRkId"`
|
|
Body string `json:"body"`
|
|
}
|
|
if err := c.BodyParser(&req); err != nil {
|
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid payload"})
|
|
}
|
|
body := strings.TrimSpace(req.Body)
|
|
if body == "" || req.StudentRkID <= 0 {
|
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "studentRkId and body required"})
|
|
}
|
|
var staff models.StaffAccount
|
|
if err := db.First(&staff, staffID).Error; err != nil {
|
|
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "staff not found"})
|
|
}
|
|
msg := models.ChatMessage{
|
|
StaffID: staffID,
|
|
StudentRkID: req.StudentRkID,
|
|
SenderRole: "staff",
|
|
Body: body,
|
|
}
|
|
if err := db.Create(&msg).Error; err != nil {
|
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
|
|
}
|
|
dto := chatMessageDTO(msg, staff.FullName)
|
|
internalWs.Hub.PushChatToStudent(req.StudentRkID, dto)
|
|
return c.JSON(fiber.Map{"ok": true, "data": dto})
|
|
}
|
|
}
|
|
|
|
// GET /api/student/chat/conversations?studentRkId=
|
|
func StudentListChatConversationsHandler(db *gorm.DB) fiber.Handler {
|
|
return func(c *fiber.Ctx) error {
|
|
studentRkID, _ := strconv.ParseInt(c.Query("studentRkId"), 10, 64)
|
|
if studentRkID <= 0 {
|
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "studentRkId required"})
|
|
}
|
|
type row struct {
|
|
StaffID uint
|
|
LastAt time.Time
|
|
Unread int64
|
|
}
|
|
var rows []row
|
|
db.Raw(`
|
|
SELECT staff_id AS staff_id, MAX(created_at) AS last_at,
|
|
SUM(CASE WHEN sender_role = 'staff' AND read_at IS NULL THEN 1 ELSE 0 END) AS unread
|
|
FROM chat_messages WHERE student_rk_id = ? GROUP BY staff_id ORDER BY last_at DESC LIMIT 50
|
|
`, studentRkID).Scan(&rows)
|
|
|
|
out := make([]fiber.Map, 0, len(rows))
|
|
for _, r := range rows {
|
|
var staff models.StaffAccount
|
|
name, email := "", ""
|
|
if db.First(&staff, r.StaffID).Error == nil {
|
|
name = staff.FullName
|
|
email = staff.Email
|
|
if name == "" {
|
|
name = email
|
|
}
|
|
}
|
|
var last models.ChatMessage
|
|
db.Where("staff_id = ? AND student_rk_id = ?", r.StaffID, studentRkID).Order("id desc").First(&last)
|
|
out = append(out, fiber.Map{
|
|
"staffId": r.StaffID,
|
|
"staffName": name,
|
|
"staffEmail": email,
|
|
"lastMessage": last.Body,
|
|
"lastAt": r.LastAt,
|
|
"unread": r.Unread,
|
|
})
|
|
}
|
|
return c.JSON(fiber.Map{"data": out})
|
|
}
|
|
}
|
|
|
|
// GET /api/student/chat/messages?studentRkId=&staffId=
|
|
func StudentListChatHandler(db *gorm.DB) fiber.Handler {
|
|
return func(c *fiber.Ctx) error {
|
|
studentRkID, _ := strconv.ParseInt(c.Query("studentRkId"), 10, 64)
|
|
staffID, _ := strconv.ParseUint(c.Query("staffId", "0"), 10, 64)
|
|
if studentRkID <= 0 {
|
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "studentRkId required"})
|
|
}
|
|
query := db.Where("student_rk_id = ?", studentRkID)
|
|
if staffID > 0 {
|
|
query = query.Where("staff_id = ?", staffID)
|
|
}
|
|
var msgs []models.ChatMessage
|
|
query.Order("id asc").Limit(200).Find(&msgs)
|
|
now := time.Now()
|
|
readQ := db.Model(&models.ChatMessage{}).
|
|
Where("student_rk_id = ? AND sender_role = 'staff' AND read_at IS NULL", studentRkID)
|
|
if staffID > 0 {
|
|
readQ = readQ.Where("staff_id = ?", staffID)
|
|
}
|
|
readQ.Update("read_at", now)
|
|
|
|
staffNames := map[uint]string{}
|
|
out := make([]fiber.Map, 0, len(msgs))
|
|
for _, m := range msgs {
|
|
name := staffNames[m.StaffID]
|
|
if name == "" {
|
|
var st models.StaffAccount
|
|
if db.First(&st, m.StaffID).Error == nil {
|
|
name = st.FullName
|
|
if name == "" {
|
|
name = st.Email
|
|
}
|
|
}
|
|
staffNames[m.StaffID] = name
|
|
}
|
|
out = append(out, chatMessageDTO(m, name))
|
|
}
|
|
var lastStaffID uint
|
|
for i := len(msgs) - 1; i >= 0; i-- {
|
|
if msgs[i].SenderRole == "staff" {
|
|
lastStaffID = msgs[i].StaffID
|
|
break
|
|
}
|
|
}
|
|
return c.JSON(fiber.Map{"data": out, "replyStaffId": lastStaffID})
|
|
}
|
|
}
|
|
|
|
// POST /api/student/chat/messages
|
|
func StudentSendChatHandler(db *gorm.DB) fiber.Handler {
|
|
return func(c *fiber.Ctx) error {
|
|
var req struct {
|
|
StudentRkID int64 `json:"studentRkId"`
|
|
StaffID uint `json:"staffId"`
|
|
Body string `json:"body"`
|
|
}
|
|
if err := c.BodyParser(&req); err != nil {
|
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid payload"})
|
|
}
|
|
body := strings.TrimSpace(req.Body)
|
|
if body == "" || req.StudentRkID <= 0 {
|
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "invalid payload"})
|
|
}
|
|
staffID := req.StaffID
|
|
if staffID == 0 {
|
|
var last models.ChatMessage
|
|
if err := db.Where("student_rk_id = ? AND sender_role = 'staff'", req.StudentRkID).
|
|
Order("id desc").First(&last).Error; err != nil {
|
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "chưa có giáo viên nhắn tin"})
|
|
}
|
|
staffID = last.StaffID
|
|
}
|
|
msg := models.ChatMessage{
|
|
StaffID: staffID,
|
|
StudentRkID: req.StudentRkID,
|
|
SenderRole: "student",
|
|
Body: body,
|
|
}
|
|
if err := db.Create(&msg).Error; err != nil {
|
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
|
|
}
|
|
var staff models.StaffAccount
|
|
_ = db.First(&staff, staffID).Error
|
|
dto := chatMessageDTO(msg, staff.FullName)
|
|
internalWs.Hub.PushChatToStaff(staffID, dto)
|
|
return c.JSON(fiber.Map{"ok": true, "data": dto})
|
|
}
|
|
}
|