Skip to content

Commit

Permalink
Merge pull request #42 from CMIPT/kaiser-crud
Browse files Browse the repository at this point in the history
Add function for checking the info validity.
  • Loading branch information
Kaiser-Yang authored Sep 13, 2024
2 parents 1a8f6c7 + 89031c6 commit ef444f4
Show file tree
Hide file tree
Showing 7 changed files with 198 additions and 12 deletions.
5 changes: 4 additions & 1 deletion src/main/java/edu/cmipt/gcs/constant/ApiPathConstant.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,8 @@ public class ApiPathConstant {

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}";
public static final String USER_GET_USER_BY_NAME_API_PATH = USER_API_PREFIX + "/{username}";
public static final String USER_CHECK_EMAIL_VALIDITY_API_PATH = USER_API_PREFIX + "/email";
public static final String USER_CHECK_USERNAME_VALIDITY_API_PATH =
USER_API_PREFIX + "/username";
}
94 changes: 93 additions & 1 deletion src/main/java/edu/cmipt/gcs/controller/UserController.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;

import edu.cmipt.gcs.constant.ApiPathConstant;
import edu.cmipt.gcs.constant.HeaderParameter;
import edu.cmipt.gcs.constant.ValidationConstant;
import edu.cmipt.gcs.enumeration.ErrorCodeEnum;
import edu.cmipt.gcs.exception.GenericException;
import edu.cmipt.gcs.pojo.error.ErrorVO;
Expand All @@ -11,27 +13,52 @@
import edu.cmipt.gcs.service.UserService;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
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 jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;

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

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

