This commit is contained in:
@@ -48,6 +48,8 @@ func AutoMigrate(db *gorm.DB) error {
|
||||
&models.ClassSchedule{},
|
||||
&models.ClassCourse{},
|
||||
&models.ClassAllowedApp{},
|
||||
&models.ClassSeatingLayout{},
|
||||
&models.StaffMyClass{},
|
||||
&models.AppPoolEntry{},
|
||||
&models.AppTemplate{},
|
||||
&models.WifiPoolEntry{},
|
||||
|
||||
@@ -811,6 +811,43 @@ func RandomAssignExamPapersHandler(db *gorm.DB) fiber.Handler {
|
||||
}
|
||||
}
|
||||
|
||||
// POST /api/exam-rooms/:id/assign-papers-batch — chia đề theo sơ đồ chỗ ngồi (frontend tính toán)
|
||||
func AssignExamPapersBatchHandler(db *gorm.DB) fiber.Handler {
|
||||
return func(c *fiber.Ctx) error {
|
||||
roomID, err := parseUintParam(c, "id")
|
||||
if err != nil {
|
||||
return c.Status(400).JSON(fiber.Map{"error": "Invalid id"})
|
||||
}
|
||||
var room models.ExamRoom
|
||||
if err := db.First(&room, roomID).Error; err != nil {
|
||||
return c.Status(404).JSON(fiber.Map{"error": "Không tìm thấy phòng"})
|
||||
}
|
||||
if !canModifyExamPapers(db, room) {
|
||||
return c.Status(403).JSON(fiber.Map{"error": "Không thể chia gói đề khi đề đã gửi hoặc phòng thi đã kết thúc"})
|
||||
}
|
||||
var req struct {
|
||||
Assignments []struct {
|
||||
StudentRkID int64 `json:"studentRkId"`
|
||||
PaperID uint `json:"paperId"`
|
||||
} `json:"assignments"`
|
||||
}
|
||||
if err := c.BodyParser(&req); err != nil || len(req.Assignments) == 0 {
|
||||
return c.Status(400).JSON(fiber.Map{"error": "assignments rỗng hoặc không hợp lệ"})
|
||||
}
|
||||
count := 0
|
||||
for _, a := range req.Assignments {
|
||||
pid := a.PaperID
|
||||
res := db.Model(&models.ExamRoomStudent{}).
|
||||
Where("exam_room_id = ? AND student_rk_id = ?", roomID, a.StudentRkID).
|
||||
Update("assigned_paper_id", pid)
|
||||
if res.RowsAffected > 0 {
|
||||
count++
|
||||
}
|
||||
}
|
||||
return c.JSON(fiber.Map{"assigned": count})
|
||||
}
|
||||
}
|
||||
|
||||
// POST /api/exam-rooms/:id/send-papers
|
||||
func SendExamPapersHandler(db *gorm.DB) fiber.Handler {
|
||||
return func(c *fiber.Ctx) error {
|
||||
|
||||
125
server/internal/handlers/handlers_seating.go
Normal file
125
server/internal/handlers/handlers_seating.go
Normal file
@@ -0,0 +1,125 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"server/internal/middleware"
|
||||
"server/internal/models"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
)
|
||||
|
||||
// GET /classes/:rkId/seating-layout
|
||||
func GetClassSeatingLayoutHandler(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 ID"})
|
||||
}
|
||||
var layout models.ClassSeatingLayout
|
||||
if err := db.Where("class_rk_id = ?", classRkID).First(&layout).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return c.JSON(fiber.Map{"layoutJson": nil})
|
||||
}
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
|
||||
}
|
||||
return c.JSON(fiber.Map{"layoutJson": layout.LayoutJSON})
|
||||
}
|
||||
}
|
||||
|
||||
// PUT /classes/:rkId/seating-layout
|
||||
func PutClassSeatingLayoutHandler(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 ID"})
|
||||
}
|
||||
var body struct {
|
||||
LayoutJSON string `json:"layoutJson"`
|
||||
}
|
||||
if err := c.BodyParser(&body); err != nil {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid body"})
|
||||
}
|
||||
if body.LayoutJSON == "" {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "layoutJson is required"})
|
||||
}
|
||||
layout := models.ClassSeatingLayout{
|
||||
ClassRkID: classRkID,
|
||||
LayoutJSON: body.LayoutJSON,
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
result := db.Clauses(clause.OnConflict{
|
||||
Columns: []clause.Column{{Name: "class_rk_id"}},
|
||||
DoUpdates: clause.AssignmentColumns([]string{"layout_json", "updated_at"}),
|
||||
}).Create(&layout)
|
||||
if result.Error != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": result.Error.Error()})
|
||||
}
|
||||
return c.JSON(fiber.Map{"ok": true})
|
||||
}
|
||||
}
|
||||
|
||||
// DELETE /classes/:rkId/seating-layout
|
||||
func DeleteClassSeatingLayoutHandler(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 ID"})
|
||||
}
|
||||
db.Where("class_rk_id = ?", classRkID).Delete(&models.ClassSeatingLayout{})
|
||||
return c.JSON(fiber.Map{"ok": true})
|
||||
}
|
||||
}
|
||||
|
||||
// GET /staff/my-classes
|
||||
func GetMyClassesHandler(db *gorm.DB) fiber.Handler {
|
||||
return func(c *fiber.Ctx) error {
|
||||
staffID := middleware.StaffIDFromCtx(c)
|
||||
type classRow struct {
|
||||
RkID int64 `json:"rkId"`
|
||||
Name string `json:"name"`
|
||||
ClassCode string `json:"classCode"`
|
||||
}
|
||||
var rows []classRow
|
||||
db.Table("staff_my_classes").
|
||||
Select("classes.rk_id, classes.name, classes.class_code").
|
||||
Joins("JOIN classes ON classes.rk_id = staff_my_classes.class_rk_id").
|
||||
Where("staff_my_classes.staff_id = ?", staffID).
|
||||
Scan(&rows)
|
||||
if rows == nil {
|
||||
rows = []classRow{}
|
||||
}
|
||||
return c.JSON(fiber.Map{"data": rows})
|
||||
}
|
||||
}
|
||||
|
||||
// POST /staff/my-classes/:classRkId
|
||||
func AddMyClassHandler(db *gorm.DB) fiber.Handler {
|
||||
return func(c *fiber.Ctx) error {
|
||||
staffID := middleware.StaffIDFromCtx(c)
|
||||
classRkID, err := strconv.ParseInt(c.Params("classRkId"), 10, 64)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid class ID"})
|
||||
}
|
||||
rec := models.StaffMyClass{StaffID: staffID, ClassRkID: classRkID}
|
||||
db.Clauses(clause.OnConflict{DoNothing: true}).Create(&rec)
|
||||
return c.JSON(fiber.Map{"ok": true})
|
||||
}
|
||||
}
|
||||
|
||||
// DELETE /staff/my-classes/:classRkId
|
||||
func RemoveMyClassHandler(db *gorm.DB) fiber.Handler {
|
||||
return func(c *fiber.Ctx) error {
|
||||
staffID := middleware.StaffIDFromCtx(c)
|
||||
classRkID, err := strconv.ParseInt(c.Params("classRkId"), 10, 64)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid class ID"})
|
||||
}
|
||||
db.Where("staff_id = ? AND class_rk_id = ?", staffID, classRkID).Delete(&models.StaffMyClass{})
|
||||
return c.JSON(fiber.Map{"ok": true})
|
||||
}
|
||||
}
|
||||
@@ -97,6 +97,15 @@ type AttendanceResult struct {
|
||||
|
||||
func (AttendanceResult) TableName() string { return "attendance_results" }
|
||||
|
||||
// ClassSeatingLayout lưu sơ đồ chỗ ngồi theo lớp
|
||||
type ClassSeatingLayout struct {
|
||||
ClassRkID int64 `gorm:"column:class_rk_id;primaryKey;not null" json:"classRkId"`
|
||||
LayoutJSON string `gorm:"column:layout_json;type:longtext;not null" json:"layoutJson"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
func (ClassSeatingLayout) TableName() string { return "class_seating_layouts" }
|
||||
|
||||
// ClassAllowedApp lưu từ khóa app được mở của lớp
|
||||
type ClassAllowedApp struct {
|
||||
ClassRkID int64 `gorm:"column:class_rk_id;primaryKey;not null" json:"classRkId"`
|
||||
|
||||
@@ -38,6 +38,14 @@ type PasswordResetToken struct {
|
||||
|
||||
func (PasswordResetToken) TableName() string { return "password_reset_tokens" }
|
||||
|
||||
// StaffMyClass lưu danh sách lớp học cá nhân của giáo viên
|
||||
type StaffMyClass struct {
|
||||
StaffID uint `gorm:"column:staff_id;primaryKey;not null;index" json:"staffId"`
|
||||
ClassRkID int64 `gorm:"column:class_rk_id;primaryKey;not null" json:"classRkId"`
|
||||
}
|
||||
|
||||
func (StaffMyClass) TableName() string { return "staff_my_classes" }
|
||||
|
||||
// ChatMessage — tin nhắn thầy cô ↔ sinh viên
|
||||
type ChatMessage struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
|
||||
@@ -130,6 +130,14 @@ func main() {
|
||||
staff.Get("/classes/:rkId", handlers.GetClassHandler(gormDB))
|
||||
staff.Patch("/classes/:rkId/studying", handlers.PatchClassStudyingHandler(gormDB))
|
||||
staff.Get("/classes/:rkId/students", handlers.ListClassStudentsHandler(gormDB))
|
||||
staff.Get("/classes/:rkId/seating-layout", handlers.GetClassSeatingLayoutHandler(gormDB))
|
||||
staff.Put("/classes/:rkId/seating-layout", handlers.PutClassSeatingLayoutHandler(gormDB))
|
||||
staff.Delete("/classes/:rkId/seating-layout", handlers.DeleteClassSeatingLayoutHandler(gormDB))
|
||||
|
||||
// My classes (lớp cá nhân theo tài khoản)
|
||||
staff.Get("/staff/my-classes", handlers.GetMyClassesHandler(gormDB))
|
||||
staff.Post("/staff/my-classes/:classRkId", handlers.AddMyClassHandler(gormDB))
|
||||
staff.Delete("/staff/my-classes/:classRkId", handlers.RemoveMyClassHandler(gormDB))
|
||||
|
||||
// Students endpoints
|
||||
staff.Get("/students", handlers.ListAllStudentsHandler(gormDB))
|
||||
@@ -204,6 +212,7 @@ func main() {
|
||||
staff.Post("/exam-rooms/:id/unpublish", handlers.UnpublishExamRoomHandler(gormDB))
|
||||
staff.Post("/exam-rooms/:id/cancel", handlers.CancelExamRoomHandler(gormDB))
|
||||
staff.Post("/exam-rooms/:id/assign-random", handlers.RandomAssignExamPapersHandler(gormDB))
|
||||
staff.Post("/exam-rooms/:id/assign-papers-batch", handlers.AssignExamPapersBatchHandler(gormDB))
|
||||
staff.Post("/exam-rooms/:id/send-papers", handlers.SendExamPapersHandler(gormDB))
|
||||
staff.Get("/exam-rooms/:id/submissions", handlers.ListExamSubmissionsHandler(gormDB))
|
||||
staff.Get("/exam-rooms/:id/submissions/download-all", handlers.DownloadAllExamSubmissionsHandler(gormDB))
|
||||
|
||||
Reference in New Issue
Block a user