Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add function to get user info by name #41

Merged
merged 2 commits into from
Sep 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/main/java/edu/cmipt/gcs/constant/ApiPathConstant.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,6 @@ public class ApiPathConstant {
DEVELOPMENT_API_PREFIX + "/error";

public static final String USER_API_PREFIX = ALL_API_PREFIX + "/user";

public static final String USER_GET_BY_NAME_API_PATH = USER_API_PREFIX + "/{username}";
}
2 changes: 2 additions & 0 deletions src/main/java/edu/cmipt/gcs/constant/HeaderParameter.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,6 @@

public class HeaderParameter {
public static final String TOKEN = "Token";
public static final String ACCESS_TOKEN = "Access-Token";
public static final String REFRESH_TOKEN = "Refresh-Token";
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import io.swagger.v3.oas.annotations.tags.Tag;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
Expand Down Expand Up @@ -96,14 +97,22 @@ public void signUp(@Validated(CreateGroup.class) @RequestBody UserDTO user) {
content = @Content(schema = @Schema(implementation = ErrorVO.class))),
@ApiResponse(responseCode = "500", description = "Internal server error")
})
public ResponseEntity<?> signIn(@Validated @RequestBody UserSignInDTO user) {
public ResponseEntity<UserVO> signIn(@Validated @RequestBody UserSignInDTO user) {
QueryWrapper<UserPO> wrapper = new QueryWrapper<UserPO>();
wrapper.eq("username", user.username());
wrapper.eq("user_password", MD5Converter.convertToMD5(user.userPassword()));
if (!userService.exists(wrapper)) {
throw new GenericException(ErrorCodeEnum.WRONG_SIGN_IN_INFORMATION);
}
return ResponseEntity.ok(new UserVO(userService.getOne(wrapper)));
UserVO userVO = new UserVO(userService.getOne(wrapper));
HttpHeaders headers = new HttpHeaders();
headers.add(
HeaderParameter.ACCESS_TOKEN,
JwtUtil.generateToken(userVO.id(), TokenTypeEnum.ACCESS_TOKEN));
headers.add(
HeaderParameter.REFRESH_TOKEN,
JwtUtil.generateToken(userVO.id(), TokenTypeEnum.REFRESH_TOKEN));
return ResponseEntity.ok().headers(headers).body(userVO);
}

@DeleteMapping(ApiPathConstant.AUTHENTICATION_SIGN_OUT_API_PATH)
Expand Down Expand Up @@ -134,7 +143,11 @@ public void signOut(@RequestBody List<String> tokenList) {
content = @Content(schema = @Schema(implementation = String.class))),
@ApiResponse(responseCode = "500", description = "Internal server error")
})
public String refreshToken(@RequestHeader(HeaderParameter.TOKEN) String token) {
return JwtUtil.generateToken(JwtUtil.getID(token), TokenTypeEnum.ACCESS_TOKEN);
public ResponseEntity<Void> refreshToken(@RequestHeader(HeaderParameter.TOKEN) String token) {
HttpHeaders headers = new HttpHeaders();
headers.add(
HeaderParameter.ACCESS_TOKEN,
JwtUtil.generateToken(JwtUtil.getID(token), TokenTypeEnum.ACCESS_TOKEN));
return ResponseEntity.ok().headers(headers).build();
}
}
36 changes: 36 additions & 0 deletions src/main/java/edu/cmipt/gcs/controller/UserController.java
Original file line number Diff line number Diff line change
@@ -1,14 +1,50 @@
package edu.cmipt.gcs.controller;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;

import edu.cmipt.gcs.constant.ApiPathConstant;
import edu.cmipt.gcs.enumeration.ErrorCodeEnum;
import edu.cmipt.gcs.exception.GenericException;
import edu.cmipt.gcs.pojo.error.ErrorVO;
import edu.cmipt.gcs.pojo.user.UserPO;
import edu.cmipt.gcs.pojo.user.UserVO;
import edu.cmipt.gcs.service.UserService;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

