Skip to content

Commit

Permalink
Merge pull request #138 from COS301-SE-2024/feature/backend/RetrieveU…
Browse files Browse the repository at this point in the history
…serDetails

User Details Endpoint
  • Loading branch information
Rethakgetse-Manaka authored Jul 3, 2024
2 parents b4d3aec + 775486b commit 33bbc70
Show file tree
Hide file tree
Showing 6 changed files with 216 additions and 3 deletions.
50 changes: 48 additions & 2 deletions documentation/occupi-docs/pages/api-documentation/api-usage.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -499,7 +499,7 @@ If there are any errors during the process, appropriate error messages are retur

- **Method**

`POST`
`GET`

- **Request Body**

Expand All @@ -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/[email protected]`

- **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: <name of field>"}, }`

**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"} }`







Expand Down
14 changes: 14 additions & 0 deletions occupi-backend/pkg/database/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
22 changes: 22 additions & 0 deletions occupi-backend/pkg/handlers/api_handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,28 @@ 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
}

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 {
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
Expand Down
35 changes: 35 additions & 0 deletions occupi-backend/pkg/models/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,41 @@ 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"`
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"`
Gender string `json:"gender" bson:"gender"`
Pronouns string `json:"pronouns" bson:"pronouns"`
}

type Notifications struct {
Allow *bool `json:"allow" bson:"allow"`
BookingReminder *bool `json:"bookingReminder" bson:"bookingReminder"`
MaxCapacity *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"`
Expand Down
1 change: 1 addition & 0 deletions occupi-backend/pkg/router/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
{
Expand Down
97 changes: 96 additions & 1 deletion occupi-backend/tests/handlers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package tests

import (
"bytes"
"context"
"encoding/json"
"fmt"
"net/http"
Expand All @@ -12,6 +13,8 @@ import (
"time"

"github.com/stretchr/testify/assert"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"

"github.com/gin-gonic/gin"

Expand Down Expand Up @@ -157,6 +160,9 @@ func createMockBooking(r *gin.Engine, payload string, cookies []*http.Cookie) (m

return response, nil
}
func BoolPtr(b bool) *bool {
return &b
}

// SetupTestEnvironment initializes the test environment and returns the router and cookies
func setupTestEnvironment(t *testing.T) (*gin.Engine, []*http.Cookie) {
Expand Down Expand Up @@ -195,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
Expand Down Expand Up @@ -279,6 +301,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
Expand Down Expand Up @@ -372,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: "[email protected]",
// 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/[email protected]",
// 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/[email protected]",
// 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
Expand Down

0 comments on commit 33bbc70

Please sign in to comment.