@GetMapping(ApiPathConstant.USER_GET_BY_NAME_API_PATH)
@GetMapping(ApiPathConstant.USER_GET_USER_BY_NAME_API_PATH)
@Operation(
summary = "Get user by name",
description = "Get user information by user name",
tags = {"User", "Get Method"})
@Parameters({
@Parameter(
name = HeaderParameter.TOKEN,
description = "Access token",
required = true,
in = ParameterIn.HEADER,
schema = @Schema(implementation = String.class)),
@Parameter(
name = "username",
description = "User name",
example = "admin",
required = true,
in = ParameterIn.PATH,
schema = @Schema(implementation = String.class))
})
@ApiResponses({
@ApiResponse(responseCode = "200", description = "User information returned successfully"),
@ApiResponse(
Expand All @@ -47,4 +74,69 @@ public UserVO getUserByName(@PathVariable("username") String username) {
}
return new UserVO(userService.getOne(wrapper));
}

@GetMapping(ApiPathConstant.USER_CHECK_EMAIL_VALIDITY_API_PATH)
@Operation(
summary = "Check email validity",
description = "Check if the email is valid",
tags = {"User", "Get Method"})
@Parameter(
name = "email",
description = "Email",
example = "[email protected]",
required = true,
in = ParameterIn.QUERY,
schema = @Schema(implementation = String.class))
@ApiResponses({
@ApiResponse(responseCode = "200", description = "Email validity checked successfully"),
@ApiResponse(
responseCode = "403",
description = "Email is invalid",
content = @Content(schema = @Schema(implementation = ErrorVO.class)))
})
public void checkEmailValidity(
@RequestParam("email")
@Email(message = "USERDTO_EMAIL_EMAIL {UserDTO.email.Email}")
@NotBlank(message = "USERDTO_EMAIL_NOTBLANK {UserDTO.email.NotBlank}")
String email) {
QueryWrapper<UserPO> wrapper = new QueryWrapper<UserPO>();
wrapper.eq("email", email);
if (userService.exists(wrapper)) {
throw new GenericException(ErrorCodeEnum.EMAIL_ALREADY_EXISTS, email);
}
}

@GetMapping(ApiPathConstant.USER_CHECK_USERNAME_VALIDITY_API_PATH)
@Operation(
summary = "Check username validity",
description = "Check if the username is valid",
tags = {"User", "Get Method"})
@Parameter(
name = "username",
description = "User name",
example = "admin",
required = true,
in = ParameterIn.QUERY,
schema = @Schema(implementation = String.class))
@ApiResponses({
@ApiResponse(responseCode = "200", description = "Username validity checked successfully"),
@ApiResponse(
responseCode = "403",
description = "Username is not valid",
content = @Content(schema = @Schema(implementation = ErrorVO.class)))
})
public void checkUsernameValidity(
@RequestParam("username")
@Size(
min = ValidationConstant.MIN_USERNAME_LENGTH,
max = ValidationConstant.MAX_USERNAME_LENGTH,
message = "USERDTO_USERNAME_SIZE {UserDTO.username.Size}")
@NotBlank(message = "USERDTO_USERNAME_NOTBLANK {UserDTO.username.NotBlank}")
String username) {
QueryWrapper<UserPO> wrapper = new QueryWrapper<UserPO>();
wrapper.eq("username", username);
if (userService.exists(wrapper)) {
throw new GenericException(ErrorCodeEnum.USERNAME_ALREADY_EXISTS, username);
}
}
}
1 change: 1 addition & 0 deletions src/main/java/edu/cmipt/gcs/enumeration/ErrorCodeEnum.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public enum ErrorCodeEnum {

INVALID_TOKEN("INVALID_TOKEN"),
ACCESS_DENIED("ACCESS_DENIED"),
TOKEN_NOT_FOUND("TOKEN_NOT_FOUND"),

MESSAGE_CONVERSION_ERROR("MESSAGE_CONVERSION_ERROR"),

Expand Down
37 changes: 28 additions & 9 deletions src/main/java/edu/cmipt/gcs/exception/GlobalExceptionHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import edu.cmipt.gcs.pojo.error.ErrorVO;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.ConstraintViolationException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand Down Expand Up @@ -35,15 +36,22 @@ public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ErrorVO> handleMethodArgumentNotValidException(
MethodArgumentNotValidException e, HttpServletRequest request) {
// we only handle one validation message
String codeAndMessage = e.getFieldError().getDefaultMessage();
int firstSpaceIndex = codeAndMessage.indexOf(" ");
// There must be a space and not at the end of the message
assert firstSpaceIndex != -1;
assert firstSpaceIndex != codeAndMessage.length() - 1;
var exception = new GenericException(codeAndMessage.substring(firstSpaceIndex + 1));
exception.setCode(ErrorCodeEnum.valueOf(codeAndMessage.substring(0, firstSpaceIndex)));
return handleGenericException(exception, request);
return handleValidationException(e.getFieldError().getDefaultMessage(), request);
}

/**
* Handles ConstraintViolationException
*
* <p>This method is used to handle the ConstraintViolationException, which is thrown when the
* validation of the path variables or request parameters fails.
*
* @param e ConstraintViolationException
*/
@ExceptionHandler(ConstraintViolationException.class)
public ResponseEntity<ErrorVO> handleConstraintViolationException(
ConstraintViolationException e, HttpServletRequest request) {
return handleValidationException(
e.getConstraintViolations().iterator().next().getMessage(), request);
}

@ExceptionHandler(HttpMessageNotReadableException.class)
Expand Down Expand Up @@ -78,4 +86,15 @@ public ResponseEntity<ErrorVO> handleGenericException(
public void handleException(Exception e) {
logger.error(e.getMessage());
}

private ResponseEntity<ErrorVO> handleValidationException(
String codeAndMessage, HttpServletRequest request) {
int firstSpaceIndex = codeAndMessage.indexOf(" ");
// There must be a space and not at the end of the message
assert firstSpaceIndex != -1;
assert firstSpaceIndex != codeAndMessage.length() - 1;
var exception = new GenericException(codeAndMessage.substring(firstSpaceIndex + 1));
exception.setCode(ErrorCodeEnum.valueOf(codeAndMessage.substring(0, firstSpaceIndex)));
return handleGenericException(exception, request);
}
}
7 changes: 6 additions & 1 deletion src/main/java/edu/cmipt/gcs/filter/JwtFilter.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@ public class JwtFilter extends OncePerRequestFilter {
ApiPathConstant.AUTHENTICATION_SIGN_IN_API_PATH,
ApiPathConstant.AUTHENTICATION_SIGN_OUT_API_PATH,
ApiPathConstant.DEVELOPMENT_GET_API_MAP_API_PATH,
ApiPathConstant.DEVELOPMENT_GET_ERROR_MESSAGE_API_PATH);
ApiPathConstant.DEVELOPMENT_GET_ERROR_MESSAGE_API_PATH,
ApiPathConstant.USER_CHECK_EMAIL_VALIDITY_API_PATH,
ApiPathConstant.USER_CHECK_USERNAME_VALIDITY_API_PATH);

@Override
protected void doFilterInternal(
Expand All @@ -55,6 +57,9 @@ protected void doFilterInternal(
}

private void authorize(HttpServletRequest request, String token) {
if (token == null) {
throw new GenericException(ErrorCodeEnum.TOKEN_NOT_FOUND);
}
switch (JwtUtil.getTokenType(token)) {
case ACCESS_TOKEN:
// ACCESS_TOKEN can not be used for refresh
Expand Down
1 change: 1 addition & 0 deletions src/main/resources/message/message.properties
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ EMAIL_ALREADY_EXISTS=Email already exists: {}
WRONG_SIGN_IN_INFORMATION=Wrong sign in information

INVALID_TOKEN=Invalid token: {}
TOKEN_NOT_FOUND=Token not found in header
ACCESS_DENIED=Operation without privileges

MESSAGE_CONVERSION_ERROR=Error occurs while converting message
Expand Down
65 changes: 65 additions & 0 deletions src/test/java/edu/cmipt/gcs/controller/UserControllerTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.servlet.MockMvc;

import java.util.Date;

/**
* Tests for UserController
*
Expand Down Expand Up @@ -62,4 +64,67 @@ public void testGetUserByNameInvalid() throws Exception {
ErrorCodeEnum.USER_NOT_FOUND,
invalidUsername))));
}

@Test
public void testCheckEmailValidityExists() throws Exception {
mvc.perform(
get(ApiPathConstant.USER_CHECK_EMAIL_VALIDITY_API_PATH)
.param("email", TestConstant.EMAIL))
.andExpectAll(
status().isBadRequest(),
content()
.json(
"""
{
"code": %d,
"message": "%s"
}
"""
.formatted(
ErrorCodeEnum.EMAIL_ALREADY_EXISTS
.ordinal(),
MessageSourceUtil.getMessage(
ErrorCodeEnum.EMAIL_ALREADY_EXISTS,
TestConstant.EMAIL))));
}

@Test
public void testCheckEmailValidityValid() throws Exception {
mvc.perform(
get(ApiPathConstant.USER_CHECK_EMAIL_VALIDITY_API_PATH)
.param("email", new Date().getTime() + "@cmipt.edu"))
.andExpectAll(status().isOk());
}

@Test
public void testCheckUsernameValidityExists() throws Exception {
mvc.perform(
get(ApiPathConstant.USER_CHECK_USERNAME_VALIDITY_API_PATH)
.param("username", TestConstant.USERNAME))
.andExpectAll(
status().isBadRequest(),
content()
.json(
"""
{
"code": %d,
"message": "%s"
}
"""
.formatted(
ErrorCodeEnum.USERNAME_ALREADY_EXISTS
.ordinal(),
MessageSourceUtil.getMessage(
ErrorCodeEnum
.USERNAME_ALREADY_EXISTS,
TestConstant.USERNAME))));
}

@Test
public void testCheckUsernameValidityValid() throws Exception {
mvc.perform(
get(ApiPathConstant.USER_CHECK_USERNAME_VALIDITY_API_PATH)
.param("username", new Date().getTime() + ""))
.andExpectAll(status().isOk());
}
}

0 comments on commit ef444f4

Please sign in to comment.