Skip to content

Commit

Permalink
Merge branch 'main' into feature/137-backend-search-scroll
Browse files Browse the repository at this point in the history
  • Loading branch information
cuteJJong committed Nov 26, 2024
2 parents c25b1c6 + 9b2fac0 commit cb8bd75
Show file tree
Hide file tree
Showing 34 changed files with 644 additions and 130 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,12 @@
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.example.epari.admin.dto.ApprovalRequestDTO;
import com.example.epari.admin.dto.CognitoUserDTO;
import com.example.epari.admin.dto.InstructorApprovalRequestDTO;
import com.example.epari.admin.dto.RejectionRequestDTO;
import com.example.epari.admin.service.AdminUserService;
import com.example.epari.admin.dto.StudentApprovalRequestDTO;
import com.example.epari.admin.service.CognitoService;
import com.example.epari.admin.service.UserApprovalManager;
import com.example.epari.global.event.NotificationEvent;
import com.example.epari.global.event.NotificationType;

Expand All @@ -34,7 +35,7 @@ public class AdminUserManagementController {

private final CognitoService cognitoService;

private final AdminUserService adminUserService;
private final UserApprovalManager userApprovalManager;

private final ApplicationEventPublisher eventPublisher;

Expand All @@ -50,22 +51,19 @@ public ResponseEntity<List<CognitoUserDTO>> getPendingUsers() {
}

/**
* 임시 그룹에 속한 사용자를 승인하는 엔드포인트
* 임시 그룹에 속한 수강생을 승인하는 엔드포인트
* 특정 강의와의 매핑 작업을 수행
*/
@PostMapping("/{userEmail}/approve")
public ResponseEntity<Void> approveUser(
@PostMapping("/{userEmail}/approve/student")
public ResponseEntity<Void> approveStudent(
@PathVariable("userEmail") String email,
@RequestBody ApprovalRequestDTO request
@RequestBody StudentApprovalRequestDTO request
) {
// 1. 백엔드 DB에 승인 상태 업데이트
String courseName = adminUserService.approveUser(email, request);
// 1. DB에 학생 정보를 등록하고 Cognito 사용자 그룹을 'STUDENT'로 변경
String courseName = userApprovalManager.approveStudent(email, request);

// 2. Cognito 그룹 변경
cognitoService.changeUserGroup(request.getUsername(), "STUDENT");

// 3. 이메일 발송
NotificationEvent event = NotificationEvent.of(email, NotificationType.USER_APPROVED)
// 2. 이메일 발송
NotificationEvent event = NotificationEvent.of(email, NotificationType.STUDENT_APPROVED)
.addProperty("name", request.getName())
.addProperty("courseName", courseName);

Expand All @@ -74,6 +72,27 @@ public ResponseEntity<Void> approveUser(
return ResponseEntity.ok().build();
}

/**
* 임시 그룹에 속한 수강생을 승인하는 엔드포인트
* 특정 강의와의 매핑 작업을 수행
*/
@PostMapping("/{userEmail}/approve/instructor")
public ResponseEntity<Void> approveInstructor(
@PathVariable("userEmail") String email,
@RequestBody InstructorApprovalRequestDTO request
) {
// 1. DB에 강사 정보를 등록하고 Cognito 사용자 그룹을 'INSTRUCTOR'로 변경
userApprovalManager.approveInstructor(email, request);

// 2. 이메일 발송
NotificationEvent event = NotificationEvent.of(email, NotificationType.INSTRUCTOR_APPROVED)
.addProperty("name", request.getName());

eventPublisher.publishEvent(event);

return ResponseEntity.ok().build();
}

/**
* 임시 그룹에 속한 사용자를 반려하는 엔드포인트
*/
Expand All @@ -83,7 +102,7 @@ public ResponseEntity<Void> rejectUser(
@RequestBody RejectionRequestDTO request
) {
// 1. Cognito에서 사용자 삭제
cognitoService.deleteUser(email);
cognitoService.deleteUser(request.getUsername());

// 2. 이메일 발송
NotificationEvent event = NotificationEvent.of(email, NotificationType.USER_REJECTED)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.example.epari.admin.dto;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
* 사용자 승인 요청 DTO
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class InstructorApprovalRequestDTO {

private String username; // Cognito username

private String name; // Username

}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ApprovalRequestDTO {
public class StudentApprovalRequestDTO {

private String username; // Cognito username

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.example.epari.admin.exception;

import com.example.epari.global.exception.BusinessBaseException;
import com.example.epari.global.exception.ErrorCode;

/**
* 승인 간 발생하는 예외를 담는 커스텀 예외 클래스
*/
public class ApprovalException extends BusinessBaseException {

public ApprovalException(ErrorCode errorCode) {
super(errorCode);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import com.example.epari.admin.dto.CourseStudentResponseDTO;
import com.example.epari.course.domain.CourseStudent;
import com.example.epari.user.domain.Student;

/**
* 관리자 - 강의와 학생 관련 정보에 대한 데이터베이스 접근을 담당하는 레포지토리 인터페이스
Expand Down Expand Up @@ -35,4 +36,6 @@ public interface AdminCourseStudentRepository extends JpaRepository<CourseStuden

List<CourseStudent> findByCourseId(Long courseId);

void deleteByStudent(Student student);

}
Original file line number Diff line number Diff line change
@@ -1,36 +1,41 @@
package com.example.epari.admin.repository;

import com.example.epari.admin.dto.InstructorSearchResponseDTO;
import com.example.epari.user.domain.Instructor;
import java.util.List;
import java.util.Optional;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

import java.util.List;
import com.example.epari.admin.dto.InstructorSearchResponseDTO;
import com.example.epari.user.domain.Instructor;

/**
* 관리자 - 강사 정보에 대한 데이터베이스 접근을 담당하는 레포지토리 인터페이스
*/
public interface AdminInstructorRepository extends JpaRepository<Instructor, Long> {

/**
* 이메일로 강사를 검색하는 쿼리 메서드
* - 이메일이 비어있으면 전체 조회
* - LIKE 검색으로 부분 일치도 허용
* - 필요한 필드만 DTO로 직접 매핑
*/
@Query("""
SELECT new com.example.epari.admin.dto.InstructorSearchResponseDTO(
i.id,
u.name,
u.email
)
FROM Instructor i
JOIN BaseUser u ON u.id = i.id
WHERE (:email IS NULL OR
:email = '' OR
LOWER(u.email) LIKE LOWER(CONCAT('%', :email, '%')))
ORDER BY u.name ASC
""")
List<InstructorSearchResponseDTO> searchInstructorsWithDTO(@Param("email") String email);
/**
* 이메일로 강사를 검색하는 쿼리 메서드
* - 이메일이 비어있으면 전체 조회
* - LIKE 검색으로 부분 일치도 허용
* - 필요한 필드만 DTO로 직접 매핑
*/
@Query("""
SELECT new com.example.epari.admin.dto.InstructorSearchResponseDTO(
i.id,
u.name,
u.email
)
FROM Instructor i
JOIN BaseUser u ON u.id = i.id
WHERE (:email IS NULL OR
:email = '' OR
LOWER(u.email) LIKE LOWER(CONCAT('%', :email, '%')))
ORDER BY u.name ASC
""")
List<InstructorSearchResponseDTO> searchInstructorsWithDTO(@Param("email") String email);

Optional<Instructor> findByEmail(String email);

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.example.epari.admin.dto.ApprovalRequestDTO;
import com.example.epari.admin.dto.InstructorApprovalRequestDTO;
import com.example.epari.admin.dto.StudentApprovalRequestDTO;
import com.example.epari.admin.exception.CourseNotFoundException;
import com.example.epari.admin.repository.AdminCourseStudentRepository;
import com.example.epari.admin.repository.AdminInstructorRepository;
import com.example.epari.course.domain.Course;
import com.example.epari.course.domain.CourseStudent;
import com.example.epari.course.repository.CourseRepository;
import com.example.epari.course.repository.CourseStudentRepository;
import com.example.epari.user.domain.Instructor;
import com.example.epari.user.domain.Student;
import com.example.epari.user.repository.StudentRepository;

Expand All @@ -19,24 +22,24 @@
*/
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class AdminUserService {

private final StudentRepository studentRepository;

private final CourseRepository courseRepository;

private final CourseStudentRepository courseStudentRepository;
private final AdminCourseStudentRepository courseStudentRepository;

private final AdminInstructorRepository instructorRepository;

/**
* 사용자 승인 처리 메서드
* 사용자 저장 및 과목 매핑 수행
* 수강생 승인 처리 메서드
* 수강생 저장 및 과목 매핑 수행
*/
@Transactional
public String approveUser(String email, ApprovalRequestDTO request) {
public String approveStudent(String email, StudentApprovalRequestDTO request) {
// 1. 사용자 저장
Student student = Student
.createStudent(email, request.getName());
Student student = Student.createStudent(email, request.getName());

studentRepository.save(student);

Expand All @@ -49,4 +52,37 @@ public String approveUser(String email, ApprovalRequestDTO request) {
return course.getName();
}

/**
* 강사 승인 처리 메서드
*/
@Transactional
public void approveInstructor(String email, InstructorApprovalRequestDTO request) {
// 1. 사용자 저장
instructorRepository.save(Instructor.createInstructor(email, request.getName()));
}

/**
* 수강생의 정보를 롤백하는 메서드
* 1. 매핑된 강의 목록 조회 후 제거
* 2. 수강생 정보 제거
*/
@Transactional
public void rollbackStudentApproval(String email) {
studentRepository.findByEmail(email)
.ifPresent(student -> {
courseStudentRepository.deleteByStudent(student);
studentRepository.delete(student);
});
}

/**
* 강사의 정보를 롤백하는 메서드
* 강사 정보 제거
*/
@Transactional
public void rollbackInstructorApproval(String email) {
instructorRepository.findByEmail(email)
.ifPresent(instructorRepository::delete);
}

}
49 changes: 29 additions & 20 deletions src/main/java/com/example/epari/admin/service/CognitoService.java
Original file line number Diff line number Diff line change
Expand Up @@ -67,47 +67,56 @@ public List<CognitoUserDTO> getUsersByGroup(String groupName) {
/**
* 사용자 그룹을 변경
*/
public void changeUserGroup(String username, String groupName) {
// 1. 현재 그룹에서 제거
AdminRemoveUserFromGroupRequest removeRequest = AdminRemoveUserFromGroupRequest.builder()
.userPoolId(userPoolId)
.username(username)
.groupName("PENDING_ROLES")
.build();
public void changeUserGroup(String username, String newGroup) {
try {
// 기존 그룹에서 제거
AdminRemoveUserFromGroupRequest removeRequest = AdminRemoveUserFromGroupRequest.builder()
.userPoolId(userPoolId)
.username(username)
.groupName("PENDING_ROLES")
.build();

cognitoClient.adminRemoveUserFromGroup(removeRequest);
cognitoClient.adminRemoveUserFromGroup(removeRequest);

// 2. 새 그룹에 추가
AdminAddUserToGroupRequest addRequest = AdminAddUserToGroupRequest.builder()
.userPoolId(userPoolId)
.username(username)
.groupName(groupName)
.build();
// 새 그룹에 추가
AdminAddUserToGroupRequest addRequest = AdminAddUserToGroupRequest.builder()
.userPoolId(userPoolId)
.username(username)
.groupName(newGroup)
.build();

cognitoClient.adminAddUserToGroup(addRequest);

cognitoClient.adminAddUserToGroup(addRequest);
} catch (UserNotFoundException e) {
log.error("User not found in Cognito: {}", username, e);
throw new CognitoException(ErrorCode.COGNITO_USER_NOT_FOUND);
} catch (CognitoIdentityProviderException e) {
log.error("Failed to change user group in Cognito: {}", username, e);
throw new CognitoException(ErrorCode.COGNITO_UPDATE_FAILED);
}
}

/**
* 사용자 풀에서 특정 사용자를 삭제하는 메서드
*/
@Transactional
public void deleteUser(String email) {
public void deleteUser(String username) {
try {
// AdminDeleteUserRequest 생성
AdminDeleteUserRequest deleteRequest = AdminDeleteUserRequest.builder()
.userPoolId(userPoolId)
.username(email)
.username(username)
.build();

// Cognito API를 통해 사용자 삭제 요청
cognitoClient.adminDeleteUser(deleteRequest);

log.info("Successfully deleted user from Cognito: {}", email);
log.info("Successfully deleted user from Cognito: {}", username);
} catch (UserNotFoundException ex) {
log.error("User not found in Cognito: {}", email, ex);
log.error("User not found in Cognito: {}", username, ex);
throw new CognitoException(ErrorCode.COGNITO_USER_NOT_FOUND);
} catch (CognitoIdentityProviderException ex) {
log.error("Failed to delete user from Cognito: {}", email, ex);
log.error("Failed to delete user from Cognito: {}", username, ex);
throw new CognitoException(ErrorCode.COGNITO_USER_DELETE_ERROR);
}
}
Expand Down
Loading

0 comments on commit cb8bd75

Please sign in to comment.