From 82de0669a3732067732892f989d54718810bd84e Mon Sep 17 00:00:00 2001 From: Rethakgetse-Manaka Date: Wed, 3 Jul 2024 00:55:56 +0200 Subject: [PATCH 1/6] "Retrieveing user details works" --- occupi-backend/pkg/database/database.go | 14 +++++++++ occupi-backend/pkg/handlers/api_handlers.go | 17 +++++++++++ occupi-backend/pkg/models/database.go | 33 +++++++++++++++++++++ occupi-backend/pkg/router/router.go | 1 + 4 files changed, 65 insertions(+) diff --git a/occupi-backend/pkg/database/database.go b/occupi-backend/pkg/database/database.go index 5709c969..b68e445b 100644 --- a/occupi-backend/pkg/database/database.go +++ b/occupi-backend/pkg/database/database.go @@ -373,6 +373,20 @@ func GetAllRooms(ctx *gin.Context, db *mongo.Client, floorNo string) ([]models.R return rooms, nil } +// Get user information +func GetUserDetails(ctx *gin.Context, db *mongo.Client, email string) (models.UserDetails, error) { + collection := db.Database("Occupi").Collection("Users") + + filter := bson.M{"email": email} + var user models.UserDetails + err := collection.FindOne(ctx, filter).Decode(&user) + if err != nil { + logrus.Error(err) + return models.UserDetails{}, err + } + return user, nil +} + // Checks if a user is an admin func CheckIfUserIsAdmin(ctx *gin.Context, db *mongo.Client, email string) (bool, error) { // Check if the user is an admin diff --git a/occupi-backend/pkg/handlers/api_handlers.go b/occupi-backend/pkg/handlers/api_handlers.go index f0fddca1..9b32e504 100644 --- a/occupi-backend/pkg/handlers/api_handlers.go +++ b/occupi-backend/pkg/handlers/api_handlers.go @@ -198,6 +198,23 @@ func CheckIn(ctx *gin.Context, appsession *models.AppSession) { ctx.JSON(http.StatusOK, utils.SuccessResponse(http.StatusOK, "Successfully checked in!", nil)) } +func GetUserDetails(ctx *gin.Context, appsession *models.AppSession) { + // Extract the email query parameter + email := ctx.Query("email") + if email == "" || !utils.ValidateEmail(email) { + ctx.JSON(http.StatusBadRequest, utils.ErrorResponse(http.StatusBadRequest, "Invalid request payload", constants.InvalidRequestPayloadCode, "Expected Email Address", nil)) + return + } + + // Get all bookings for the userBooking + user, err := database.GetUserDetails(ctx, appsession.DB, email) + if err != nil { + ctx.JSON(http.StatusInternalServerError, utils.ErrorResponse(http.StatusNotFound, "Failed to get user details", constants.InternalServerErrorCode, "Failed to get user details", nil)) + return + } + + ctx.JSON(http.StatusOK, utils.SuccessResponse(http.StatusOK, "Successfully fetched user details!", user)) +} func ViewRooms(ctx *gin.Context, appsession *models.AppSession) { var roomRequest map[string]interface{} var room models.RoomRequest diff --git a/occupi-backend/pkg/models/database.go b/occupi-backend/pkg/models/database.go index 1ddeb0b5..9eb93912 100644 --- a/occupi-backend/pkg/models/database.go +++ b/occupi-backend/pkg/models/database.go @@ -14,6 +14,39 @@ type User struct { NextVerificationDate time.Time `json:"nextVerificationDate" bson:"nextVerificationDate"` } +type UserDetails struct { + ID string `json:"_id" bson:"_id,omitempty"` + OccupiID string `json:"occupiId" bson:"occupiId"` + Password string `json:"password" bson:"password"` + Email string `json:"email" bson:"email"` + Role string `json:"role" bson:"role"` + OnSite bool `json:"onSite" bson:"onSite"` + IsVerified bool `json:"isVerified" bson:"isVerified"` + NextVerificationDate time.Time `json:"nextVerificationDate" bson:"nextVerificationDate"` + Details *Details `json:"details" bson:"details"` + Notifications *Notifications `json:"notifications" bson:"notifications"` + Security *Security `json:"security" bson:"security"` +} + +type Details struct { + ContactNo string `json:"contactNo" bson:"contactNo"` + Name string `json:"name" bson:"name"` + DOB time.Time `json:"dob" bson:"dob"` + Company string `json:"company" bson:"company"` + Pronouns string `json:"pronouns" bson:"pronouns"` +} + +type Notifications struct { + Allow *bool `json:"allow" bson:"allow"` + BookingReminder *bool `json:"bookingReminder" bson:"bookingReminder"` + Max_Capacity *bool `json:"maxCapacity" bson:"maxCapacity"` +} + +type Security struct { + MFA *bool `json:"mfa" bson:"mfa"` + Biometrics *bool `json:"biometrics" bson:"biometrics"` +} + // structure of booking type Booking struct { ID string `json:"_id" bson:"_id,omitempty"` diff --git a/occupi-backend/pkg/router/router.go b/occupi-backend/pkg/router/router.go index ca3a4fde..1f5ffd51 100644 --- a/occupi-backend/pkg/router/router.go +++ b/occupi-backend/pkg/router/router.go @@ -48,6 +48,7 @@ func OccupiRouter(router *gin.Engine, db *mongo.Client) { api.POST("/cancel-booking", middleware.ProtectedRoute, func(ctx *gin.Context) { handlers.CancelBooking(ctx, appsession) }) api.GET(("/view-bookings"), middleware.ProtectedRoute, func(ctx *gin.Context) { handlers.ViewBookings(ctx, appsession) }) api.GET("/view-rooms", middleware.ProtectedRoute, func(ctx *gin.Context) { handlers.ViewRooms(ctx, appsession) }) + api.GET("/user-details", middleware.ProtectedRoute, func(ctx *gin.Context) { handlers.GetUserDetails(ctx, appsession) }) } auth := router.Group("/auth") { From a80bc932a77e3a46d54924735a2f774c1b858af7 Mon Sep 17 00:00:00 2001 From: Rethakgetse-Manaka Date: Wed, 3 Jul 2024 01:05:46 +0200 Subject: [PATCH 2/6] "Error handling included" --- occupi-backend/pkg/handlers/api_handlers.go | 5 ++++ occupi-backend/pkg/router/router.go | 2 +- occupi-backend/tests/handlers_test.go | 29 +++++++++++++++++++++ 3 files changed, 35 insertions(+), 1 deletion(-) diff --git a/occupi-backend/pkg/handlers/api_handlers.go b/occupi-backend/pkg/handlers/api_handlers.go index 9b32e504..5a08f9ae 100644 --- a/occupi-backend/pkg/handlers/api_handlers.go +++ b/occupi-backend/pkg/handlers/api_handlers.go @@ -206,6 +206,11 @@ func GetUserDetails(ctx *gin.Context, appsession *models.AppSession) { return } + if !database.EmailExists(ctx, appsession.DB, email) { + ctx.JSON(http.StatusNotFound, utils.ErrorResponse(http.StatusNotFound, "User not found", constants.InternalServerErrorCode, "User not found", nil)) + return + } + // Get all bookings for the userBooking user, err := database.GetUserDetails(ctx, appsession.DB, email) if err != nil { diff --git a/occupi-backend/pkg/router/router.go b/occupi-backend/pkg/router/router.go index 1f5ffd51..409aff62 100644 --- a/occupi-backend/pkg/router/router.go +++ b/occupi-backend/pkg/router/router.go @@ -48,7 +48,7 @@ func OccupiRouter(router *gin.Engine, db *mongo.Client) { api.POST("/cancel-booking", middleware.ProtectedRoute, func(ctx *gin.Context) { handlers.CancelBooking(ctx, appsession) }) api.GET(("/view-bookings"), middleware.ProtectedRoute, func(ctx *gin.Context) { handlers.ViewBookings(ctx, appsession) }) api.GET("/view-rooms", middleware.ProtectedRoute, func(ctx *gin.Context) { handlers.ViewRooms(ctx, appsession) }) - api.GET("/user-details", middleware.ProtectedRoute, func(ctx *gin.Context) { handlers.GetUserDetails(ctx, appsession) }) + api.GET("/user-details", middleware.UnProtectedRoute, func(ctx *gin.Context) { handlers.GetUserDetails(ctx, appsession) }) } auth := router.Group("/auth") { diff --git a/occupi-backend/tests/handlers_test.go b/occupi-backend/tests/handlers_test.go index 61118b50..4085f45b 100644 --- a/occupi-backend/tests/handlers_test.go +++ b/occupi-backend/tests/handlers_test.go @@ -2,6 +2,7 @@ package tests import ( "bytes" + "context" "encoding/json" "fmt" "net/http" @@ -12,6 +13,7 @@ import ( "time" "github.com/stretchr/testify/assert" + "go.mongodb.org/mongo-driver/mongo" "github.com/gin-gonic/gin" @@ -19,6 +21,7 @@ import ( "github.com/COS301-SE-2024/occupi/occupi-backend/pkg/authenticator" "github.com/COS301-SE-2024/occupi/occupi-backend/pkg/constants" "github.com/COS301-SE-2024/occupi/occupi-backend/pkg/middleware" + "github.com/COS301-SE-2024/occupi/occupi-backend/pkg/models" "github.com/COS301-SE-2024/occupi/occupi-backend/pkg/router" // "github.com/stretchr/testify/mock" ) @@ -158,6 +161,30 @@ func createMockBooking(r *gin.Engine, payload string, cookies []*http.Cookie) (m return response, nil } +// Insert mock data into the test database +func InsertMockData(db *mongo.Client) { + collection := db.Database("Occupi").Collection("users") + user := models.UserDetails{ + ID: "1234567890", + Email: "john.doe@example.com", + Password: "hashedpassword", + Role: "admin", + OnSite: true, + Details: *models.Details{ + ContactNo: "123-456-7890", + Dob: time.Date(1990, 1, 1, 0, 0, 0, 0, time.UTC), + Gender: "male", + Name: "John Doe", + Pronouns: "He/him", + }, + Position: "Manager", + Status: "Active", + IsVerified: true, + NextVerificationDate: time.Date(2024, 9, 15, 0, 0, 0, 0, time.UTC), + } + collection.InsertOne(context.Background(), user) +} + // SetupTestEnvironment initializes the test environment and returns the router and cookies func setupTestEnvironment(t *testing.T) (*gin.Engine, []*http.Cookie) { // Connect to the test database @@ -279,6 +306,8 @@ func getSharedTestCases(r *gin.Engine, cookies []*http.Cookie) []testCase { } } +// Tests the ViewUserDetails handler + // Tests the CancelBooking handler func TestCancelBooking(t *testing.T) { // Setup the test environment From 9cd2c5367e93fed2fce2de9f0119ab0f47ffac36 Mon Sep 17 00:00:00 2001 From: Rethakgetse-Manaka Date: Wed, 3 Jul 2024 02:12:23 +0200 Subject: [PATCH 3/6] "Testing get requests proves difficult" --- occupi-backend/pkg/models/database.go | 4 +- occupi-backend/tests/handlers_test.go | 116 ++++++++++++++++++++------ 2 files changed, 94 insertions(+), 26 deletions(-) diff --git a/occupi-backend/pkg/models/database.go b/occupi-backend/pkg/models/database.go index 9eb93912..9ddeab99 100644 --- a/occupi-backend/pkg/models/database.go +++ b/occupi-backend/pkg/models/database.go @@ -26,13 +26,15 @@ type UserDetails struct { Details *Details `json:"details" bson:"details"` Notifications *Notifications `json:"notifications" bson:"notifications"` Security *Security `json:"security" bson:"security"` + Status string `json:"status" bson:"status"` + Position string `json:"position" bson:"position"` } type Details struct { ContactNo string `json:"contactNo" bson:"contactNo"` Name string `json:"name" bson:"name"` DOB time.Time `json:"dob" bson:"dob"` - Company string `json:"company" bson:"company"` + Gender string `json:"gender" bson:"gender"` Pronouns string `json:"pronouns" bson:"pronouns"` } diff --git a/occupi-backend/tests/handlers_test.go b/occupi-backend/tests/handlers_test.go index 4085f45b..e4fa8ae0 100644 --- a/occupi-backend/tests/handlers_test.go +++ b/occupi-backend/tests/handlers_test.go @@ -13,6 +13,7 @@ import ( "time" "github.com/stretchr/testify/assert" + "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo" "github.com/gin-gonic/gin" @@ -21,7 +22,6 @@ import ( "github.com/COS301-SE-2024/occupi/occupi-backend/pkg/authenticator" "github.com/COS301-SE-2024/occupi/occupi-backend/pkg/constants" "github.com/COS301-SE-2024/occupi/occupi-backend/pkg/middleware" - "github.com/COS301-SE-2024/occupi/occupi-backend/pkg/models" "github.com/COS301-SE-2024/occupi/occupi-backend/pkg/router" // "github.com/stretchr/testify/mock" ) @@ -160,29 +160,8 @@ func createMockBooking(r *gin.Engine, payload string, cookies []*http.Cookie) (m return response, nil } - -// Insert mock data into the test database -func InsertMockData(db *mongo.Client) { - collection := db.Database("Occupi").Collection("users") - user := models.UserDetails{ - ID: "1234567890", - Email: "john.doe@example.com", - Password: "hashedpassword", - Role: "admin", - OnSite: true, - Details: *models.Details{ - ContactNo: "123-456-7890", - Dob: time.Date(1990, 1, 1, 0, 0, 0, 0, time.UTC), - Gender: "male", - Name: "John Doe", - Pronouns: "He/him", - }, - Position: "Manager", - Status: "Active", - IsVerified: true, - NextVerificationDate: time.Date(2024, 9, 15, 0, 0, 0, 0, time.UTC), - } - collection.InsertOne(context.Background(), user) +func BoolPtr(b bool) *bool { + return &b } // SetupTestEnvironment initializes the test environment and returns the router and cookies @@ -222,13 +201,29 @@ func setupTestEnvironment(t *testing.T) (*gin.Engine, []*http.Cookie) { return r, cookies } +// Clean up the test database +func CleanupTestDatabase(db *mongo.Database) { + collection := db.Collection("users") + collection.DeleteMany(context.Background(), bson.M{}) +} + // Helper function to send a request and verify the response func sendRequestAndVerifyResponse(t *testing.T, r *gin.Engine, method, url string, payload string, cookies []*http.Cookie, expectedStatusCode int, expectedMessage string) { // Create a request to pass to the handler - req, err := http.NewRequest(method, url, bytes.NewBuffer([]byte(payload))) + var req *http.Request + var err error + + if method == http.MethodGet { + req, err = http.NewRequest(method, url, nil) + } else { + req, err = http.NewRequest(method, url, bytes.NewBuffer([]byte(payload))) + } + if err != nil { t.Fatal(err) } + + // Set the request header req.Header.Set("Content-Type", "application/json") // Add the stored cookies to the request @@ -401,6 +396,77 @@ func TestCancelBooking(t *testing.T) { } } +// Tests the GetUserDetails handler +// func TestGetUserDetails(t *testing.T) { +// // connect to the database +// db := configs.ConnectToDatabase(constants.AdminDBAccessOption) +// collection := db.Database("Occupi").Collection("users") +// parsedTime, _ := time.Parse(time.RFC3339, "2024-09-15T00:00:00Z") +// documents := []interface{}{ +// models.UserDetails{ +// OccupiID: "OCCUPI20245311", +// Password: "hashedpassword", +// Email: "john.doe@example.com", +// Role: "admin", +// OnSite: true, +// IsVerified: true, +// NextVerificationDate: parsedTime, +// Details: &models.Details{ +// ContactNo: "123-456-7890", +// DOB: parsedTime, +// Gender: "male", +// Name: "John Doe", +// Pronouns: "He/him", +// }, +// Notifications: &models.Notifications{ +// Allow: BoolPtr(true), +// BookingReminder: BoolPtr(true), +// Max_Capacity: BoolPtr(true), +// }, +// Security: &models.Security{ +// MFA: BoolPtr(true), +// Biometrics: BoolPtr(true), +// }, +// Position: "Manager", +// Status: "Active", +// }, +// } +// setup.InsertData(collection, documents) +// // Setup the test environment +// r, cookies := setupTestEnvironment(t) + +// // Define test cases +// testCases := []testCase{ +// { +// name: "Valid Request", +// payload: "/api/user-details?email=john.doe@example.com", +// expectedStatusCode: http.StatusOK, +// expectedMessage: "Successfully fetched user details", +// setupFunc: func() string { return "" }, +// }, +// { +// name: "Invalid Request", +// payload: "/api/user-details", +// expectedStatusCode: http.StatusBadRequest, +// expectedMessage: "Invalid request payload", +// setupFunc: func() string { return "" }, +// }, +// { +// name: "User Not Found", +// payload: "/api/user-details?email=jane.doe@example.com", +// expectedStatusCode: http.StatusNotFound, +// expectedMessage: "User not found", +// setupFunc: func() string { return "" }, +// }, +// } + +// for _, tc := range testCases { +// t.Run(tc.name, func(t *testing.T) { +// sendRequestAndVerifyResponse(t, r, "GET", tc.payload, "", cookies, tc.expectedStatusCode, tc.expectedMessage) +// }) +// } +// } + // Tests the BookRoom handler func TestBookRoom(t *testing.T) { // Setup the test environment From 32b9a273d65b0c1f9a94fec487029bc15006115e Mon Sep 17 00:00:00 2001 From: Rethakgetse-Manaka Date: Wed, 3 Jul 2024 02:13:09 +0200 Subject: [PATCH 4/6] "" --- occupi-backend/pkg/router/router.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/occupi-backend/pkg/router/router.go b/occupi-backend/pkg/router/router.go index 409aff62..1f5ffd51 100644 --- a/occupi-backend/pkg/router/router.go +++ b/occupi-backend/pkg/router/router.go @@ -48,7 +48,7 @@ func OccupiRouter(router *gin.Engine, db *mongo.Client) { api.POST("/cancel-booking", middleware.ProtectedRoute, func(ctx *gin.Context) { handlers.CancelBooking(ctx, appsession) }) api.GET(("/view-bookings"), middleware.ProtectedRoute, func(ctx *gin.Context) { handlers.ViewBookings(ctx, appsession) }) api.GET("/view-rooms", middleware.ProtectedRoute, func(ctx *gin.Context) { handlers.ViewRooms(ctx, appsession) }) - api.GET("/user-details", middleware.UnProtectedRoute, func(ctx *gin.Context) { handlers.GetUserDetails(ctx, appsession) }) + api.GET("/user-details", middleware.ProtectedRoute, func(ctx *gin.Context) { handlers.GetUserDetails(ctx, appsession) }) } auth := router.Group("/auth") { From 63433d0c97bf42e2b6df8e1cc86416fd12dc9d0d Mon Sep 17 00:00:00 2001 From: Rethakgetse-Manaka Date: Wed, 3 Jul 2024 02:17:10 +0200 Subject: [PATCH 5/6] "Docs updated" --- .../pages/api-documentation/api-usage.mdx | 50 ++++++++++++++++++- 1 file changed, 48 insertions(+), 2 deletions(-) diff --git a/documentation/occupi-docs/pages/api-documentation/api-usage.mdx b/documentation/occupi-docs/pages/api-documentation/api-usage.mdx index f78e743c..b2a81b98 100644 --- a/documentation/occupi-docs/pages/api-documentation/api-usage.mdx +++ b/documentation/occupi-docs/pages/api-documentation/api-usage.mdx @@ -499,7 +499,7 @@ If there are any errors during the process, appropriate error messages are retur - **Method** - `POST` + `GET` - **Request Body** @@ -526,7 +526,53 @@ If there are any errors during the process, appropriate error messages are retur - **Code:** 500 - **Content:** `{ "status": 500, "message": "Failed to get rooms", "error": {"code":"INTERNAL_SERVER_ERROR","details":null,"message":"Failed to get rooms"} }` - +### GetUserDetails + +This endpoint is used to view the details of a user in the Occupi system. +Upon a successful request, the details of the user are returned. +If there are any errors during the process, appropriate error messages are returned. + +- **URL** + + `/api/user-details?email=jane.doe@example.com` + +- **Method** + + `GET` + +- **Request Body** + +- **Content** + +```json copy +{ + "email": "string" +} +``` + +**Success Response** + +- **Code:** 200 +- **Content:** `{ "status": 200, "message": "Successfully fetched user details!", "data": {"user details"} }` + +**Error Response** + +- **Code:** 400 +- **Content:** `{ "status": 400, "message": "Invalid request payload", "error": {"code":"BAD_REQUEST","details":null,"message":"missing field required: "}, }` + +**Error Response** + +- **Code:** 404 +- **Content:** `{ "status": 404, "message": "User not found", "error": {"code":"BAD_REQUEST","details":null,"message":"User not found"} }` + +**Error Response** + +- **Code:** 500 +- **Content:** `{ "status": 500, "message": "Failed to get user details", "error": {"code":"INTERNAL_SERVER_ERROR","details":null,"message":"Failed to get user details"} }` + + + + From 775486b78f8f97421b7ba18407d91102d3687ea8 Mon Sep 17 00:00:00 2001 From: Rethakgetse-Manaka Date: Wed, 3 Jul 2024 02:25:53 +0200 Subject: [PATCH 6/6] "Lint error fixed" --- occupi-backend/pkg/models/database.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/occupi-backend/pkg/models/database.go b/occupi-backend/pkg/models/database.go index 9ddeab99..72cc94c2 100644 --- a/occupi-backend/pkg/models/database.go +++ b/occupi-backend/pkg/models/database.go @@ -41,7 +41,7 @@ type Details struct { type Notifications struct { Allow *bool `json:"allow" bson:"allow"` BookingReminder *bool `json:"bookingReminder" bson:"bookingReminder"` - Max_Capacity *bool `json:"maxCapacity" bson:"maxCapacity"` + MaxCapacity *bool `json:"maxCapacity" bson:"maxCapacity"` } type Security struct {