diff --git a/backend/controllers/hardwarecontroller.go b/backend/controllers/hardwarecontroller.go index 0b3f0a8..3d20af5 100644 --- a/backend/controllers/hardwarecontroller.go +++ b/backend/controllers/hardwarecontroller.go @@ -7,9 +7,11 @@ import ( "net/http" "os" "strconv" // Digunakan untuk mengubah teks Watt menjadi angka + "strings" // Digunakan untuk manipulasi string (ToUpper/Trim) "time" "s-class-backend/config" // Import database + "s-class-backend/models" // Import models untuk query database mqtt "github.com/eclipse/paho.mqtt.golang" "github.com/gin-gonic/gin" @@ -25,7 +27,6 @@ type VerifyRequest struct { Token string `json:"token"` } -// Tambahan untuk kontrol daya global (ON/OFF) type GlobalPowerRequest struct { Action string `json:"action"` // "on" atau "off" } @@ -41,7 +42,7 @@ var DeviceStatusCache = map[string]string{ } // ========================================================================= -// FUNGSI 1: VERIFIKASI TOKEN (Bisa Master & Mahasiswa) +// FUNGSI 1: VERIFIKASI TOKEN & KODE MATA KULIAH (ON-DEMAND) // ========================================================================= func VerifyHardwareCode(c *gin.Context) { var req VerifyRequest @@ -51,8 +52,9 @@ func VerifyHardwareCode(c *gin.Context) { return } - tokenInput := req.Token - sekarang := time.Now() + tokenInput := strings.ToUpper(strings.TrimSpace(req.Token)) + loc, _ := time.LoadLocation("Asia/Jakarta") + sekarang := time.Now().In(loc) sisaMenit := 0 isTokenValid := false @@ -65,71 +67,74 @@ func VerifyHardwareCode(c *gin.Context) { isTokenValid = true } else if tokenInput == "ADM999" { fmt.Printf("[VERIFY] Master Token Admin digunakan\n") - sisaMenit = 999 + sisaMenit = 1440 // 24 Jam isTokenValid = true } else { // ========================================================= - // 2. PENGECEKAN KELOMPOK TOKEN MAHASISWA (Cek Database & Waktu) + // 2. CEK TOKEN BOOKING VIA WEB (6 DIGIT) // ========================================================= - var jamMulai time.Time - var jamSelesai time.Time - type ResultTime struct { - JamMulai time.Time `gorm:"column:start_time"` - JamSelesai time.Time `gorm:"column:end_time"` - } - var result ResultTime + var booking models.Booking + errBooking := config.DB.First(&booking, "redeem_code = ? AND status = 'Approved'", tokenInput).Error - // Cek Bookings - errBooking := config.DB.Table("bookings"). - Select("start_time, end_time"). - Where("redeem_code = ?", tokenInput). - Scan(&result).Error + if errBooking == nil { + // Cek apakah hari ini (tanggal) sama dengan hari booking + if booking.StartTime.Format("2006-01-02") != sekarang.Format("2006-01-02") { + c.JSON(http.StatusForbidden, gin.H{"error": "Peminjaman bukan untuk hari ini"}) + return + } - if errBooking == nil && !result.JamSelesai.IsZero() { - jamMulai = result.JamMulai - jamSelesai = result.JamSelesai + toleransiMasuk := booking.StartTime.Add(-15 * time.Minute) + if sekarang.Before(toleransiMasuk) { + c.JSON(http.StatusForbidden, gin.H{"error": "Belum waktunya. Bisa masuk 15 mnt sblm jam peminjaman!"}) + return + } + if sekarang.After(booking.EndTime) { + c.JSON(http.StatusForbidden, gin.H{"error": "Waktu peminjaman sudah habis!"}) + return + } + + sisaMenit = int(booking.EndTime.Sub(sekarang).Minutes()) + if sisaMenit <= 0 { + sisaMenit = 1 + } isTokenValid = true - } else { - // Cek Jadwal Kelas Tetap - errSchedule := config.DB.Table("class_schedules"). - Select("start_time, end_time"). - Where("kode_mk = ?", tokenInput). - Scan(&result).Error - if errSchedule == nil && !result.JamSelesai.IsZero() { - jamMulai = result.JamMulai - jamSelesai = result.JamSelesai + } else { + // ========================================================= + // 3. CEK KODE MATA KULIAH (ON-DEMAND FLEKSIBEL) + // ========================================================= + var schedule models.ClassSchedule + // Abaikan hari dan jam, cukup cek apakah kode matkul eksis di database + errSchedule := config.DB.First(&schedule, "kode_mk = ?", tokenInput).Error + + if errSchedule == nil { + // KODE MATKUL EKSIS. + // CEK BENTROK: Apakah ada orang yang SEDANG booking via web sekarang? + var countBookingAktif int64 + config.DB.Model(&models.Booking{}).Where( + "room_id = ? AND status = 'Approved' AND start_time <= ? AND end_time >= ?", + schedule.RoomID, sekarang, sekarang, + ).Count(&countBookingAktif) + + if countBookingAktif > 0 { + c.JSON(http.StatusConflict, gin.H{"error": "Ruangan sedang dipakai peminjam resmi (Web)!"}) + return + } + + // Jika aman (tidak ada yang booking), berikan akses 120 Menit! + sisaMenit = 120 isTokenValid = true } } if !isTokenValid { - c.JSON(http.StatusUnauthorized, gin.H{"error": "Token salah atau tidak ditemukan"}) - return - } - - jamMulaiHariIni := time.Date(sekarang.Year(), sekarang.Month(), sekarang.Day(), - jamMulai.Hour(), jamMulai.Minute(), jamMulai.Second(), 0, sekarang.Location()) - jamSelesaiHariIni := time.Date(sekarang.Year(), sekarang.Month(), sekarang.Day(), - jamSelesai.Hour(), jamSelesai.Minute(), jamSelesai.Second(), 0, sekarang.Location()) - - batasMasukAwal := jamMulaiHariIni.Add(-15 * time.Minute) - if sekarang.Before(batasMasukAwal) { - c.JSON(http.StatusForbidden, gin.H{"error": "Jadwal kelas belum dimulai"}) - return - } - - selisihWaktu := jamSelesaiHariIni.Sub(sekarang) - sisaMenit = int(selisihWaktu.Minutes()) - - if sisaMenit <= 0 { - c.JSON(http.StatusForbidden, gin.H{"error": "Waktu peminjaman sudah habis"}) + c.JSON(http.StatusUnauthorized, gin.H{"error": "Token atau Kode MK tidak ditemukan / salah!"}) return } } // ========================================================= - // 3. JIKA VALID (Master / Mahasiswa), BUKA GEMBOK VIA MQTT + // 4. JIKA VALID (Master / Matkul / Web), BUKA GEMBOK VIA MQTT // ========================================================= DeviceStatusCache["lampu1"] = "on" DeviceStatusCache["lampu2"] = "on" @@ -157,7 +162,7 @@ func VerifyHardwareCode(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ "status": "success", - "message": "Token Valid", + "message": "Akses Diberikan", "duration_minutes": sisaMenit, }) } @@ -172,7 +177,6 @@ func ControlHardware(c *gin.Context) { return } - // 🌟 BLOK 1: KONTROL GEMBOK / OTORISASI RELAY if req.Device == "auth" { broker := os.Getenv("MQTT_BROKER") user := os.Getenv("MQTT_USER") @@ -191,7 +195,6 @@ func ControlHardware(c *gin.Context) { return } - // --- BLOK 2: KONTROL LAMPU --- if req.Device == "lampu1" || req.Device == "lampu2" { broker := os.Getenv("MQTT_BROKER") user := os.Getenv("MQTT_USER") @@ -219,7 +222,6 @@ func ControlHardware(c *gin.Context) { return } - // --- BLOK 3: KONTROL AC & PROYEKTOR --- if req.Device == "ac" || req.Device == "projector" { haURL := os.Getenv("HA_URL") haToken := os.Getenv("HA_TOKEN") @@ -264,7 +266,7 @@ func ControlHardware(c *gin.Context) { } // ========================================================================= -// HELPER 1: MENGAMBIL ANGKA DARI HOME ASSISTANT (Untuk Daya/Watt) +// HELPER 1 & 2: HTTP GET KE HOME ASSISTANT // ========================================================================= func fetchHAState(haURL string, haToken string, entityID string) float64 { apiURL := fmt.Sprintf("%s/api/states/%s", haURL, entityID) @@ -294,9 +296,6 @@ func fetchHAState(haURL string, haToken string, entityID string) float64 { return val } -// ========================================================================= -// HELPER 2: MENGAMBIL STATUS TEKS DARI HOME ASSISTANT (Untuk ON/OFF) -// ========================================================================= func fetchHAStringState(haURL string, haToken string, entityID string) string { apiURL := fmt.Sprintf("%s/api/states/%s", haURL, entityID) @@ -325,7 +324,7 @@ func fetchHAStringState(haURL string, haToken string, entityID string) string { } // ========================================================================= -// FUNGSI 3: MENDAPATKAN STATUS DAYA DARI 3 MCB SEKALIGUS +// FUNGSI 3: MENDAPATKAN STATUS DAYA // ========================================================================= func GetPowerStatus(c *gin.Context) { haURL := os.Getenv("HA_URL") @@ -343,7 +342,7 @@ func GetPowerStatus(c *gin.Context) { } // ========================================================================= -// FUNGSI 4: GLOBAL POWER CONTROL (Sequential ON/OFF dengan Jeda 1 Detik) +// FUNGSI 4: GLOBAL POWER CONTROL // ========================================================================= func GlobalPowerControl(c *gin.Context) { var req GlobalPowerRequest @@ -404,31 +403,25 @@ func GlobalPowerControl(c *gin.Context) { } // ========================================================================= -// FUNGSI 5: MENGIRIM STATUS REAL-TIME KE WEB FRONTEND (SENSOR FUSION) +// FUNGSI 5: SENSOR FUSION FRONTEND // ========================================================================= func GetHardwareStatus(c *gin.Context) { haURL := os.Getenv("HA_URL") haToken := os.Getenv("HA_TOKEN") - // 1. Cek status fisik MCB di Home Assistant mcbUmum := fetchHAStringState(haURL, haToken, "switch.wifi_smart_meter_pro_switch") mcbAC1 := fetchHAStringState(haURL, haToken, "switch.wifi_smart_meter_pro_2_switch") - // 2. Ambil data dari Cache (Perintah terakhir dari Web/ESP32 melalui IR/MQTT) statusLampu1 := DeviceStatusCache["lampu1"] statusLampu2 := DeviceStatusCache["lampu2"] statusProyektor := DeviceStatusCache["projector"] statusAC := DeviceStatusCache["ac"] - // 3. LOGIKA SENSOR FUSION (Penggabungan Fisik & Virtual) - // Jika listrik MCB fisik terputus, maka otomatis perangkat di jalur tersebut PASTI MATI (off), - // mengabaikan apapun status terakhir yang ada di memori Cache. if mcbUmum == "off" { statusLampu1 = "off" statusLampu2 = "off" statusProyektor = "off" - // Sinkronkan kembali cache agar tidak ada "state" nyangkut DeviceStatusCache["lampu1"] = "off" DeviceStatusCache["lampu2"] = "off" DeviceStatusCache["projector"] = "off" @@ -439,7 +432,6 @@ func GetHardwareStatus(c *gin.Context) { DeviceStatusCache["ac"] = "off" } - // Kirim data yang sudah divalidasi ke Web dan Layar ESP32 c.JSON(http.StatusOK, gin.H{ "status": "success", "data": map[string]string{