This commit is contained in:
@@ -735,6 +735,227 @@ func ReportBlockedAppHandler(db *gorm.DB) fiber.Handler {
|
||||
}
|
||||
}
|
||||
|
||||
// POST /api/student/report-violation — SV tắt app / vi phạm khi đang giám sát
|
||||
func ReportViolationHandler(db *gorm.DB) fiber.Handler {
|
||||
return func(c *fiber.Ctx) error {
|
||||
var req struct {
|
||||
StudentRkID int64 `json:"studentRkId"`
|
||||
Kind string `json:"kind"`
|
||||
Reason string `json:"reason"`
|
||||
MonitorMode string `json:"monitorMode"`
|
||||
ClientAt string `json:"clientAt"` // RFC3339 optional
|
||||
}
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid payload format"})
|
||||
}
|
||||
if req.StudentRkID <= 0 || strings.TrimSpace(req.Kind) == "" {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "studentRkId and kind are required"})
|
||||
}
|
||||
|
||||
kind := strings.ToLower(strings.TrimSpace(req.Kind))
|
||||
reason := strings.TrimSpace(req.Reason)
|
||||
if reason == "" {
|
||||
reason = kind
|
||||
}
|
||||
classID := internalDb.FindActiveClassForStudent(db, req.StudentRkID)
|
||||
clientAt := time.Now()
|
||||
if t, err := time.Parse(time.RFC3339, strings.TrimSpace(req.ClientAt)); err == nil {
|
||||
clientAt = t
|
||||
}
|
||||
|
||||
row := models.StudentViolation{
|
||||
StudentRkID: req.StudentRkID,
|
||||
ClassRkID: classID,
|
||||
Kind: kind,
|
||||
Reason: reason,
|
||||
MonitorMode: strings.TrimSpace(req.MonitorMode),
|
||||
ClientAt: clientAt,
|
||||
}
|
||||
if err := db.Create(&row).Error; err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
|
||||
}
|
||||
|
||||
studentName := ""
|
||||
studentCode := ""
|
||||
var st models.Student
|
||||
if err := db.Where("rk_id = ?", req.StudentRkID).First(&st).Error; err == nil {
|
||||
studentName = st.FullName
|
||||
studentCode = st.StudentCode
|
||||
}
|
||||
|
||||
// Cố ý tắt / crash / môi trường → offline ngay, không dùng grace 8s
|
||||
switch kind {
|
||||
case "app_closed", "unclean_shutdown", "wifi",
|
||||
"guard", "multi_monitor", "user_switch", "session_change", "virtual_desktop":
|
||||
internalWs.Hub.ForceStudentOffline(req.StudentRkID)
|
||||
}
|
||||
|
||||
internalWs.Hub.BroadcastStudentViolation(map[string]any{
|
||||
"id": row.ID,
|
||||
"studentId": req.StudentRkID,
|
||||
"studentName": studentName,
|
||||
"studentCode": studentCode,
|
||||
"classId": classID,
|
||||
"kind": kind,
|
||||
"reason": reason,
|
||||
"monitorMode": row.MonitorMode,
|
||||
"clientAt": clientAt.Format(time.RFC3339),
|
||||
"createdAt": row.CreatedAt.Format(time.RFC3339),
|
||||
})
|
||||
|
||||
return c.JSON(fiber.Map{"ok": true, "id": row.ID})
|
||||
}
|
||||
}
|
||||
|
||||
type studentViolationItem struct {
|
||||
ID uint `json:"id"`
|
||||
StudentRkID int64 `json:"studentRkId"`
|
||||
StudentCode string `json:"studentCode"`
|
||||
FullName string `json:"fullName"`
|
||||
ClassRkID int64 `json:"classRkId"`
|
||||
Kind string `json:"kind"`
|
||||
Reason string `json:"reason"`
|
||||
MonitorMode string `json:"monitorMode"`
|
||||
ClientAt time.Time `json:"clientAt"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
}
|
||||
|
||||
// GET /api/classes/:rkId/violations?date=YYYY-MM-DD&kind=
|
||||
func ListClassViolationsHandler(db *gorm.DB) fiber.Handler {
|
||||
return func(c *fiber.Ctx) error {
|
||||
classRkID, err := strconv.ParseInt(c.Params("rkId"), 10, 64)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid class rkId"})
|
||||
}
|
||||
date := c.Query("date")
|
||||
if date == "" {
|
||||
date = time.Now().Format("2006-01-02")
|
||||
}
|
||||
kindFilter := strings.TrimSpace(c.Query("kind", ""))
|
||||
|
||||
var mappings []models.ClassStudent
|
||||
if err := db.Where("class_rk_id = ?", classRkID).Find(&mappings).Error; err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
|
||||
}
|
||||
if len(mappings) == 0 {
|
||||
return c.JSON(fiber.Map{"data": []any{}})
|
||||
}
|
||||
studentIDs := make([]int64, 0, len(mappings))
|
||||
for _, m := range mappings {
|
||||
studentIDs = append(studentIDs, m.StudentRkID)
|
||||
}
|
||||
|
||||
var students []models.Student
|
||||
if err := db.Where("rk_id IN ?", studentIDs).Find(&students).Error; err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
|
||||
}
|
||||
nameByID := map[int64]models.Student{}
|
||||
for _, st := range students {
|
||||
nameByID[st.RkID] = st
|
||||
}
|
||||
|
||||
dayStart, err := time.ParseInLocation("2006-01-02", date, time.Local)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid date"})
|
||||
}
|
||||
dayEnd := dayStart.Add(24 * time.Hour)
|
||||
|
||||
q := db.Where("student_rk_id IN ? AND created_at >= ? AND created_at < ?", studentIDs, dayStart, dayEnd)
|
||||
if kindFilter != "" {
|
||||
q = q.Where("kind = ?", kindFilter)
|
||||
}
|
||||
var rows []models.StudentViolation
|
||||
if err := q.Order("created_at desc").Limit(500).Find(&rows).Error; err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
|
||||
}
|
||||
|
||||
out := make([]studentViolationItem, 0, len(rows))
|
||||
for _, r := range rows {
|
||||
st := nameByID[r.StudentRkID]
|
||||
out = append(out, studentViolationItem{
|
||||
ID: r.ID,
|
||||
StudentRkID: r.StudentRkID,
|
||||
StudentCode: st.StudentCode,
|
||||
FullName: st.FullName,
|
||||
ClassRkID: r.ClassRkID,
|
||||
Kind: r.Kind,
|
||||
Reason: r.Reason,
|
||||
MonitorMode: r.MonitorMode,
|
||||
ClientAt: r.ClientAt,
|
||||
CreatedAt: r.CreatedAt,
|
||||
})
|
||||
}
|
||||
return c.JSON(fiber.Map{"data": out, "date": date})
|
||||
}
|
||||
}
|
||||
|
||||
// GET /api/exam-rooms/:id/violations?date=YYYY-MM-DD
|
||||
func ListExamRoomViolationsHandler(db *gorm.DB) fiber.Handler {
|
||||
return func(c *fiber.Ctx) error {
|
||||
id, err := strconv.ParseUint(c.Params("id"), 10, 64)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid exam room id"})
|
||||
}
|
||||
date := c.Query("date")
|
||||
if date == "" {
|
||||
date = time.Now().Format("2006-01-02")
|
||||
}
|
||||
kindFilter := strings.TrimSpace(c.Query("kind", ""))
|
||||
|
||||
var roster []models.ExamRoomStudent
|
||||
if err := db.Where("exam_room_id = ?", id).Find(&roster).Error; err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
|
||||
}
|
||||
if len(roster) == 0 {
|
||||
return c.JSON(fiber.Map{"data": []any{}})
|
||||
}
|
||||
studentIDs := make([]int64, 0, len(roster))
|
||||
for _, s := range roster {
|
||||
studentIDs = append(studentIDs, s.StudentRkID)
|
||||
}
|
||||
|
||||
var students []models.Student
|
||||
_ = db.Where("rk_id IN ?", studentIDs).Find(&students)
|
||||
nameByID := map[int64]models.Student{}
|
||||
for _, st := range students {
|
||||
nameByID[st.RkID] = st
|
||||
}
|
||||
|
||||
dayStart, err := time.ParseInLocation("2006-01-02", date, time.Local)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid date"})
|
||||
}
|
||||
dayEnd := dayStart.Add(24 * time.Hour)
|
||||
|
||||
q := db.Where("student_rk_id IN ? AND created_at >= ? AND created_at < ?", studentIDs, dayStart, dayEnd)
|
||||
if kindFilter != "" {
|
||||
q = q.Where("kind = ?", kindFilter)
|
||||
}
|
||||
var rows []models.StudentViolation
|
||||
if err := q.Order("created_at desc").Limit(500).Find(&rows).Error; err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
|
||||
}
|
||||
|
||||
out := make([]studentViolationItem, 0, len(rows))
|
||||
for _, r := range rows {
|
||||
st := nameByID[r.StudentRkID]
|
||||
out = append(out, studentViolationItem{
|
||||
ID: r.ID,
|
||||
StudentRkID: r.StudentRkID,
|
||||
StudentCode: st.StudentCode,
|
||||
FullName: st.FullName,
|
||||
ClassRkID: r.ClassRkID,
|
||||
Kind: r.Kind,
|
||||
Reason: r.Reason,
|
||||
MonitorMode: r.MonitorMode,
|
||||
ClientAt: r.ClientAt,
|
||||
CreatedAt: r.CreatedAt,
|
||||
})
|
||||
}
|
||||
return c.JSON(fiber.Map{"data": out, "date": date})
|
||||
}
|
||||
}
|
||||
|
||||
// GET /api/app-pool — kho app bị chặn toàn hệ thống (?q= tìm kiếm, limit= mặc định 50)
|
||||
func ListAppPoolHandler(db *gorm.DB) fiber.Handler {
|
||||
return func(c *fiber.Ctx) error {
|
||||
|
||||
Reference in New Issue
Block a user