Skip to content

Commit

Permalink
feat: Get user profile (#169)
Browse files Browse the repository at this point in the history
* feat(accounts): Add endpoint to get the profile of the user from their session

* docs(openapi): Update spec

* docs(http): Update bruno collection

* test(accounts): Add tests to ensure users can get and update their profile
  • Loading branch information
PedroChaparro authored Jan 15, 2024
1 parent 4c54419 commit 97a2dc9
Show file tree
Hide file tree
Showing 9 changed files with 223 additions and 3 deletions.
92 changes: 92 additions & 0 deletions __tests__/integration/accounts_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -373,3 +373,95 @@ func TestUpdatePassword(t *testing.T) {
router.ServeHTTP(w, r)
c.Equal(http.StatusOK, w.Code)
}

func TestGetAndUpdateProfile(t *testing.T) {
c := require.New(t)

// --- 1. Login as a student ---
// Register a student
testStudentName := "Yulia Ronja"
testStudentInstitutionalId := "000456791"
testStudentEmail := "yulia.ronja.2020@upb.edu.co"
testStudentOldPassword := "yulia/password/2023"

RegisterStudentAccount(requests.RegisterUserRequest{
FullName: testStudentName,
Email: testStudentEmail,
InstitutionalId: testStudentInstitutionalId,
Password: testStudentOldPassword,
})

// Login as the student
w, r := PrepareRequest("POST", "/api/v1/session/login", map[string]interface{}{
"email": testStudentEmail,
"password": testStudentOldPassword,
})
router.ServeHTTP(w, r)
cookie := w.Result().Cookies()[0]

// Get profile
profileResponse, code := GetProfileUtil(cookie)
c.Equal(http.StatusOK, code)
c.Equal(testStudentName, profileResponse["full_name"])
c.Equal(testStudentEmail, profileResponse["email"])
c.Equal(testStudentInstitutionalId, profileResponse["institutional_id"])

// Update profile
testStudentNewName := "Yulia Ronja Gillian"
testStudentNewEmail := "yulia.gillian.2024@upb.edu.co"
testStudentNewInstitutionalId := "000456792"

testCases := []GenericTestCase{
// Try to use an used email
GenericTestCase{
Payload: map[string]interface{}{
"full_name": testStudentNewName,
"email": registeredStudentEmail,
"institutional_id": testStudentNewInstitutionalId,
"password": testStudentOldPassword,
},
ExpectedStatusCode: http.StatusConflict,
},
// Try to use a wrong password
GenericTestCase{
Payload: map[string]interface{}{
"full_name": testStudentNewName,
"email": testStudentNewEmail,
"institutional_id": testStudentNewInstitutionalId,
"password": "wrong_password",
},
ExpectedStatusCode: http.StatusUnauthorized,
},
// Valid update
GenericTestCase{
Payload: map[string]interface{}{
"full_name": testStudentNewName,
"email": testStudentNewEmail,
"institutional_id": testStudentNewInstitutionalId,
"password": testStudentOldPassword,
},
ExpectedStatusCode: http.StatusNoContent,
},
}

for _, testCase := range testCases {
tcInstitutionalId := testCase.Payload["institutional_id"].(string)

code = UpdateProfileUtil(UpdateProfileUtilDTO{
FullName: testCase.Payload["full_name"].(string),
Email: testCase.Payload["email"].(string),
InstitutionalId: &tcInstitutionalId,
Password: testCase.Payload["password"].(string),
Cookie: cookie,
})

c.Equal(testCase.ExpectedStatusCode, code)
}

// Get updated profile
profileResponse, code = GetProfileUtil(cookie)
c.Equal(http.StatusOK, code)
c.Equal(testStudentNewName, profileResponse["full_name"])
c.Equal(testStudentNewEmail, profileResponse["email"])
c.Equal(testStudentNewInstitutionalId, profileResponse["institutional_id"])
}
31 changes: 31 additions & 0 deletions __tests__/integration/accounts_utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,34 @@ func UpdatePasswordUtil(dto UpdatePasswordUtilDTO) int {
router.ServeHTTP(w, r)
return w.Code
}

type UpdateProfileUtilDTO struct {
FullName string
Email string
Password string
InstitutionalId *string
Cookie *http.Cookie
}

// UpdateProfileUtil sends a request to update the profile of an account and returns the status code
// of the response
func UpdateProfileUtil(dto UpdateProfileUtilDTO) int {
w, r := PrepareRequest("PUT", "/api/v1/accounts/profile", map[string]interface{}{
"full_name": dto.FullName,
"email": dto.Email,
"password": dto.Password,
"institutional_id": dto.InstitutionalId,
})
r.AddCookie(dto.Cookie)
router.ServeHTTP(w, r)
return w.Code
}

// GetProfileUtil sends a request to get the profile of an account and returns the JSON response
// as a map and the status code of the response
func GetProfileUtil(cookie *http.Cookie) (response map[string]interface{}, statusCode int) {
w, r := PrepareRequest("GET", "/api/v1/accounts/profile", nil)
r.AddCookie(cookie)
router.ServeHTTP(w, r)
return ParseJsonResponse(w.Body), w.Code
}
11 changes: 11 additions & 0 deletions docs/bruno/users/get-user-profile.bru
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
meta {
name: get-user-profile
type: http
seq: 8
}

get {
url: {{BASE_URL}}/accounts/profile
body: none
auth: none
}
2 changes: 1 addition & 1 deletion docs/bruno/users/update-profile.bru
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ meta {
}

put {
url: {{BASE_URL}}/accounts
url: {{BASE_URL}}/accounts/profile
body: json
auth: none
}
Expand Down
41 changes: 40 additions & 1 deletion docs/openapi/spec.openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,33 @@ paths:
schema:
$ref: "#/components/schemas/default_error_response"

/accounts:
/accounts/profile:
get:
tags:
- Accounts
security:
- cookieAuth: []
description: Get the user's profile. This endpoint is necessary since we do not want to include sensitive data as the user full name and email and keep them in the session when the `/whoami` endpoint is requested.
responses:
"200":
description: The profile details were obtained.
content:
application/json:
schema:
$ref: "#/components/schemas/account_profile_response"
"403":
description: The session token isn't valid or the user doesn't have enough permissions.
content:
application/json:
schema:
$ref: "#/components/schemas/default_error_response"
"500":
description: There was an unexpected error in the server side.
content:
application/json:
schema:
$ref: "#/components/schemas/default_error_response"

put:
tags:
- Accounts
Expand Down Expand Up @@ -2242,6 +2268,19 @@ components:
role:
type: string
example: "student"

account_profile_response:
type: object
properties:
full_name:
type: string
example: "Pedro Chaparro"
email:
type: string
example: "pedrochaparro@upb.edu.co"
institutional_id:
type: string
example: "000371272"

public_user_fields:
type: object
Expand Down
21 changes: 21 additions & 0 deletions src/accounts/application/use_cases.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,3 +180,24 @@ func (useCases *AccountsUseCases) UpdateProfile(dto dtos.UpdateAccountDTO) error
err = useCases.AccountsRepository.UpdateProfile(dto)
return err
}

