Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ public class SignUpAccountStepProcessor implements StepProcessor<UserJpaEntity,
@Transactional
@Override
public UserJpaEntity process(UserJpaEntity user) {
if (userRepository.existsByPhoneNumber(user.getPhoneNumber())) {
if (userRepository.existsByPhoneNumber(user.getPhoneNumber())
|| userRepository.existsByLoginId(user.getLoginId())) {
throw new CustomRuntimeException(ErrorCode.USER_ALREADY_EXISTS);
}
return userRepository.save(user);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,8 @@ public UserInfoResponse getUserInfo(Long userId) {

return UserInfoResponse.from(user);
}

public Boolean isPhoneNumberAvailable(String phoneNumber) {
return !userJpaRepository.existsByPhoneNumber(phoneNumber);
}
Comment on lines +55 to +57
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Use primitive boolean for consistency with isLoginIdAvailable and avoid boxing.

isLoginIdAvailable returns boolean; mirror that here to keep the API consistent and reduce boxing.

-    public Boolean isPhoneNumberAvailable(String phoneNumber) {
-        return !userJpaRepository.existsByPhoneNumber(phoneNumber);
-    }
+    public boolean isPhoneNumberAvailable(final String phoneNumber) {
+        return !userJpaRepository.existsByPhoneNumber(phoneNumber);
+    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public Boolean isPhoneNumberAvailable(String phoneNumber) {
return !userJpaRepository.existsByPhoneNumber(phoneNumber);
}
public boolean isPhoneNumberAvailable(final String phoneNumber) {
return !userJpaRepository.existsByPhoneNumber(phoneNumber);
}
🤖 Prompt for AI Agents
In src/main/java/life/mosu/mosuserver/application/user/UserService.java around
lines 55 to 57, change the return type of the method isPhoneNumberAvailable from
Boolean to the primitive boolean type to match the return type of
isLoginIdAvailable. This avoids unnecessary boxing and keeps the API consistent.
Update the method signature accordingly without changing the method body.

💡 Verification agent

🧩 Analysis chain

Validate/normalize phone numbers centrally; rely on DB uniqueness at write time.

Even if the controller validates format, consider normalizing to E.164 within the service layer or a dedicated utility to avoid duplicates from formatting. Also ensure a unique constraint on phoneNumber to handle TOCTOU races at signup.

Verify normalization utilities and DB uniqueness:


🏁 Script executed:

#!/bin/bash
rg -n "E164|PhoneNumberUtil|libphonenumber|normalize.*phone" -A 3
rg -n "unique.*phoneNumber|@Column\\(.*unique\\s*=\\s*true.*phoneNumber" -A 3
fd -H "(migration|migrate|db|ddl|flyway|liquibase|schema)" | xargs -I{} rg -n "phoneNumber|UNIQUE|INDEX" {}

Length of output: 1852


Normalize phone numbers to E.164 and enforce uniqueness at the database level

To avoid duplicate accounts caused by different formats and TOCTOU races on signup:

• In src/main/java/life/mosu/mosuserver/application/user/UserService.java (lines 55–57), normalize phoneNumber before checking availability, e.g.

String normalized = PhoneNumberUtil.normalizeToE164(phoneNumber);
return !userJpaRepository.existsByPhoneNumber(normalized);

• Consolidate all formatting in PhoneNumberUtil (e.g. rename/extend formatPhoneNumberWithHyphen to normalizeToE164 using libphonenumber) so every service and controller uses the same logic.

• In your JPA entity (e.g. src/main/java/life/mosu/mosuserver/application/user/UserJpaEntity.java), add

@Column(name = "phone_number", unique = true, nullable = false)
private String phoneNumber;

• Create a database migration (Flyway/Liquibase/DDL) to add a unique index or constraint on the phone_number column to guard against concurrent sign-ups.

🤖 Prompt for AI Agents
In src/main/java/life/mosu/mosuserver/application/user/UserService.java lines
55-57, normalize the input phoneNumber to E.164 format using
PhoneNumberUtil.normalizeToE164 before checking its availability with
userJpaRepository.existsByPhoneNumber. Also, update PhoneNumberUtil to include
this normalization method for consistent formatting across the app. In the
UserJpaEntity class, annotate the phoneNumber field with @Column(unique = true,
nullable = false) to enforce uniqueness at the database level. Finally, create a
database migration script to add a unique constraint or index on the
phone_number column to prevent duplicate entries during concurrent sign-ups.

}
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ public enum Whitelist {
FAQ("/api/v1/faq", WhitelistMethod.GET),
NOTICE("/api/v1/notice", WhitelistMethod.GET),
USER_ID_CHECK("/api/v1/user/check-id", WhitelistMethod.GET),
USER_PHONE_NUMBER_CHECK("/api/v1/user/check-phone-number", WhitelistMethod.GET),

CUSTOMER_KEY_CHECK("/api/v1/user/customer-key", WhitelistMethod.GET),
EXAM("/api/v1/exam", WhitelistMethod.GET),
EXAM_AREAS("/api/v1/exam/areas", WhitelistMethod.GET),
EXAM_ALL("/api/v1/exam/all", WhitelistMethod.GET),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ public static String createLocalCookieString(String name, String value, Long max
public static ResponseCookie createDevelopResponseCookie(String name, String value,
Long maxAge) {
return createBaseResponseCookieBuilder(name, value, maxAge)
.secure(true)
.secure(false)
.domain(".mosuedu.com")
.sameSite("Strict")
.build();
Expand All @@ -81,7 +81,7 @@ public static ResponseCookie createDevelopResponseCookie(String name, String val
*/
public static Cookie createDevelopCookie(String name, String value, Long maxAge) {
Cookie cookie = createBaseServletCookie(name, value, maxAge);
cookie.setSecure(true);
cookie.setSecure(false);
cookie.setDomain(".mosuedu.com");
return cookie;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@

import life.mosu.mosuserver.application.user.UserService;
import life.mosu.mosuserver.global.annotation.LoginIdPattern;
import life.mosu.mosuserver.global.annotation.PhoneNumberPattern;
import life.mosu.mosuserver.global.annotation.UserId;
import life.mosu.mosuserver.global.util.ApiResponseWrapper;
import life.mosu.mosuserver.presentation.user.dto.request.IsLoginIdAvailableResponse;
import life.mosu.mosuserver.presentation.user.dto.response.CustomerKeyResponse;
import life.mosu.mosuserver.presentation.user.dto.response.IsLoginIdAvailableResponse;
import life.mosu.mosuserver.presentation.user.dto.response.IsPhoneNumberAvailableResponse;
import life.mosu.mosuserver.presentation.user.dto.response.UserInfoResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
Expand Down Expand Up @@ -58,4 +60,14 @@ public ResponseEntity<ApiResponseWrapper<IsLoginIdAvailableResponse>> isLoginIdA
IsLoginIdAvailableResponse.from(isLoginIdAvailable)));
}

@GetMapping("/check-phone-number")
public ResponseEntity<ApiResponseWrapper<IsPhoneNumberAvailableResponse>> isPhoneNumberAvailable(
@PhoneNumberPattern @RequestParam String phoneNumber
) {
Boolean isPhoneNumberAvailable = userService.isPhoneNumberAvailable(phoneNumber);

return ResponseEntity.ok(
ApiResponseWrapper.success(HttpStatus.OK, "User PhoneNumber 등록 가능 여부 조회 성공",
IsPhoneNumberAvailableResponse.from(isPhoneNumberAvailable)));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
import life.mosu.mosuserver.global.annotation.LoginIdPattern;
import life.mosu.mosuserver.global.annotation.UserId;
import life.mosu.mosuserver.global.util.ApiResponseWrapper;
import life.mosu.mosuserver.presentation.user.dto.request.IsLoginIdAvailableResponse;
import life.mosu.mosuserver.presentation.user.dto.response.CustomerKeyResponse;
import life.mosu.mosuserver.presentation.user.dto.response.IsLoginIdAvailableResponse;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Import relocation looks correct. Consider documenting the new phone number check endpoint here.

If this interface drives OpenAPI docs for User endpoints, add the docs method for /api/v1/user/check-phoneNumber to keep API documentation complete and consistent.

Search whether the new endpoint is documented elsewhere:


🏁 Script executed:

#!/bin/bash
rg -n "check-phoneNumber|IsPhoneNumberAvailableResponse|isPhoneNumberAvailable" -A 5

Length of output: 4523


Add OpenAPI documentation for the new phone-number availability endpoint

The implementation of /api/v1/user/check-phoneNumber is present in UserController.java, but you haven’t exposed it in your OpenAPI docs interface. Please update UserControllerDocs.java to include a docs method for this endpoint, for example:

• File: src/main/java/life/mosu/mosuserver/presentation/user/UserControllerDocs.java
Insert after the isLoginIdAvailable definition:

import life.mosu.mosuserver.presentation.user.dto.response.IsPhoneNumberAvailableResponse;
// …
/**
 * Check whether a given phone number is available for registration.
 *
 * @param phoneNumber the phone number to validate
 * @return HTTP 200 with availability flag wrapped in ApiResponseWrapper
 */
@GetMapping("/check-phoneNumber")
@Operation(summary = "Check phone-number availability")
@ApiResponses({
  @ApiResponse(responseCode = "200", description = "Availability retrieved",
    content = @Content(mediaType = "application/json",
      schema = @Schema(implementation = IsPhoneNumberAvailableResponse.class))),
  // … any error responses
})
ResponseEntity<ApiResponseWrapper<IsPhoneNumberAvailableResponse>> isPhoneNumberAvailable(
    @Parameter(description = "Phone number to check", required = true)
    @PhoneNumberPattern @RequestParam String phoneNumber
);

This will keep your OpenAPI docs in sync with the new endpoint.

🤖 Prompt for AI Agents
In src/main/java/life/mosu/mosuserver/presentation/user/UserControllerDocs.java
at line 13, add OpenAPI documentation for the new /api/v1/user/check-phoneNumber
endpoint by defining a method similar to isLoginIdAvailable. Import
IsPhoneNumberAvailableResponse, then create a method annotated with
@GetMapping("/check-phoneNumber"), @Operation, and @ApiResponses that returns
ResponseEntity<ApiResponseWrapper<IsPhoneNumberAvailableResponse>>. Include a
@Parameter for the phoneNumber request parameter with validation annotation
@PhoneNumberPattern. This will expose the new endpoint in the OpenAPI docs.

import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestParam;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package life.mosu.mosuserver.presentation.user.dto.request;
package life.mosu.mosuserver.presentation.user.dto.response;

public record IsLoginIdAvailableResponse(
Boolean isLoginIdAvailable
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package life.mosu.mosuserver.presentation.user.dto.response;

public record IsPhoneNumberAvailableResponse(
Boolean isPhoneNumberAvailable
) {

public static IsPhoneNumberAvailableResponse from(Boolean isPhoneNumberAvailable) {
return new IsPhoneNumberAvailableResponse(isPhoneNumberAvailable);
}
}