[Valentino Heman Budiarto] 0f56b0bb6d .
2026-06-02 20:32:53 +07:00

259 lines
7.4 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
}
// 🌟 PERBAIKAN 1: 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
}
// 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
}
// 🌟 PERBAIKAN 1 Lanjutan: 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, // <-- Gunakan variabel 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,
})
}
// 🌟 PERBAIKAN 3: 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
// 🌟 PERBAIKAN 2: Menghapus blok Save dan JSON ganda yang bikin error
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,
})
}