272 lines
7.8 KiB
Go
272 lines
7.8 KiB
Go
package controllers
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"s-class-backend/config"
|
|
"s-class-backend/helpers"
|
|
"s-class-backend/models"
|
|
"time"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
type BookingInput struct {
|
|
RoomID uint `json:"room_id" binding:"required"`
|
|
StartTime time.Time `json:"start_time" binding:"required"`
|
|
EndTime time.Time `json:"end_time" binding:"required"`
|
|
Purpose string `json:"purpose" binding:"required"`
|
|
}
|
|
|
|
func CreateBooking(c *gin.Context) {
|
|
var input BookingInput
|
|
if err := c.ShouldBindJSON(&input); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
// Handle UUID User
|
|
userIDInterface, exists := c.Get("user_id")
|
|
if !exists {
|
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "User ID tidak ditemukan"})
|
|
return
|
|
}
|
|
|
|
userIDStr, ok := userIDInterface.(string)
|
|
if !ok {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Format User ID token salah"})
|
|
return
|
|
}
|
|
|
|
userID, err := uuid.Parse(userIDStr)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Gagal memproses User ID"})
|
|
return
|
|
}
|
|
|
|
// 🌟 Ambil Role User dari Token
|
|
userRoleInterface, _ := c.Get("role")
|
|
userRole, _ := userRoleInterface.(string)
|
|
|
|
// Validasi Waktu
|
|
if input.EndTime.Before(input.StartTime) {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Waktu selesai tidak boleh lebih awal dari waktu mulai!"})
|
|
return
|
|
}
|
|
|
|
// =========================================================
|
|
// 🌟 PERBAIKAN: PROTEKSI STATUS MAINTENANCE RUANGAN
|
|
// =========================================================
|
|
var targetRoom models.Room
|
|
if err := config.DB.First(&targetRoom, input.RoomID).Error; err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Ruangan tidak ditemukan di database"})
|
|
return
|
|
}
|
|
|
|
if targetRoom.Status == "Maintenance" {
|
|
c.JSON(http.StatusForbidden, gin.H{"error": "Akses Ditolak: Ruangan ini sedang dalam perawatan teknis dan tidak dapat dipesan saat ini."})
|
|
return
|
|
}
|
|
// =========================================================
|
|
|
|
// Overlap Check (Mencegah bentrok jadwal)
|
|
var count int64
|
|
config.DB.Model(&models.Booking{}).Where(
|
|
"room_id = ? AND status IN ('Pending', 'Approved') AND ((start_time < ? AND end_time > ?) OR (start_time < ? AND end_time > ?) OR (start_time >= ? AND end_time <= ?))",
|
|
input.RoomID, input.EndTime, input.StartTime, input.EndTime, input.StartTime, input.StartTime, input.EndTime,
|
|
).Count(&count)
|
|
|
|
if count > 0 {
|
|
c.JSON(http.StatusConflict, gin.H{"error": "Ruangan sudah dibooking atau sedang dalam antrean persetujuan pada jam tersebut!"})
|
|
return
|
|
}
|
|
|
|
// Penentuan Status Otomatis
|
|
statusPeminjaman := "Pending" // Default untuk student
|
|
if userRole == "lecturer" {
|
|
statusPeminjaman = "Approved" // Dosen otomatis disetujui!
|
|
}
|
|
|
|
// Simpan Booking
|
|
booking := models.Booking{
|
|
UserID: userID,
|
|
RoomID: input.RoomID,
|
|
StartTime: input.StartTime,
|
|
EndTime: input.EndTime,
|
|
Purpose: input.Purpose,
|
|
Status: statusPeminjaman,
|
|
}
|
|
|
|
if err := config.DB.Create(&booking).Error; err != nil {
|
|
fmt.Println("Error DB:", err)
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"message": "Booking Berhasil diajukan!",
|
|
"data": booking,
|
|
})
|
|
}
|
|
|
|
// Fungsi khusus untuk mengambil riwayat pribadi
|
|
func GetMyBookings(c *gin.Context) {
|
|
var bookings []models.Booking
|
|
|
|
userID, _ := c.Get("user_id")
|
|
role, _ := c.Get("role")
|
|
|
|
if role == "admin" {
|
|
// Admin bisa melihat semua riwayat
|
|
if err := config.DB.Preload("Room").Preload("User").Order("created_at desc").Find(&bookings).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
} else {
|
|
// Mahasiswa/Dosen HANYA BISA melihat riwayat miliknya sendiri
|
|
if err := config.DB.Preload("Room").Where("user_id = ?", userID).Order("created_at desc").Find(&bookings).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"data": bookings})
|
|
}
|
|
|
|
type UpdateStatusInput struct {
|
|
Status string `json:"status" binding:"required"`
|
|
}
|
|
|
|
// UPDATE STATUS (ADMIN)
|
|
func UpdateBookingStatus(c *gin.Context) {
|
|
bookingID := c.Param("id")
|
|
|
|
var input UpdateStatusInput
|
|
if err := c.ShouldBindJSON(&input); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Format data salah"})
|
|
return
|
|
}
|
|
|
|
var booking models.Booking
|
|
if err := config.DB.Where("booking_id = ?", bookingID).First(&booking).Error; err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Data booking tidak ditemukan di database"})
|
|
return
|
|
}
|
|
|
|
switch input.Status {
|
|
case "Approved":
|
|
if booking.RedeemCode == "" {
|
|
booking.RedeemCode = helpers.GenerateRedeemCode()
|
|
}
|
|
case "Rejected", "Cancelled":
|
|
booking.RedeemCode = ""
|
|
}
|
|
|
|
booking.Status = input.Status
|
|
|
|
if err := config.DB.Save(&booking).Error; err != nil {
|
|
fmt.Println("🔥 ERROR DARI DATABASE:", err.Error())
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"message": "Status booking berhasil diperbarui!",
|
|
"data": booking,
|
|
})
|
|
}
|
|
|
|
// INPUT UNTUK ESP32
|
|
type TokenInput struct {
|
|
Token string `json:"token" binding:"required"` // Berubah menjadi 'token' menyesuaikan payload ESP32
|
|
}
|
|
|
|
// VERIFIKASI KODE DARI ESP32 (Booking & Jadwal Kuliah)
|
|
func VerifyRedeemCode(c *gin.Context) {
|
|
var input TokenInput
|
|
|
|
if err := c.ShouldBindJSON(&input); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Format data salah"})
|
|
return
|
|
}
|
|
|
|
loc, _ := time.LoadLocation("Asia/Jakarta")
|
|
currentTime := time.Now().In(loc)
|
|
var endTime time.Time
|
|
var roomName string
|
|
|
|
// 1. CARI DI TABEL BOOKING (REDEEM CODE)
|
|
var booking models.Booking
|
|
errBooking := config.DB.Preload("Room").First(&booking, "redeem_code = ? AND status = 'Approved'", input.Token).Error
|
|
|
|
if errBooking == nil {
|
|
// LOGIKA BOOKING (Sudah menggunakan time.Time)
|
|
if currentTime.Before(booking.StartTime) {
|
|
c.JSON(http.StatusForbidden, gin.H{"error": "Belum waktunya peminjaman!"})
|
|
return
|
|
}
|
|
if currentTime.After(booking.EndTime) {
|
|
c.JSON(http.StatusForbidden, gin.H{"error": "Waktu peminjaman sudah habis!"})
|
|
return
|
|
}
|
|
endTime = booking.EndTime
|
|
roomName = booking.Room.Name
|
|
|
|
} else {
|
|
// 2. JIKA TIDAK KETEMU, CARI DI TABEL JADWAL KELAS (KODE MK)
|
|
var schedule models.ClassSchedule
|
|
errSchedule := config.DB.Preload("Room").First(&schedule, "kode_mk = ?", input.Token).Error
|
|
|
|
if errSchedule != nil {
|
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "Token atau Kode MK tidak valid!"})
|
|
return
|
|
}
|
|
|
|
// LOGIKA JADWAL KULIAH (Konversi String Jam ke time.Time hari ini)
|
|
todayStr := currentTime.Format("2006-01-02")
|
|
// Gabungkan tanggal hari ini dengan jam dari database (JamMulai & JamSelesai)
|
|
startTime, _ := time.Parse("2006-01-02 15:04:05", todayStr+" "+schedule.JamMulai)
|
|
endTimeParsed, _ := time.Parse("2006-01-02 15:04:05", todayStr+" "+schedule.JamSelesai)
|
|
|
|
if currentTime.Before(startTime) {
|
|
c.JSON(http.StatusForbidden, gin.H{"error": "Belum waktunya jadwal kuliah!"})
|
|
return
|
|
}
|
|
if currentTime.After(endTimeParsed) {
|
|
c.JSON(http.StatusForbidden, gin.H{"error": "Waktu perkuliahan sudah habis!"})
|
|
return
|
|
}
|
|
|
|
endTime = endTimeParsed
|
|
roomName = schedule.Room.Name
|
|
}
|
|
|
|
// 3. HITUNG SISA WAKTU
|
|
sisaWaktuMenit := int(endTime.Sub(currentTime).Minutes())
|
|
if sisaWaktuMenit < 1 {
|
|
sisaWaktuMenit = 1
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"status": "success",
|
|
"message": "Token valid!",
|
|
"room": roomName,
|
|
"duration_minutes": sisaWaktuMenit,
|
|
})
|
|
}
|
|
|
|
// GET ALL BOOKINGS (Untuk Calendar View semua user)
|
|
func GetAllBookings(c *gin.Context) {
|
|
var bookings []models.Booking
|
|
|
|
if err := config.DB.Preload("Room").Preload("User").Order("created_at desc").Find(&bookings).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"status": "success",
|
|
"data": bookings,
|
|
})
|
|
} |