clean ui
This commit is contained in:
@@ -49,6 +49,7 @@ func AutoMigrate(db *gorm.DB) error {
|
||||
&models.ClassCourse{},
|
||||
&models.ClassAllowedApp{},
|
||||
&models.AppPoolEntry{},
|
||||
&models.AppTemplate{},
|
||||
&models.WifiPoolEntry{},
|
||||
&models.AcceptedWifi{},
|
||||
&models.StudentSession{},
|
||||
|
||||
119
server/internal/handlers/handlers_app_templates.go
Normal file
119
server/internal/handlers/handlers_app_templates.go
Normal file
@@ -0,0 +1,119 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"server/internal/models"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// GET /api/app-templates
|
||||
func ListAppTemplatesHandler(db *gorm.DB) fiber.Handler {
|
||||
return func(c *fiber.Ctx) error {
|
||||
var rows []models.AppTemplate
|
||||
if err := db.Order("name asc").Find(&rows).Error; err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
|
||||
}
|
||||
return c.JSON(fiber.Map{"data": rows})
|
||||
}
|
||||
}
|
||||
|
||||
// POST /api/app-templates
|
||||
func CreateAppTemplateHandler(db *gorm.DB) fiber.Handler {
|
||||
return func(c *fiber.Ctx) error {
|
||||
var req struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Keywords string `json:"keywords"`
|
||||
}
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid payload"})
|
||||
}
|
||||
name := strings.TrimSpace(req.Name)
|
||||
if name == "" {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "name required"})
|
||||
}
|
||||
keywords := normalizeKeywordCSV(req.Keywords)
|
||||
if keywords == "" {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "keywords required"})
|
||||
}
|
||||
row := models.AppTemplate{
|
||||
Name: name,
|
||||
Description: strings.TrimSpace(req.Description),
|
||||
Keywords: keywords,
|
||||
}
|
||||
if err := db.Create(&row).Error; err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
|
||||
}
|
||||
return c.JSON(fiber.Map{"ok": true, "data": row})
|
||||
}
|
||||
}
|
||||
|
||||
// PATCH /api/app-templates/:id
|
||||
func UpdateAppTemplateHandler(db *gorm.DB) fiber.Handler {
|
||||
return func(c *fiber.Ctx) error {
|
||||
var row models.AppTemplate
|
||||
if err := db.First(&row, c.Params("id")).Error; err != nil {
|
||||
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "not found"})
|
||||
}
|
||||
var req struct {
|
||||
Name *string `json:"name"`
|
||||
Description *string `json:"description"`
|
||||
Keywords *string `json:"keywords"`
|
||||
}
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid payload"})
|
||||
}
|
||||
if req.Name != nil {
|
||||
name := strings.TrimSpace(*req.Name)
|
||||
if name == "" {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "name required"})
|
||||
}
|
||||
row.Name = name
|
||||
}
|
||||
if req.Description != nil {
|
||||
row.Description = strings.TrimSpace(*req.Description)
|
||||
}
|
||||
if req.Keywords != nil {
|
||||
keywords := normalizeKeywordCSV(*req.Keywords)
|
||||
if keywords == "" {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "keywords required"})
|
||||
}
|
||||
row.Keywords = keywords
|
||||
}
|
||||
if err := db.Save(&row).Error; err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
|
||||
}
|
||||
return c.JSON(fiber.Map{"ok": true, "data": row})
|
||||
}
|
||||
}
|
||||
|
||||
// DELETE /api/app-templates/:id
|
||||
func DeleteAppTemplateHandler(db *gorm.DB) fiber.Handler {
|
||||
return func(c *fiber.Ctx) error {
|
||||
if err := db.Delete(&models.AppTemplate{}, c.Params("id")).Error; err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
|
||||
}
|
||||
return c.JSON(fiber.Map{"ok": true})
|
||||
}
|
||||
}
|
||||
|
||||
func normalizeKeywordCSV(raw string) string {
|
||||
parts := strings.Split(raw, ",")
|
||||
seen := make(map[string]struct{})
|
||||
var out []string
|
||||
for _, p := range parts {
|
||||
kw := strings.ToLower(strings.TrimSpace(p))
|
||||
if kw == "" {
|
||||
continue
|
||||
}
|
||||
if _, ok := seen[kw]; ok {
|
||||
continue
|
||||
}
|
||||
seen[kw] = struct{}{}
|
||||
out = append(out, kw)
|
||||
}
|
||||
return strings.Join(out, ", ")
|
||||
}
|
||||
@@ -122,6 +122,18 @@ type AppPoolEntry struct {
|
||||
|
||||
func (AppPoolEntry) TableName() string { return "app_pool" }
|
||||
|
||||
// AppTemplate — khung ứng dụng (bộ keyword whitelist) dùng chung cho lớp / phòng thi
|
||||
type AppTemplate struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
Name string `gorm:"column:name;size:120;not null" json:"name"`
|
||||
Description string `gorm:"column:description;type:text" json:"description"`
|
||||
Keywords string `gorm:"column:keywords;type:text;not null" json:"keywords"`
|
||||
}
|
||||
|
||||
func (AppTemplate) TableName() string { return "app_templates" }
|
||||
|
||||
// WifiPoolEntry — kho WiFi phát hiện từ máy sinh viên (SSID + BSSID điểm phát)
|
||||
type WifiPoolEntry struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
|
||||
Reference in New Issue
Block a user