@RestController
@Tag(name = "User", description = "User Related APIs")
public class UserController {
@Autowired private UserService userService;

@GetMapping(ApiPathConstant.USER_GET_BY_NAME_API_PATH)
@Operation(
summary = "Get user by name",
description = "Get user information by user name",
tags = {"User", "Get Method"})
@ApiResponses({
@ApiResponse(responseCode = "200", description = "User information returned successfully"),
@ApiResponse(
responseCode = "404",
description = "User not found",
content = @Content(schema = @Schema(implementation = ErrorVO.class)))
})
public UserVO getUserByName(@PathVariable("username") String username) {
QueryWrapper<UserPO> wrapper = new QueryWrapper<UserPO>();
wrapper.eq("username", username);
if (!userService.exists(wrapper)) {
throw new GenericException(ErrorCodeEnum.USER_NOT_FOUND, username);
}
return new UserVO(userService.getOne(wrapper));
}
}
4 changes: 3 additions & 1 deletion src/main/java/edu/cmipt/gcs/enumeration/ErrorCodeEnum.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ public enum ErrorCodeEnum {
INVALID_TOKEN("INVALID_TOKEN"),
ACCESS_DENIED("ACCESS_DENIED"),

MESSAGE_CONVERSION_ERROR("MESSAGE_CONVERSION_ERROR");
MESSAGE_CONVERSION_ERROR("MESSAGE_CONVERSION_ERROR"),

USER_NOT_FOUND("USER_NOT_FOUND");

// code means the error code in the message.properties
private String code;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ public ResponseEntity<ErrorVO> handleGenericException(
case ACCESS_DENIED:
return ResponseEntity.status(HttpStatus.FORBIDDEN)
.body(new ErrorVO(e.getCode(), e.getMessage()));
case USER_NOT_FOUND:
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(new ErrorVO(e.getCode(), e.getMessage()));
default:
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(new ErrorVO(e.getCode(), e.getMessage()));
Expand Down
14 changes: 2 additions & 12 deletions src/main/java/edu/cmipt/gcs/pojo/user/UserVO.java
Original file line number Diff line number Diff line change
@@ -1,23 +1,13 @@
package edu.cmipt.gcs.pojo.user;

import edu.cmipt.gcs.enumeration.TokenTypeEnum;
import edu.cmipt.gcs.util.JwtUtil;

import io.swagger.v3.oas.annotations.media.Schema;

@Schema(description = "User Value Object")
public record UserVO(
@Schema(description = "User ID") Long id,
@Schema(description = "Username", example = "admin") String username,
@Schema(description = "Email", example = "[email protected]") String email,
@Schema(description = "Access Token") String accessToken,
@Schema(description = "Refresh Token") String refreshToken) {
@Schema(description = "Email", example = "[email protected]") String email) {
public UserVO(UserPO userPO) {
this(
userPO.getId(),
userPO.getUsername(),
userPO.getEmail(),
JwtUtil.generateToken(userPO.getId(), TokenTypeEnum.ACCESS_TOKEN),
JwtUtil.generateToken(userPO.getId(), TokenTypeEnum.REFRESH_TOKEN));
this(userPO.getId(), userPO.getUsername(), userPO.getEmail());
}
}
2 changes: 2 additions & 0 deletions src/main/resources/message/message.properties
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,5 @@ INVALID_TOKEN=Invalid token: {}
ACCESS_DENIED=Operation without privileges

MESSAGE_CONVERSION_ERROR=Error occurs while converting message

USER_NOT_FOUND=User not found: {}
11 changes: 11 additions & 0 deletions src/test/java/edu/cmipt/gcs/constant/TestConstant.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package edu.cmipt.gcs.constant;

import java.util.Date;

public class TestConstant {
public static String USERNAME = new Date().getTime() + "";
public static String USER_PASSWORD = "123456";
public static String EMAIL = USERNAME + "@cmipt.edu";
public static String ACCESS_TOKEN;
public static String REFRESH_TOKEN;
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
package edu.cmipt.gcs.controller;

import static org.hamcrest.Matchers.is;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import com.jayway.jsonpath.JsonPath;

import edu.cmipt.gcs.constant.ApiPathConstant;
import edu.cmipt.gcs.constant.HeaderParameter;
import edu.cmipt.gcs.constant.TestConstant;
import edu.cmipt.gcs.enumeration.ErrorCodeEnum;
import edu.cmipt.gcs.util.MessageSourceUtil;

Expand All @@ -22,9 +24,6 @@
import org.springframework.core.Ordered;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;

import java.util.Date;

/**
* Tests for AuthenticationController
Expand All @@ -34,12 +33,10 @@
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
@Order(Ordered.HIGHEST_PRECEDENCE)
public class AuthenticationControllerTest {
@Autowired private MockMvc mvc;

private static String username = new Date().getTime() + "";
private static String userPassword = "123456";
private static String email = username + "@cmipt.edu";
private static String userDTO =
"""
{
Expand All @@ -48,17 +45,16 @@ public class AuthenticationControllerTest {
"userPassword": "%s"
}
"""
.formatted(username, email, userPassword);
.formatted(
TestConstant.USERNAME, TestConstant.EMAIL, TestConstant.USER_PASSWORD);
private static String userSignInDTO =
"""
{
"username": "%s",
"userPassword": "%s"
}
"""
.formatted(username, userPassword);
private static String accessToken;
private static String refreshToken;
.formatted(TestConstant.USERNAME, TestConstant.USER_PASSWORD);

private static String invalidUserDTO =
"""
Expand All @@ -75,7 +71,7 @@ public class AuthenticationControllerTest {
"userPassword": "%s"
}
"""
.formatted(username, userPassword + "wrong");
.formatted(TestConstant.USERNAME, TestConstant.USER_PASSWORD + "wrong");

/**
* Test sign in with invalid user information
Expand All @@ -88,7 +84,7 @@ public class AuthenticationControllerTest {
@Order(Ordered.HIGHEST_PRECEDENCE)
public void testSignUpValid() throws Exception {
mvc.perform(
MockMvcRequestBuilders.post(ApiPathConstant.AUTHENTICATION_SIGN_UP_API_PATH)
post(ApiPathConstant.AUTHENTICATION_SIGN_UP_API_PATH)
.contentType(MediaType.APPLICATION_JSON)
.content(userDTO))
.andExpect(status().isOk());
Expand All @@ -105,24 +101,22 @@ public void testSignUpValid() throws Exception {
@Test
@Order(Ordered.HIGHEST_PRECEDENCE + 1)
public void testSignInValid() throws Exception {
String jsonResponse =
var response =
mvc.perform(
MockMvcRequestBuilders.post(
ApiPathConstant.AUTHENTICATION_SIGN_IN_API_PATH)
post(ApiPathConstant.AUTHENTICATION_SIGN_IN_API_PATH)
.contentType(MediaType.APPLICATION_JSON)
.content(userSignInDTO))
.andExpectAll(
status().isOk(),
jsonPath("$.username", is(username)),
jsonPath("$.email", is(email)),
jsonPath("$.username", is(TestConstant.USERNAME)),
jsonPath("$.email", is(TestConstant.EMAIL)),
jsonPath("$.id").isNumber(),
jsonPath("$.accessToken").isString(),
jsonPath("$.refreshToken").isString())
header().exists(HeaderParameter.ACCESS_TOKEN),
header().exists(HeaderParameter.REFRESH_TOKEN))
.andReturn()
.getResponse()
.getContentAsString();
accessToken = JsonPath.read(jsonResponse, "$.accessToken");
refreshToken = JsonPath.read(jsonResponse, "$.refreshToken");
.getResponse();
TestConstant.ACCESS_TOKEN = response.getHeader(HeaderParameter.ACCESS_TOKEN);
TestConstant.REFRESH_TOKEN = response.getHeader(HeaderParameter.REFRESH_TOKEN);
}

/**
Expand All @@ -136,15 +130,15 @@ public void testSignInValid() throws Exception {
@Order(Ordered.HIGHEST_PRECEDENCE + 2)
public void testRefreshValid() throws Exception {
mvc.perform(
MockMvcRequestBuilders.get(ApiPathConstant.AUTHENTICATION_REFRESH_API_PATH)
.header(HeaderParameter.TOKEN, refreshToken))
.andExpect(status().isOk());
get(ApiPathConstant.AUTHENTICATION_REFRESH_API_PATH)
.header(HeaderParameter.TOKEN, TestConstant.REFRESH_TOKEN))
.andExpectAll(status().isOk(), header().exists(HeaderParameter.ACCESS_TOKEN));
}

@Test
public void testSignInInvalid() throws Exception {
mvc.perform(
MockMvcRequestBuilders.post(ApiPathConstant.AUTHENTICATION_SIGN_IN_API_PATH)
post(ApiPathConstant.AUTHENTICATION_SIGN_IN_API_PATH)
.contentType(MediaType.APPLICATION_JSON)
.content(invalidUserSignInDTO))
.andExpectAll(
Expand All @@ -168,7 +162,7 @@ public void testSignInInvalid() throws Exception {
@Test
public void testSignUpInvalid() throws Exception {
mvc.perform(
MockMvcRequestBuilders.post(ApiPathConstant.AUTHENTICATION_SIGN_UP_API_PATH)
post(ApiPathConstant.AUTHENTICATION_SIGN_UP_API_PATH)
.contentType(MediaType.APPLICATION_JSON)
.content(invalidUserDTO))
.andExpectAll(
Expand All @@ -192,7 +186,7 @@ public void testSignUpInvalid() throws Exception {
public void testRefreshInvalid() throws Exception {
String invalidToken = "This is a invalid token";
mvc.perform(
MockMvcRequestBuilders.get(ApiPathConstant.AUTHENTICATION_REFRESH_API_PATH)
get(ApiPathConstant.AUTHENTICATION_REFRESH_API_PATH)
.header(HeaderParameter.TOKEN, invalidToken))
.andExpectAll(
status().isUnauthorized(),
Expand Down
48 changes: 48 additions & 0 deletions src/test/java/edu/cmipt/gcs/controller/UserControllerTest.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
package edu.cmipt.gcs.controller;

import static org.hamcrest.Matchers.is;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import edu.cmipt.gcs.constant.ApiPathConstant;
import edu.cmipt.gcs.constant.HeaderParameter;
import edu.cmipt.gcs.constant.TestConstant;
import edu.cmipt.gcs.enumeration.ErrorCodeEnum;
import edu.cmipt.gcs.util.MessageSourceUtil;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
Expand All @@ -14,4 +27,39 @@
@AutoConfigureMockMvc
public class UserControllerTest {
@Autowired private MockMvc mvc;

@Test
public void testGetUserByNameValid() throws Exception {
mvc.perform(
get(ApiPathConstant.USER_API_PREFIX + "/" + TestConstant.USERNAME)
.header(HeaderParameter.TOKEN, TestConstant.ACCESS_TOKEN))
.andExpectAll(
status().isOk(),
jsonPath("$.username", is(TestConstant.USERNAME)),
jsonPath("$.email", is(TestConstant.EMAIL)),
jsonPath("$.id").isNumber());
}

@Test
public void testGetUserByNameInvalid() throws Exception {
String invalidUsername = TestConstant.USERNAME + "invalid";
mvc.perform(
get(ApiPathConstant.USER_API_PREFIX + "/" + invalidUsername)
.header(HeaderParameter.TOKEN, TestConstant.ACCESS_TOKEN))
.andExpectAll(
status().isNotFound(),
content()
.json(
"""
{
"code": %d,
"message": "%s"
}
"""
.formatted(
ErrorCodeEnum.USER_NOT_FOUND.ordinal(),
MessageSourceUtil.getMessage(
ErrorCodeEnum.USER_NOT_FOUND,
invalidUsername))));
}
}
Loading