Menyelesaikan fitur Login, Dashboard Mahasiswa, dan Admin Panel
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"os"
|
||||
"s-class-backend/config"
|
||||
"s-class-backend/models"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
// Input Register
|
||||
type RegisterInput struct {
|
||||
NrpNip string `json:"nrp_nip" binding:"required"`
|
||||
FullName string `json:"full_name" binding:"required"`
|
||||
Email string `json:"email" binding:"required,email"`
|
||||
Phone string `json:"phone" binding:"required"`
|
||||
Password string `json:"password" binding:"required,min=6"`
|
||||
}
|
||||
|
||||
// Input Login
|
||||
type LoginInput struct {
|
||||
NrpNip string `json:"nrp_nip" binding:"required"`
|
||||
Password string `json:"password" binding:"required"`
|
||||
}
|
||||
|
||||
// --- FUNGSI REGISTER ---
|
||||
func Register(c *gin.Context) {
|
||||
var input RegisterInput
|
||||
if err := c.ShouldBindJSON(&input); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
hashedPassword, _ := bcrypt.GenerateFromPassword([]byte(input.Password), bcrypt.DefaultCost)
|
||||
|
||||
user := models.User{
|
||||
NrpNip: input.NrpNip,
|
||||
FullName: input.FullName,
|
||||
Email: input.Email,
|
||||
Phone: input.Phone,
|
||||
PasswordHash: string(hashedPassword),
|
||||
Role: "student",
|
||||
}
|
||||
|
||||
if err := config.DB.Create(&user).Error; err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "NRP/NIP atau Email sudah terdaftar!"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Registrasi Berhasil!", "data": user})
|
||||
}
|
||||
|
||||
// --- FUNGSI LOGIN (BARU) ---
|
||||
func Login(c *gin.Context) {
|
||||
var input LoginInput
|
||||
|
||||
// 1. Cek Input
|
||||
if err := c.ShouldBindJSON(&input); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// 2. Cari User berdasarkan NRP
|
||||
var user models.User
|
||||
if err := config.DB.Where("nrp_nip = ?", input.NrpNip).First(&user).Error; err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "NRP atau Password salah!"})
|
||||
return
|
||||
}
|
||||
|
||||
// 3. Cek Password (Bandingkan Hash)
|
||||
err := bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(input.Password))
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "NRP atau Password salah!"})
|
||||
return
|
||||
}
|
||||
|
||||
// 4. Bikin Token JWT (Tiket Masuk)
|
||||
// Token berlaku selama 24 jam
|
||||
expirationTime := time.Now().Add(24 * time.Hour)
|
||||
claims := &jwt.MapClaims{
|
||||
"user_id": user.UserID,
|
||||
"role": user.Role,
|
||||
"exp": expirationTime.Unix(),
|
||||
}
|
||||
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
tokenString, err := token.SignedString([]byte(os.Getenv("JWT_SECRET")))
|
||||
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Gagal membuat token"})
|
||||
return
|
||||
}
|
||||
|
||||
// 5. Kirim Token ke Pengguna
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "Login Berhasil!",
|
||||
"token": tokenString,
|
||||
"user": gin.H{
|
||||
"full_name": user.FullName,
|
||||
"role": user.Role,
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"s-class-backend/config"
|
||||
"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
|
||||
}
|
||||
|
||||
// --- PERBAIKAN DI SINI (Handle UUID) ---
|
||||
// Ambil user_id dari context (hasil login)
|
||||
userIDInterface, exists := c.Get("user_id")
|
||||
if !exists {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "User ID tidak ditemukan"})
|
||||
return
|
||||
}
|
||||
|
||||
// Konversi interface{} ke string dulu, baru ke UUID
|
||||
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
|
||||
}
|
||||
// ---------------------------------------
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// CEK BENTROK (Overlap Check)
|
||||
var count int64
|
||||
config.DB.Model(&models.Booking{}).Where("room_id = ? AND status != 'Cancelled' 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 pada jam tersebut!"})
|
||||
return
|
||||
}
|
||||
|
||||
// Simpan Booking
|
||||
booking := models.Booking{
|
||||
UserID: userID,
|
||||
RoomID: input.RoomID,
|
||||
StartTime: input.StartTime,
|
||||
EndTime: input.EndTime,
|
||||
Purpose: input.Purpose,
|
||||
Status: "Pending",
|
||||
}
|
||||
|
||||
// Tampilkan error asli database jika gagal
|
||||
if err := config.DB.Create(&booking).Error; err != nil {
|
||||
fmt.Println("Error DB:", err) // Print error ke terminal
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "Booking Berhasil diajukan!",
|
||||
"data": booking,
|
||||
})
|
||||
}
|
||||
func GetUserBookings(c *gin.Context) {
|
||||
var bookings []models.Booking
|
||||
|
||||
// 1. Ambil User ID dan Role dari token yang sedang login
|
||||
userID, _ := c.Get("user_id")
|
||||
role, _ := c.Get("role")
|
||||
|
||||
// 2. Cek apakah dia Admin
|
||||
if role == "admin" {
|
||||
// Jika ADMIN: Ambil SEMUA data peminjaman dari semua user, urutkan dari yang terbaru
|
||||
// Kita juga ambil data Room dan User agar detailnya lengkap
|
||||
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 {
|
||||
// Jika MAHASISWA: Ambil HANYA data peminjaman 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"` // Isinya: 'Approved', 'Rejected', 'Completed', 'Cancelled'
|
||||
}
|
||||
|
||||
// --- FUNGSI 3: UPDATE STATUS (ADMIN) ---
|
||||
func UpdateBookingStatus(c *gin.Context) {
|
||||
// 1. Ambil ID Booking dari URL (misal: /bookings/123)
|
||||
bookingID := c.Param("id")
|
||||
|
||||
// 2. Cek apakah booking ada?
|
||||
var booking models.Booking
|
||||
if err := config.DB.First(&booking, "booking_id = ?", bookingID).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Booking tidak ditemukan"})
|
||||
return
|
||||
}
|
||||
|
||||
// 3. Validasi Input JSON
|
||||
var input UpdateStatusInput
|
||||
if err := c.ShouldBindJSON(&input); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// 4. Update Status ke Database
|
||||
booking.Status = input.Status
|
||||
config.DB.Save(&booking)
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "Status booking berhasil diperbarui!",
|
||||
"data": booking,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"s-class-backend/config"
|
||||
"s-class-backend/models"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// --- GET ALL ROOMS ---
|
||||
func GetRooms(c *gin.Context) {
|
||||
var rooms []models.Room
|
||||
if err := config.DB.Find(&rooms).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Gagal mengambil data ruangan"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"data": rooms})
|
||||
}
|
||||
|
||||
// --- CREATE ROOM ---
|
||||
func CreateRoom(c *gin.Context) {
|
||||
var input models.Room
|
||||
if err := c.ShouldBindJSON(&input); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
if err := config.DB.Create(&input).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Gagal membuat ruangan"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Ruangan berhasil ditambahkan!", "data": input})
|
||||
}
|
||||
Reference in New Issue
Block a user