Skip to content

Commit

Permalink
Merge pull request #48 from COS301-SE-2024/feature/CheckIn-refactor
Browse files Browse the repository at this point in the history
Feature/check in refactor
  • Loading branch information
Rethakgetse-Manaka authored Jun 7, 2024
2 parents 7def1a1 + c3ac8ef commit 6a27e15
Show file tree
Hide file tree
Showing 7 changed files with 135 additions and 61 deletions.
5 changes: 2 additions & 3 deletions occupi-backend/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ go 1.21
toolchain go1.21.6

require (
github.com/alexedwards/argon2id v1.0.0
github.com/coreos/go-oidc/v3 v3.10.0
github.com/gin-contrib/sessions v1.0.1
github.com/gin-gonic/gin v1.10.0
github.com/joho/godotenv v1.5.1
github.com/microcosm-cc/bluemonday v1.0.26
github.com/sirupsen/logrus v1.9.3
github.com/stretchr/testify v1.9.0
go.mongodb.org/mongo-driver v1.15.0
Expand All @@ -17,7 +19,6 @@ require (
)

require (
github.com/alexedwards/argon2id v1.0.0 // indirect
github.com/aymerick/douceur v0.2.0 // indirect
github.com/bytedance/sonic v1.11.7 // indirect
github.com/bytedance/sonic/loader v0.1.1 // indirect
Expand All @@ -42,15 +43,13 @@ require (
github.com/kr/pretty v0.3.0 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/microcosm-cc/bluemonday v1.0.26 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/montanaflynn/stats v0.7.1 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rogpeppe/go-internal v1.9.0 // indirect
github.com/stretchr/objx v0.5.2 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect
github.com/ulule/limiter/v3 v3.11.2 // indirect
Expand Down
1 change: 0 additions & 1 deletion occupi-backend/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,6 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
Expand Down
91 changes: 91 additions & 0 deletions occupi-backend/pkg/database/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,52 @@ func SaveBooking(ctx *gin.Context, db *mongo.Client, booking models.Booking) (bo
}
return true, nil
}
func ConfirmCheckIn(ctx *gin.Context, db *mongo.Client, checkIn models.CheckIn) (bool, error) {
// Save the check-in to the database
collection := db.Database("Occupi").Collection("RoomBooking")

//Find the booking by bookingId, roomId, and check if the email is in the emails object
filter := bson.M{
"bookingId": checkIn.BookingID,
}

// Find the booking
var booking models.Booking
fmt.Println(filter)
err := collection.FindOne(context.TODO(), filter).Decode(&booking)
if err != nil {
fmt.Println(err)
if err == mongo.ErrNoDocuments {
logrus.Error("Booking not found")
return false, fmt.Errorf("booking not found")
}
logrus.Error("Failed to find booking:", err)
return false, err
}

// Check if the email exists in any of the emails map values
for _, email := range booking.Emails {
if email == checkIn.Email {
break
} else {
logrus.Error("Email not associated with the room")
return false, fmt.Errorf("email not associated with the room")
}
}

update := bson.M{
"$set": bson.M{"checkedIn": true},
}

opts := options.FindOneAndUpdate().SetReturnDocument(options.After)
var updatedBooking models.Booking
err = collection.FindOneAndUpdate(context.TODO(), filter, update, opts).Decode(&updatedBooking)
if err != nil {
logrus.Error("Failed to update booking:", err)
return false, err
}
return true, nil
}

// checks if email exists in database
func EmailExists(ctx *gin.Context, db *mongo.Client, email string) bool {
Expand All @@ -111,6 +157,20 @@ func EmailExists(ctx *gin.Context, db *mongo.Client, email string) bool {
return true
}

// checks if booking exists in database
func BookingExists(ctx *gin.Context, db *mongo.Client, bookingID int) bool {
// Check if the booking exists in the database
collection := db.Database("Occupi").Collection("RoomBooking")
filter := bson.M{"bookingId": bookingID}
var existingbooking models.Booking
err := collection.FindOne(ctx, filter).Decode(&existingbooking)
if err != nil {
logrus.Error(err)
return false
}
return true
}

// adds user to database
func AddUser(ctx *gin.Context, db *mongo.Client, user models.RequestUser) (bool, error) {
// convert to user struct
Expand Down Expand Up @@ -254,3 +314,34 @@ func UpdateVerificationStatusTo(ctx *gin.Context, db *mongo.Client, email string
}
return true, nil
}

// Confirms if a booking has been cancelled
func ConfirmCancellation(ctx *gin.Context, db *mongo.Client, bookingID int) (bool, error) {
// Save the check-in to the database
collection := db.Database("Occupi").Collection("RoomBooking")

//Find the booking by bookingId, roomId, and check if the email is in the emails object
filter := bson.M{
"bookingId": bookingID}

// Find the booking
var localBooking models.Booking
err := collection.FindOne(context.TODO(), filter).Decode(&localBooking)
if err != nil {
fmt.Println(err)
if err == mongo.ErrNoDocuments {
logrus.Error("Email not associated with the room")
return false, fmt.Errorf("email not associated with the room")
}
logrus.Error("Failed to find booking:", err)
return false, err
}

// Delete the booking
_, err = collection.DeleteOne(context.TODO(), filter)
if err != nil {
logrus.Error("Failed to cancel booking:", err)
return false, err
}
return true, nil
}
89 changes: 33 additions & 56 deletions occupi-backend/pkg/handlers/api_handlers.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,12 @@
package handlers

import (
"context"
"fmt"
"net/http"
"strconv"

"github.com/COS301-SE-2024/occupi/occupi-backend/pkg/constants"
"github.com/COS301-SE-2024/occupi/occupi-backend/pkg/database"
"github.com/COS301-SE-2024/occupi/occupi-backend/pkg/models"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"

"github.com/COS301-SE-2024/occupi/occupi-backend/pkg/mail"
"github.com/COS301-SE-2024/occupi/occupi-backend/pkg/utils"
Expand Down Expand Up @@ -77,70 +72,52 @@ func BookRoom(ctx *gin.Context, appsession *models.AppSession) {
ctx.JSON(http.StatusOK, gin.H{"message": "Booking successful! Confirmation emails sent."})
}

// CheckIn handles the check-in process for a booking
func CheckIn(ctx *gin.Context, appsession *models.AppSession) {
// consider structuring api responses to match that as outlined in our coding standards documentation
//link: https://cos301-se-2024.github.io/occupi/coding-standards/go-coding-standards#response-and-error-handling

var request models.CheckIn

if err := ctx.ShouldBindJSON(&request); err != nil {
ctx.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request payload"})
// Cancel booking handles the cancellation of a booking
func CancelBooking(ctx *gin.Context, appsession *models.AppSession) {
var booking models.Booking
if err := ctx.ShouldBindJSON(&booking); err != nil {
ctx.JSON(http.StatusBadRequest, utils.ErrorResponse(http.StatusBadRequest, "Invalid request payload", constants.InvalidRequestPayloadCode, "Expected Booking ID, Room ID, and Email Address", nil))
return
}

collection := appsession.DB.Database("occupi").Collection("bookings")

// Build the dynamic filter for email check
emailFilter := bson.A{}
for key := range request.Email {
emailFilter = append(emailFilter, bson.M{"emails." + strconv.Itoa(key): request.Email})
// Check if the booking exists
exists := database.BookingExists(ctx, appsession.DB, booking.BookingID)
if !exists {
ctx.JSON(http.StatusBadRequest, utils.ErrorResponse(404, "Booking not found", constants.InternalServerErrorCode, "Booking not found", nil))
return
}

// Print the emailFilter for debugging
fmt.Printf("Email Filter: %+v\n", emailFilter) // it would be better if you used a logger here, logrus is already setup, dont print

// Find the booking by bookingId, roomId, and check if the email is in the emails object
filter := bson.M{
"bookingId": request.BookingID,
"roomId": request.RoomID,
"$or": emailFilter,
// Confirm the cancellation to the database
_, err := database.ConfirmCancellation(ctx, appsession.DB, booking.BookingID)
if err != nil {
ctx.JSON(http.StatusInternalServerError, utils.ErrorResponse(http.StatusInternalServerError, "Failed to cancel booking", constants.InternalServerErrorCode, "Failed to cancel booking", nil))
return
}

// Print the filter for debugging
fmt.Printf("Filter: %+v\n", filter) // it would be better if you used a logger here, logrus is already setup, dont print
ctx.JSON(http.StatusOK, utils.SuccessResponse(http.StatusOK, "Successfully cancelled booking!", nil))
}

// Find the booking
var booking models.Booking
err := collection.FindOne(context.TODO(), filter).Decode(&booking)
if err != nil {
if err == mongo.ErrNoDocuments {
ctx.JSON(http.StatusNotFound, gin.H{"error": "Booking not found or email not associated with the room"})
} else {
ctx.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to find booking"})
}
// CheckIn handles the check-in process for a booking
func CheckIn(ctx *gin.Context, appsession *models.AppSession) {
var checkIn models.CheckIn

if err := ctx.ShouldBindJSON(&checkIn); err != nil {
ctx.JSON(http.StatusBadRequest, utils.ErrorResponse(http.StatusBadRequest, "Invalid request payload", constants.InvalidRequestPayloadCode, "Expected Booking ID, Room ID, and Email Address", nil))
return
}
/**This section of code needs to be reviewed, can't make it to prod**/
// Print the emails for debugging
for key, email := range booking.Emails {
fmt.Printf("Email %s: %s\n", key, email) // it would be better if you used a logger here, logrus is already setup, dont print
}
/**This section of code needs to be reviewed**/

// Update the CheckedIn status
update := bson.M{
"$set": bson.M{"checkedIn": true},
// Check if the booking exists
exists := database.BookingExists(ctx, appsession.DB, checkIn.BookingID)
if !exists {
ctx.JSON(http.StatusInternalServerError, utils.ErrorResponse(http.StatusInternalServerError, "Failed to find booking", constants.InternalServerErrorCode, "Failed to find booking", nil))
return
}

opts := options.FindOneAndUpdate().SetReturnDocument(options.After)
var updatedBooking models.Booking

err = collection.FindOneAndUpdate(context.TODO(), filter, update, opts).Decode(&updatedBooking)
//Confirm the check-in to the database
_, err := database.ConfirmCheckIn(ctx, appsession.DB, checkIn)
if err != nil {
ctx.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update booking"})
ctx.JSON(http.StatusInternalServerError, utils.ErrorResponse(http.StatusInternalServerError, "Failed to check in", constants.InternalServerErrorCode, "Failed to check in. Email not associated with booking", nil))
return
}

ctx.JSON(http.StatusOK, gin.H{"message": "Check-in successful", "booking": updatedBooking})
ctx.JSON(http.StatusOK, utils.SuccessResponse(http.StatusOK, "Successfully checked in!", nil))
}
6 changes: 6 additions & 0 deletions occupi-backend/pkg/middleware/middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ func ProtectedRoute(ctx *gin.Context) {
"message": "Bad Request",
"error": "User not authenticated",
})
//Add the following so that the next() doesn't get called
c.Abort()
return
} else {
ctx.Next()
}
Expand All @@ -35,6 +38,9 @@ func UnProtectedRoute(ctx *gin.Context) {
"message": "Bad Request",
"error": "User already authenticated",
})
//Add the following so that the next() doesn't get called
c.Abort()
return
} else {
ctx.Next()
}
Expand Down
3 changes: 2 additions & 1 deletion occupi-backend/pkg/router/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ func OccupiRouter(router *gin.Engine, db *mongo.Client) {
{
api.GET("/resource-auth", middleware.ProtectedRoute, func(ctx *gin.Context) { handlers.FetchResourceAuth(ctx, appsession) }) // authenticated
api.GET("/book-room", middleware.ProtectedRoute, func(ctx *gin.Context) { handlers.BookRoom(ctx, appsession) })
api.GET("check-in", middleware.ProtectedRoute, func(ctx *gin.Context) { handlers.CheckIn(ctx, appsession) })
api.GET("/check-in", middleware.ProtectedRoute, func(ctx *gin.Context) { handlers.CheckIn(ctx, appsession) })
api.GET("cancel-booking", middleware.ProtectedRoute, func(ctx *gin.Context) { handlers.CancelBooking(ctx, appsession) })
}
auth := router.Group("/auth")
{
Expand Down
1 change: 1 addition & 0 deletions occupi-backend/tests/handlers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ func TestVerifyOTP_InvalidOTP(t *testing.T) {
assert.Equal(t, http.StatusBadRequest, rr.Code)
}
// TestVerifyOTP_EmailNotRegistered tests OTP verification for an unregistered email.
func TestVerifyOTP_EmailNotRegistered(t *testing.T) {
// Create a new HTTP request with an unregistered email.
Expand Down

0 comments on commit 6a27e15

Please sign in to comment.