func (useCases *AccountsUseCases) GetProfile(userUUID string) (*dtos.UserProfileDTO, error) {
// Get user
user, err := useCases.AccountsRepository.GetUserByUUID(userUUID)
if err != nil {
if err == sql.ErrNoRows {
return nil, errors.UserNotFoundError{}
}

return nil, err
}

// Create DTO
dto := dtos.UserProfileDTO{
FullName: user.FullName,
Email: user.Email,
InstitutionalId: &user.InstitutionalId,
}

return &dto, nil
}
6 changes: 6 additions & 0 deletions src/accounts/domain/dtos/accounts_dtos.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,9 @@ type UpdateAccountDTO struct {
InstitutionalId *string
Password string
}

type UserProfileDTO struct {
FullName string `json:"full_name"`
Email string `json:"email"`
InstitutionalId *string `json:"institutional_id"`
}
15 changes: 15 additions & 0 deletions src/accounts/infrastructure/http/controllers.go
Original file line number Diff line number Diff line change
Expand Up @@ -271,3 +271,18 @@ func (controller *AccountsController) HandleUpdateProfile(c *gin.Context) {

c.Status(http.StatusNoContent)
}

// HandleGetProfile controller to get the profile of an account
func (controller *AccountsController) HandleGetProfile(c *gin.Context) {
userUUID := c.GetString("session_uuid")

// Get profile
profileDTO, err := controller.UseCases.GetProfile(userUUID)

if err != nil {
c.Error(err)
return
}

c.JSON(http.StatusOK, profileDTO)
}
7 changes: 6 additions & 1 deletion src/accounts/infrastructure/http/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,13 @@ func StartAccountsRoutes(g *gin.RouterGroup) {
sharedInfrastructure.WithAuthenticationMiddleware(),
controller.HandleUpdatePassword,
)
accountsGroup.GET(
"/profile",
sharedInfrastructure.WithAuthenticationMiddleware(),
controller.HandleGetProfile,
)
accountsGroup.PUT(
"",
"/profile",
sharedInfrastructure.WithAuthenticationMiddleware(),
controller.HandleUpdateProfile,
)
Expand Down

0 comments on commit 97a2dc9

Please sign in to comment.