-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #42 from Tico-Corp/feat-be/TICO-222-create-admin-api
[FEAT] 관리자 회원 가입 및 로그인 API 구현 (TICO-222)
- Loading branch information
Showing
15 changed files
with
428 additions
and
44 deletions.
There are no files selected for viewing
111 changes: 111 additions & 0 deletions
111
...nd/pomoro-do/src/main/java/com/tico/pomoro_do/domain/user/controller/AdminController.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
package com.tico.pomoro_do.domain.user.controller; | ||
|
||
import com.tico.pomoro_do.domain.user.dto.request.AdminJoinDTO; | ||
import com.tico.pomoro_do.domain.user.dto.request.AdminLoginDTO; | ||
import com.tico.pomoro_do.domain.user.dto.response.JwtDTO; | ||
import com.tico.pomoro_do.domain.user.service.AdminService; | ||
import com.tico.pomoro_do.global.base.CustomSuccessCode; | ||
import com.tico.pomoro_do.global.base.SuccessResponseDTO; | ||
import com.tico.pomoro_do.global.exception.ErrorResponseEntity; | ||
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 lombok.RequiredArgsConstructor; | ||
import lombok.extern.slf4j.Slf4j; | ||
import org.springframework.http.HttpStatus; | ||
import org.springframework.http.ResponseEntity; | ||
import org.springframework.web.bind.annotation.*; | ||
|
||
import java.io.IOException; | ||
import java.security.GeneralSecurityException; | ||
|
||
@Tag(name = "admin: 관리자", description = "백엔드를 테스트를 위한 API") | ||
@RestController | ||
@RequiredArgsConstructor | ||
@RequestMapping("/api/admin") | ||
@Slf4j | ||
public class AdminController { | ||
//백엔드에서는 구글 로그인이 불가하므로 생성함. | ||
//관리자 로그인 | ||
//관리자 로그아웃 | ||
|
||
private final AdminService adminService; | ||
|
||
/** | ||
* 관리자 회원가입 API | ||
* | ||
* @param request AdminJoinDTO 객체 | ||
* @return 성공 시 JwtDTO를 포함하는 SuccessResponseDTO | ||
*/ | ||
@Operation( | ||
summary = "관리자 회원가입", | ||
description = "관리자 회원가입을 수행합니다. <br>" | ||
+ "관리자의 이메일은 @pomorodo.shop 도메인으로 제한됩니다. <br>" | ||
+ "성공 시에는 JwtDTO를 포함하는 SuccessResponseDTO를 반환합니다.", | ||
requestBody = @io.swagger.v3.oas.annotations.parameters.RequestBody( | ||
description = "AdminJoinDTO 객체", | ||
required = true, | ||
content = @Content(schema = @Schema(implementation = AdminJoinDTO.class)) | ||
) | ||
) | ||
@ApiResponses(value = { | ||
@ApiResponse(responseCode = "201", description = "회원가입 성공"), | ||
@ApiResponse(responseCode = "400", description = "잘못된 요청", | ||
content = @Content(schema = @Schema(implementation = ErrorResponseEntity.class))), | ||
@ApiResponse(responseCode = "409", description = "이미 등록된 사용자", | ||
content = @Content(schema = @Schema(implementation = ErrorResponseEntity.class))) | ||
}) | ||
@PostMapping("/join") | ||
public ResponseEntity<SuccessResponseDTO<JwtDTO>> adminJoin(@RequestBody AdminJoinDTO request) { | ||
log.info("관리자 회원가입 요청: {}", request.getUsername()); | ||
JwtDTO jwtResponse = adminService.adminJoin(request); | ||
SuccessResponseDTO<JwtDTO> response = SuccessResponseDTO.<JwtDTO>builder() | ||
.status(CustomSuccessCode.ADMIN_SIGNUP_SUCCESS.getHttpStatus().value()) | ||
.message(CustomSuccessCode.ADMIN_SIGNUP_SUCCESS.getMessage()) | ||
.data(jwtResponse) | ||
.build(); | ||
log.info("관리자 회원가입 성공: {}", request.getUsername()); | ||
return ResponseEntity.status(HttpStatus.CREATED).body(response); | ||
} | ||
|
||
/** | ||
* 관리자 로그인 API | ||
* | ||
* @param request AdminLoginDTO 객체 | ||
* @return 성공 시 JwtDTO를 포함하는 SuccessResponseDTO | ||
*/ | ||
@Operation( | ||
summary = "관리자 로그인", | ||
description = "관리자 로그인을 수행합니다. <br>" | ||
+ "성공 시에는 JwtDTO를 포함하는 SuccessResponseDTO를 반환합니다.", | ||
requestBody = @io.swagger.v3.oas.annotations.parameters.RequestBody( | ||
description = "AdminLoginDTO 객체", | ||
required = true, | ||
content = @Content(schema = @Schema(implementation = AdminLoginDTO.class)) | ||
) | ||
) | ||
@ApiResponses(value = { | ||
@ApiResponse(responseCode = "200", description = "로그인 성공"), | ||
@ApiResponse(responseCode = "400", description = "잘못된 요청", | ||
content = @Content(schema = @Schema(implementation = ErrorResponseEntity.class))), | ||
@ApiResponse(responseCode = "404", description = "등록되지 않은 사용자", | ||
content = @Content(schema = @Schema(implementation = ErrorResponseEntity.class))), | ||
@ApiResponse(responseCode = "403", description = "관리자 권한이 없음", | ||
content = @Content(schema = @Schema(implementation = ErrorResponseEntity.class))) | ||
}) | ||
@PostMapping("/login") | ||
public ResponseEntity<SuccessResponseDTO<JwtDTO>> adminLogin(@RequestBody AdminLoginDTO request) { | ||
log.info("관리자 로그인 요청: {}", request.getUsername()); | ||
JwtDTO jwtResponse = adminService.adminLogin(request); | ||
SuccessResponseDTO<JwtDTO> response = SuccessResponseDTO.<JwtDTO>builder() | ||
.status(CustomSuccessCode.ADMIN_LOGIN_SUCCESS.getHttpStatus().value()) | ||
.message(CustomSuccessCode.ADMIN_LOGIN_SUCCESS.getMessage()) | ||
.data(jwtResponse) | ||
.build(); | ||
log.info("관리자 로그인 성공: {}", request.getUsername()); | ||
return ResponseEntity.ok(response); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
18 changes: 18 additions & 0 deletions
18
backend/pomoro-do/src/main/java/com/tico/pomoro_do/domain/user/dto/request/AdminJoinDTO.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package com.tico.pomoro_do.domain.user.dto.request; | ||
|
||
import io.swagger.v3.oas.annotations.media.Schema; | ||
import jakarta.validation.constraints.Email; | ||
import jakarta.validation.constraints.NotBlank; | ||
import lombok.Getter; | ||
|
||
@Getter | ||
@Schema(description = "Admin Join Info") | ||
public class AdminJoinDTO { | ||
|
||
@NotBlank(message = "이메일을 입력해주세요.") | ||
private String username; | ||
@NotBlank(message = "닉네임을 입력해주세요.") | ||
private String nickname; | ||
|
||
} |
17 changes: 17 additions & 0 deletions
17
...end/pomoro-do/src/main/java/com/tico/pomoro_do/domain/user/dto/request/AdminLoginDTO.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package com.tico.pomoro_do.domain.user.dto.request; | ||
|
||
import io.swagger.v3.oas.annotations.media.Schema; | ||
import jakarta.validation.constraints.Email; | ||
import jakarta.validation.constraints.NotBlank; | ||
import lombok.Getter; | ||
|
||
@Getter | ||
@Schema(description = "Admin login Info") | ||
public class AdminLoginDTO { | ||
|
||
@NotBlank(message = "이메일을 입력해주세요.") | ||
private String username; | ||
@NotBlank(message = "닉네임을 입력해주세요.") | ||
private String nickname; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
14 changes: 14 additions & 0 deletions
14
backend/pomoro-do/src/main/java/com/tico/pomoro_do/domain/user/service/AdminService.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
package com.tico.pomoro_do.domain.user.service; | ||
|
||
import com.tico.pomoro_do.domain.user.dto.request.AdminJoinDTO; | ||
import com.tico.pomoro_do.domain.user.dto.request.AdminLoginDTO; | ||
import com.tico.pomoro_do.domain.user.dto.response.JwtDTO; | ||
|
||
public interface AdminService { | ||
|
||
//관리자 회원가입 | ||
JwtDTO adminJoin(AdminJoinDTO adminJoinDTO); | ||
|
||
//관리자 로그인 | ||
JwtDTO adminLogin(AdminLoginDTO adminLoginDTO); | ||
} |
135 changes: 135 additions & 0 deletions
135
backend/pomoro-do/src/main/java/com/tico/pomoro_do/domain/user/service/AdminServiceImpl.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
package com.tico.pomoro_do.domain.user.service; | ||
|
||
import com.tico.pomoro_do.domain.user.dto.request.AdminJoinDTO; | ||
import com.tico.pomoro_do.domain.user.dto.request.AdminLoginDTO; | ||
import com.tico.pomoro_do.domain.user.dto.response.JwtDTO; | ||
import com.tico.pomoro_do.domain.user.entity.User; | ||
import com.tico.pomoro_do.domain.user.repository.UserRepository; | ||
import com.tico.pomoro_do.global.common.enums.UserRole; | ||
import com.tico.pomoro_do.global.exception.CustomErrorCode; | ||
import com.tico.pomoro_do.global.exception.CustomException; | ||
import lombok.RequiredArgsConstructor; | ||
import lombok.extern.slf4j.Slf4j; | ||
import org.springframework.stereotype.Service; | ||
import org.springframework.transaction.annotation.Transactional; | ||
|
||
import java.util.Optional; | ||
|
||
@Service | ||
@Transactional(readOnly = true) // (성능 최적화 - 읽기 전용에만 사용) | ||
@RequiredArgsConstructor // 파이널 필드만 가지고 생성사 주입 함수 만듬 (따로 작성할 필요 없다.) | ||
@Slf4j | ||
public class AdminServiceImpl implements AdminService { | ||
|
||
private final UserRepository userRepository; | ||
private final AuthService authService; | ||
|
||
private static final String ADMIN_EMAIL_DOMAIN = "pomorodo.shop"; | ||
|
||
/** | ||
* 관리자 회원가입 처리 | ||
* | ||
* @param adminJoinDTO AdminJoinDTO 객체 | ||
* @return JwtDTO를 포함하는 ResponseEntity | ||
* @throws CustomException 이메일 도메인이 유효하지 않거나 이미 등록된 사용자인 경우 예외 | ||
*/ | ||
@Override | ||
@Transactional | ||
public JwtDTO adminJoin(AdminJoinDTO adminJoinDTO) { | ||
String username = adminJoinDTO.getUsername(); | ||
String nickname = adminJoinDTO.getNickname(); | ||
|
||
//관리자 회원가입 도메인 가져오기 | ||
String domain = getEmailDomain(username); | ||
//관리자 회원가입 이메일 도메인 검증 | ||
validateAdminEmailDomain(domain); | ||
//관리자 이메일 가입 여부 검증 | ||
checkUserExistence(username); | ||
|
||
// 관리자 생성하기 | ||
User admin = authService.createUser(username, nickname, "", UserRole.ADMIN); | ||
|
||
return authService.createJwtTokens(username, String.valueOf(UserRole.ADMIN)); | ||
} | ||
|
||
/** | ||
* 관리자 로그인 처리 | ||
* | ||
* @param adminLoginDTO AdminLoginDTO 객체 | ||
* @return JwtDTO를 포함하는 ResponseEntity | ||
* @throws CustomException 이메일 도메인이 유효하지 않거나 관리자가 아닌 경우 예외 | ||
*/ | ||
@Override | ||
public JwtDTO adminLogin(AdminLoginDTO adminLoginDTO){ | ||
String username = adminLoginDTO.getUsername(); | ||
String nickname = adminLoginDTO.getNickname(); | ||
|
||
//관리자 로그인 도메인 가져오기 | ||
String domain = getEmailDomain(username); | ||
// 관리자 로그인 이메일 도메인 검증 | ||
validateAdminEmailDomain(domain); | ||
// 관리자 로그인 검증 | ||
validateAdminUser(username, nickname); | ||
return authService.createJwtTokens(username, String.valueOf(UserRole.ADMIN)); | ||
} | ||
|
||
/** | ||
* 이메일에서 도메인 부분을 추출 | ||
* | ||
* @param email 이메일 주소 | ||
* @return 이메일 도메인 부분 | ||
*/ | ||
private String getEmailDomain(String email) { | ||
return email.substring(email.indexOf("@") + 1); | ||
} | ||
|
||
/** | ||
* 이메일 도메인 검증 | ||
* | ||
* @param domain 이메일 도메인 | ||
* @throws CustomException 유효하지 않은 이메일 도메인의 경우 예외 발생 | ||
*/ | ||
private void validateAdminEmailDomain(String domain) { | ||
if (!ADMIN_EMAIL_DOMAIN.equals(domain)) { | ||
log.error("유효하지 않은 이메일 도메인: {}", domain); | ||
throw new CustomException(CustomErrorCode.ADMIN_EMAIL_ONLY); | ||
} | ||
} | ||
|
||
/** | ||
* 사용자가 이미 존재하는지 확인 | ||
* | ||
* @param username 사용자 이름 | ||
* @throws CustomException 이미 등록된 사용자인 경우 예외 발생 | ||
*/ | ||
private void checkUserExistence(String username) { | ||
if (userRepository.existsByUsername(username)) { | ||
log.error("이미 등록된 사용자: {}", username); | ||
throw new CustomException(CustomErrorCode.USER_ALREADY_REGISTERED); | ||
} | ||
} | ||
|
||
/** | ||
* 관리자 검증 | ||
* | ||
* @param username 사용자 이름 | ||
* @param nickname 사용자 닉네임 | ||
* @throws CustomException 사용자가 존재하지 않거나 관리자가 아닌 경우 예외 발생 | ||
*/ | ||
private void validateAdminUser(String username, String nickname) { | ||
Optional<User> userData = userRepository.findByUsername(username); | ||
if (userData.isEmpty()) { | ||
log.error("사용자를 찾을 수 없음: {}", username); | ||
throw new CustomException(CustomErrorCode.USER_NOT_FOUND); | ||
} | ||
User admin = userData.get(); | ||
if (!admin.getRole().equals(UserRole.ADMIN)) { | ||
log.error("관리자 권한 없음: {}", username); | ||
throw new CustomException(CustomErrorCode.NOT_AN_ADMIN); | ||
} | ||
if (!admin.getNickname().equals(nickname)) { | ||
log.error("닉네임 불일치: {}", username); | ||
throw new CustomException(CustomErrorCode.ADMIN_LOGIN_FAILED); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.