package main import ( "log" "os" "server/internal/db" "server/internal/handlers" "server/internal/qldt" "server/internal/syncjobs" internalWs "server/internal/websocket" "github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2/middleware/cors" gofiberWs "github.com/gofiber/websocket/v2" "github.com/joho/godotenv" ) func main() { if err := godotenv.Load(); err != nil { log.Println("Note: .env file not found or couldn't be loaded, using system environments.") } dbHost := getEnv("DB_HOST", "127.0.0.1") dbPort := getEnv("DB_PORT", "3306") dbUser := getEnv("DB_USER", "naruto") dbPass := getEnv("DB_PASSWORD", "29121999aA@") dbName := getEnv("DB_NAME", "simple_care") qldtToken := getEnv("QLDT_TOKEN", "") qldtBaseURL := getEnv("QLDT_BASE_URL", "https://apiportal.rikkei.edu.vn") qldtOrigin := getEnv("QLDT_ORIGIN", "https://qldt.rikkei.edu.vn") if qldtToken == "" { log.Println("WARNING: QLDT_TOKEN is empty! Sync features will fail until QLDT_TOKEN is configured in environment.") } gormDB, err := db.ConnectDB(dbHost, dbPort, dbUser, dbPass, dbName) if err != nil { log.Fatalf("Database connection failed: %v", err) } if err := db.AutoMigrate(gormDB); err != nil { log.Fatalf("Database migration failed: %v", err) } qldtClient := qldt.NewClient(qldtBaseURL, qldtOrigin) classesJob := syncjobs.NewClassesSyncJob() studentsJob := syncjobs.NewStudentsSyncJob() app := fiber.New(fiber.Config{ AppName: "Simple Care Sync Backend", }) app.Use(cors.New(cors.Config{ AllowOrigins: "*", AllowHeaders: "Origin, Content-Type, Accept, Authorization", AllowMethods: "GET, POST, PATCH, PUT, DELETE, OPTIONS", })) // WebSocket Upgrade Check app.Use("/ws", func(c *fiber.Ctx) error { if gofiberWs.IsWebSocketUpgrade(c) { c.Locals("allowed", true) return c.Next() } return fiber.ErrUpgradeRequired }) // WebSocket Route app.Get("/ws", gofiberWs.New(internalWs.WebSocketHandler(gormDB))) api := app.Group("/api") // Dashboard Stats api.Get("/stats", handlers.GetStatsHandler(gormDB)) // Classes endpoints api.Get("/classes", handlers.ListClassesHandler(gormDB)) api.Get("/classes/schedules", handlers.ListActiveSchedulesHandler(gormDB)) api.Get("/classes/:rkId", handlers.GetClassHandler(gormDB)) api.Patch("/classes/:rkId/studying", handlers.PatchClassStudyingHandler(gormDB)) api.Get("/classes/:rkId/students", handlers.ListClassStudentsHandler(gormDB)) // Students endpoints api.Get("/students", handlers.ListAllStudentsHandler(gormDB)) // Sync endpoints api.Post("/sync/classes/start", handlers.StartClassesSyncHandler(gormDB, qldtClient, classesJob, qldtToken)) api.Get("/sync/classes/status", handlers.GetClassesSyncStatusHandler(classesJob)) api.Post("/sync/students/start", handlers.StartStudentsSyncHandler(gormDB, qldtClient, studentsJob, qldtToken)) api.Get("/sync/students/status", handlers.GetStudentsSyncStatusHandler(studentsJob)) // Learning Management endpoints api.Get("/classes/:rkId/schedule", handlers.GetClassScheduleHandler(gormDB)) api.Post("/classes/:rkId/schedule", handlers.SaveClassScheduleHandler(gormDB)) api.Delete("/classes/:rkId/schedule", handlers.DeleteClassScheduleHandler(gormDB)) api.Get("/classes/:rkId/allowed-apps", handlers.GetAllowedAppsHandler(gormDB)) api.Post("/classes/:rkId/allowed-apps", handlers.SaveAllowedAppsHandler(gormDB)) api.Get("/app-pool", handlers.ListAppPoolHandler(gormDB)) api.Get("/wifi-pool", handlers.ListWifiPoolHandler(gormDB)) api.Get("/network/accepted-wifis", handlers.ListAcceptedWifisHandler(gormDB)) api.Post("/network/accepted-wifis", handlers.SaveAcceptedWifisHandler(gormDB)) api.Post("/network/accepted-wifis/add", handlers.AddAcceptedWifiHandler(gormDB)) api.Delete("/network/accepted-wifis/:id", handlers.DeleteAcceptedWifiHandler(gormDB)) api.Get("/classes/:rkId/session-logs", handlers.ListClassSessionLogsHandler(gormDB)) api.Get("/classes/:rkId/online-students", handlers.GetOnlineStudentsHandler(gormDB)) api.Get("/classes/:rkId/courses", handlers.GetClassCoursesHandler(gormDB, qldtClient, qldtToken)) api.Post("/classes/:rkId/schedule/apply-template", handlers.ApplyScheduleTemplateHandler(gormDB)) api.Get("/classes/:rkId/attendance", handlers.GetClassAttendanceHandler(gormDB)) api.Get("/classes/:rkId/attendance/shifts", handlers.ListAttendanceShiftsHandler(gormDB)) api.Put("/classes/:rkId/attendance/status", handlers.UpdateAttendanceStatusHandler(gormDB)) api.Post("/classes/:rkId/attendance/push-qldt", handlers.PushAttendanceToQLDTHandler(gormDB, qldtClient, qldtToken)) // Student syncing offline-resilient log api.Post("/student/sync-log", handlers.SyncStudentSessionLogHandler(gormDB)) api.Get("/student/status", handlers.GetStudentStatusHandler(gormDB)) api.Get("/student/wifi-policy", handlers.GetStudentWifiPolicyHandler(gormDB)) api.Post("/student/report-wifi", handlers.ReportWifiHandler(gormDB)) api.Post("/student/report-blocked-app", handlers.ReportBlockedAppHandler(gormDB)) port := getEnv("PORT", "8080") log.Printf("Server starting on port %s...", port) if err := app.Listen(":" + port); err != nil { log.Fatalf("Failed to start web server: %v", err) } } func getEnv(key, defaultVal string) string { if val := os.Getenv(key); val != "" { return val } return defaultVal }