From 473e43bbf4d60065ad73ccc28d844ad08924a2e0 Mon Sep 17 00:00:00 2001 From: llddang Date: Thu, 29 Aug 2024 16:41:34 +0900 Subject: [PATCH 01/18] =?UTF-8?q?feat:=20=EA=B4=80=EB=A6=AC=EC=9E=90=20?= =?UTF-8?q?=EA=B3=84=EC=A0=95=EB=93=A4=EC=9D=98=20id=20=EC=9E=90=EB=8F=99?= =?UTF-8?q?=20=EC=83=9D=EC=84=B1=EB=90=98=EB=8F=84=EB=A1=9D=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #170 --- backend/src/main/resources/schema.sql | 2 +- backend/src/main/resources/test-data.sql | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/backend/src/main/resources/schema.sql b/backend/src/main/resources/schema.sql index 108f49c4..91f3297b 100644 --- a/backend/src/main/resources/schema.sql +++ b/backend/src/main/resources/schema.sql @@ -32,7 +32,7 @@ create table student_member create table faculty_member ( - id bigint primary key, + id bigint auto_increment primary key, member_id bigint not null, created_at datetime(6) not null default current_timestamp(6) ); diff --git a/backend/src/main/resources/test-data.sql b/backend/src/main/resources/test-data.sql index a2dcc63d..44b13d71 100644 --- a/backend/src/main/resources/test-data.sql +++ b/backend/src/main/resources/test-data.sql @@ -1,24 +1,24 @@ ## member(student) insert into member (email, name, password, phone_number, is_deleted) -values ('songsy405@pusan.ac.kr', '송세연', '$2a$10$YyiOL/E5WjKrZPkB6eQSK.PwZtAO.z3JimFbq/Ky3u3rFf3XTGrWK', '010-0000-0000', +values ('admin@pusan.ac.kr', '관리자', '$2a$10$YyiOL/E5WjKrZPkB6eQSK.PwZtAO.z3JimFbq/Ky3u3rFf3XTGrWK', '01000000000', false); -insert into student_member (id, member_id, major_id, minor_id, double_major_id, career, career_detail) -values (202055558, 1, 1, null, null, 'EMPLOYMENT_COMPANY', 'IT 기업 개발자'); +insert into faculty_member (member_id) +values (1); insert into member (email, name, password, phone_number, is_deleted) -values ( 'ddang@pusan.ac.kr', '이다은', '$2a$10$YyiOL/E5WjKrZPkB6eQSK.PwZtAO.z3JimFbq/Ky3u3rFf3XTGrWK', '010-0000-0000' - , false); +values ('songsy405@pusan.ac.kr', '송세연', '$2a$10$YyiOL/E5WjKrZPkB6eQSK.PwZtAO.z3JimFbq/Ky3u3rFf3XTGrWK', '01000000000', + false); insert into student_member (id, member_id, major_id, minor_id, double_major_id, career, career_detail) -values (202055555, 2, 1, null, null, 'GRADUATE_SCHOOL', 'IT 기업 개발자'); +values (202055558, 2, 1, null, null, 'EMPLOYMENT_COMPANY', 'IT 기업 개발자'); insert into member (email, name, password, phone_number, is_deleted) -values ( 'hayun@pusan.ac.kr', '김하윤', '$2a$10$YyiOL/E5WjKrZPkB6eQSK.PwZtAO.z3JimFbq/Ky3u3rFf3XTGrWK', '010-0000-0000' +values ( 'ddang@pusan.ac.kr', '이다은', '$2a$10$YyiOL/E5WjKrZPkB6eQSK.PwZtAO.z3JimFbq/Ky3u3rFf3XTGrWK', '01000000000' , false); -insert into faculty_member (id, member_id) -values (1, 3); +insert into student_member (id, member_id, major_id, minor_id, double_major_id, career, career_detail) +values (202055555, 3, 1, null, null, 'GRADUATE_SCHOOL', 'IT 기업 개발자'); ## milestone histories INSERT INTO sw_css.milestone_history (id, milestone_id, student_id, description, file_url, status, reject_reason, count, From c7e3e94f262aa1d4e0766ee4137b7190f6522857 Mon Sep 17 00:00:00 2001 From: llddang Date: Thu, 29 Aug 2024 16:41:52 +0900 Subject: [PATCH 02/18] =?UTF-8?q?feat:=20super=20admin=20=EC=96=B4?= =?UTF-8?q?=EB=85=B8=ED=85=8C=EC=9D=B4=EC=85=98=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #170 --- .../JwtToken/SuperAdminArgumentResolver.java | 60 +++++++++++++++++++ .../sw_css/utils/annotation/SuperAdmin.java | 12 ++++ 2 files changed, 72 insertions(+) create mode 100644 backend/src/main/java/sw_css/utils/JwtToken/SuperAdminArgumentResolver.java create mode 100644 backend/src/main/java/sw_css/utils/annotation/SuperAdmin.java diff --git a/backend/src/main/java/sw_css/utils/JwtToken/SuperAdminArgumentResolver.java b/backend/src/main/java/sw_css/utils/JwtToken/SuperAdminArgumentResolver.java new file mode 100644 index 00000000..31dc9905 --- /dev/null +++ b/backend/src/main/java/sw_css/utils/JwtToken/SuperAdminArgumentResolver.java @@ -0,0 +1,60 @@ +package sw_css.utils.JwtToken; + +import jakarta.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; +import org.springframework.core.MethodParameter; +import org.springframework.stereotype.Component; +import org.springframework.web.bind.support.WebDataBinderFactory; +import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.method.support.ModelAndViewContainer; +import sw_css.member.domain.Member; +import sw_css.member.domain.repository.FacultyMemberRepository; +import sw_css.member.domain.repository.MemberRepository; +import sw_css.member.exception.MemberException; +import sw_css.member.exception.MemberExceptionType; +import sw_css.utils.JwtToken.exception.JwtTokenException; +import sw_css.utils.JwtToken.exception.JwtTokenExceptionType; +import sw_css.utils.annotation.Admin; + +@Component +@RequiredArgsConstructor +public class SuperAdminArgumentResolver implements HandlerMethodArgumentResolver { + private final MemberRepository memberRepository; + private final FacultyMemberRepository facultyMemberRepository; + private final JwtTokenProvider jwtTokenProvider; + + @Override + public boolean supportsParameter(MethodParameter parameter) { + return parameter.hasParameterAnnotation(Admin.class); + } + + @Override + public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, + NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { + HttpServletRequest httpServletRequest = webRequest.getNativeRequest(HttpServletRequest.class); + + if (httpServletRequest == null) { + throw new JwtTokenException(JwtTokenExceptionType.JWT_TOKEN_INACCESSIBLE); + } + + String token = httpServletRequest.getHeader("Authorization"); + if (token == null || token.trim().isEmpty()) { + throw new JwtTokenException(JwtTokenExceptionType.JWT_TOKEN_EMPTY); + } + + jwtTokenProvider.validateToken(token); + + long userId = jwtTokenProvider.getUserId(token); + Member member = memberRepository.findById(userId) + .orElseThrow(() -> new MemberException(MemberExceptionType.MEMBER_NOT_FOUND)); + + if (member.getId() != 1) { + throw new JwtTokenException(JwtTokenExceptionType.JWT_TOKEN_EMPTY); + } + + return facultyMemberRepository.findByMemberId(member.getId()) + .orElseThrow(() -> new JwtTokenException(JwtTokenExceptionType.JWT_TOKEN_INACCESSIBLE)); + } +} + diff --git a/backend/src/main/java/sw_css/utils/annotation/SuperAdmin.java b/backend/src/main/java/sw_css/utils/annotation/SuperAdmin.java new file mode 100644 index 00000000..c13cfb3a --- /dev/null +++ b/backend/src/main/java/sw_css/utils/annotation/SuperAdmin.java @@ -0,0 +1,12 @@ +package sw_css.utils.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.PARAMETER) +@Retention(RetentionPolicy.RUNTIME) +public @interface SuperAdmin { + boolean required() default true; +} From 2b61b0cc26952d6c91fb5d06c315cc5f7d03f58d Mon Sep 17 00:00:00 2001 From: llddang Date: Thu, 29 Aug 2024 16:42:14 +0900 Subject: [PATCH 03/18] =?UTF-8?q?feat:=20=EA=B4=80=EB=A6=AC=EC=9E=90=20?= =?UTF-8?q?=EA=B3=84=EC=A0=95=EC=9D=98=20=EC=96=B4=EB=85=B8=ED=85=8C?= =?UTF-8?q?=EC=9D=B4=EC=85=98=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #170 --- .../utils/JwtToken/AdminArgumentResolver.java | 55 +++++++++++++++++++ .../java/sw_css/utils/annotation/Admin.java | 12 ++++ 2 files changed, 67 insertions(+) create mode 100644 backend/src/main/java/sw_css/utils/JwtToken/AdminArgumentResolver.java create mode 100644 backend/src/main/java/sw_css/utils/annotation/Admin.java diff --git a/backend/src/main/java/sw_css/utils/JwtToken/AdminArgumentResolver.java b/backend/src/main/java/sw_css/utils/JwtToken/AdminArgumentResolver.java new file mode 100644 index 00000000..d8b8d909 --- /dev/null +++ b/backend/src/main/java/sw_css/utils/JwtToken/AdminArgumentResolver.java @@ -0,0 +1,55 @@ +package sw_css.utils.JwtToken; + +import jakarta.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; +import org.springframework.core.MethodParameter; +import org.springframework.stereotype.Component; +import org.springframework.web.bind.support.WebDataBinderFactory; +import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.method.support.ModelAndViewContainer; +import sw_css.member.domain.Member; +import sw_css.member.domain.repository.FacultyMemberRepository; +import sw_css.member.domain.repository.MemberRepository; +import sw_css.member.exception.MemberException; +import sw_css.member.exception.MemberExceptionType; +import sw_css.utils.JwtToken.exception.JwtTokenException; +import sw_css.utils.JwtToken.exception.JwtTokenExceptionType; +import sw_css.utils.annotation.Admin; + +@Component +@RequiredArgsConstructor +public class AdminArgumentResolver implements HandlerMethodArgumentResolver { + private final MemberRepository memberRepository; + private final FacultyMemberRepository facultyMemberRepository; + private final JwtTokenProvider jwtTokenProvider; + + @Override + public boolean supportsParameter(MethodParameter parameter) { + return parameter.hasParameterAnnotation(Admin.class); + } + + @Override + public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, + NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { + HttpServletRequest httpServletRequest = webRequest.getNativeRequest(HttpServletRequest.class); + + if (httpServletRequest == null) { + throw new JwtTokenException(JwtTokenExceptionType.JWT_TOKEN_INACCESSIBLE); + } + + String token = httpServletRequest.getHeader("Authorization"); + if (token == null || token.trim().isEmpty()) { + throw new JwtTokenException(JwtTokenExceptionType.JWT_TOKEN_EMPTY); + } + + jwtTokenProvider.validateToken(token); + + long userId = jwtTokenProvider.getUserId(token); + Member member = memberRepository.findById(userId) + .orElseThrow(() -> new MemberException(MemberExceptionType.MEMBER_NOT_FOUND)); + + return facultyMemberRepository.findByMemberId(member.getId()) + .orElseThrow(() -> new JwtTokenException(JwtTokenExceptionType.JWT_TOKEN_INACCESSIBLE)); + } +} diff --git a/backend/src/main/java/sw_css/utils/annotation/Admin.java b/backend/src/main/java/sw_css/utils/annotation/Admin.java new file mode 100644 index 00000000..0d149928 --- /dev/null +++ b/backend/src/main/java/sw_css/utils/annotation/Admin.java @@ -0,0 +1,12 @@ +package sw_css.utils.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.PARAMETER) +@Retention(RetentionPolicy.RUNTIME) +public @interface Admin { + boolean required() default true; +} From e4148feb7ce23d7bb633ef2e9f65be37fdd7ac85 Mon Sep 17 00:00:00 2001 From: llddang Date: Thu, 29 Aug 2024 16:43:18 +0900 Subject: [PATCH 04/18] =?UTF-8?q?feat:=20=EA=B4=80=EB=A6=AC=EC=9E=90=20?= =?UTF-8?q?=EB=8B=A8=EC=9D=BC=20=EB=93=B1=EB=A1=9D=20api=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #170 --- .../admin/auth/api/RegisterController.java | 40 ++++++++++++++++++ .../application/AuthAdminQueryService.java | 42 +++++++++++++++++++ .../dto/request/RegisterRequest.java | 30 +++++++++++++ 3 files changed, 112 insertions(+) create mode 100644 backend/src/main/java/sw_css/admin/auth/api/RegisterController.java create mode 100644 backend/src/main/java/sw_css/admin/auth/application/AuthAdminQueryService.java create mode 100644 backend/src/main/java/sw_css/admin/auth/application/dto/request/RegisterRequest.java diff --git a/backend/src/main/java/sw_css/admin/auth/api/RegisterController.java b/backend/src/main/java/sw_css/admin/auth/api/RegisterController.java new file mode 100644 index 00000000..65f57a1e --- /dev/null +++ b/backend/src/main/java/sw_css/admin/auth/api/RegisterController.java @@ -0,0 +1,40 @@ +package sw_css.admin.auth.api; + +import jakarta.validation.Valid; +import java.net.URI; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; +import sw_css.admin.auth.application.AuthAdminQueryService; +import sw_css.admin.auth.application.dto.request.RegisterRequest; +import sw_css.member.domain.FacultyMember; +import sw_css.utils.annotation.Admin; + +@Validated +@RequestMapping("/admin/auth") +@RestController +@RequiredArgsConstructor +public class RegisterController { + + private final AuthAdminQueryService authAdminQueryService; + + // TODO: 단일 등록 + @PostMapping + public ResponseEntity registerFaculty( + @Admin FacultyMember facultyMember, + @RequestBody @Valid RegisterRequest request) { + Long memberId = authAdminQueryService.registerFaculty(request); + return ResponseEntity.created(URI.create("/members/" + memberId)).build(); + } + + // TODO: excel을 이용한 다중 등록 + + // TODO: root 권한자만 교직원 삭제 + +} diff --git a/backend/src/main/java/sw_css/admin/auth/application/AuthAdminQueryService.java b/backend/src/main/java/sw_css/admin/auth/application/AuthAdminQueryService.java new file mode 100644 index 00000000..c5e09283 --- /dev/null +++ b/backend/src/main/java/sw_css/admin/auth/application/AuthAdminQueryService.java @@ -0,0 +1,42 @@ +package sw_css.admin.auth.application; + +import java.util.ArrayList; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.apache.commons.io.FilenameUtils; +import org.apache.poi.ss.usermodel.Workbook; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; +import sw_css.admin.auth.application.dto.request.RegisterRequest; +import sw_css.admin.auth.domain.AdminRegisterExcelData; +import sw_css.admin.auth.exception.AdminAuthException; +import sw_css.admin.auth.exception.AdminAuthExceptionType; +import sw_css.auth.application.AuthCheckDuplicateService; +import sw_css.auth.application.dto.response.CheckDuplicateResponse; +import sw_css.member.domain.repository.FacultyMemberRepository; +import sw_css.member.domain.repository.MemberRepository; + +@Service +@RequiredArgsConstructor +@Transactional +public class AuthAdminQueryService { + + private final MemberRepository memberRepository; + private final FacultyMemberRepository facultyMemberRepository; + private final AuthCheckDuplicateService authCheckDuplicateService; + + public Long registerFaculty(RegisterRequest request) { + isDuplicateEmail(request.email()); + + final long memberId = memberRepository.save(request.toMember()).getId(); + facultyMemberRepository.save(request.toFacultyMember(memberId)); + + return memberId; + } + + private void isDuplicateEmail(String email) { + CheckDuplicateResponse.from(authCheckDuplicateService.isDuplicateEmail(email)); + } + +} diff --git a/backend/src/main/java/sw_css/admin/auth/application/dto/request/RegisterRequest.java b/backend/src/main/java/sw_css/admin/auth/application/dto/request/RegisterRequest.java new file mode 100644 index 00000000..f7d23ad5 --- /dev/null +++ b/backend/src/main/java/sw_css/admin/auth/application/dto/request/RegisterRequest.java @@ -0,0 +1,30 @@ +package sw_css.admin.auth.application.dto.request; + +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.Pattern; +import sw_css.member.domain.FacultyMember; +import sw_css.member.domain.Member; +import sw_css.member.domain.embedded.EmailAddress; +import sw_css.member.domain.embedded.Password; +import sw_css.member.domain.embedded.RealName; + +public record RegisterRequest( + @Email(message = "이메일 형식을 확인해주세요.") + @Pattern(regexp = EmailAddress.EMAIL_ADDRESS_REGEX, message = EmailAddress.EMAIL_ADDRESS_INVALID) + String email, + @Pattern(regexp = RealName.NAME_REGEX, message = RealName.NAME_INVALID) + String name) { + + public Member toMember() { + return new Member(email, name, Password.encode("asdf"), "01000000000", false); + } + + public Member toMember(Long memberId) { + return new Member(memberId, email, name, Password.encode("asdf"), "01000000000", false); + } + + public FacultyMember toFacultyMember(Long memberId) { + final Member member = toMember(memberId); + return new FacultyMember(null, member); + } +} From a88af17a54711487ffe4fd508f68182bae45c562 Mon Sep 17 00:00:00 2001 From: llddang Date: Wed, 4 Sep 2024 17:38:50 +0900 Subject: [PATCH 05/18] =?UTF-8?q?feat:=20Admin=20&=20SuperAdmin=20annotati?= =?UTF-8?q?on=20=EB=93=B1=EB=A1=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #170 --- backend/src/main/java/sw_css/config/WebConfig.java | 6 ++++++ .../main/java/sw_css/utils/annotation/JwtAuthorization.java | 1 - 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/backend/src/main/java/sw_css/config/WebConfig.java b/backend/src/main/java/sw_css/config/WebConfig.java index af7488b3..24d10212 100644 --- a/backend/src/main/java/sw_css/config/WebConfig.java +++ b/backend/src/main/java/sw_css/config/WebConfig.java @@ -5,16 +5,22 @@ import org.springframework.context.annotation.Configuration; import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import sw_css.utils.JwtToken.AdminArgumentResolver; import sw_css.utils.JwtToken.JwtAuthorizationArgumentResolver; +import sw_css.utils.JwtToken.SuperAdminArgumentResolver; @Configuration @RequiredArgsConstructor public class WebConfig implements WebMvcConfigurer { private final JwtAuthorizationArgumentResolver jwtAuthorizationArgumentResolver; + private final AdminArgumentResolver adminArgumentResolver; + private final SuperAdminArgumentResolver superAdminArgumentResolver; @Override public void addArgumentResolvers(List resolvers) { resolvers.add(jwtAuthorizationArgumentResolver); + resolvers.add(adminArgumentResolver); + resolvers.add(superAdminArgumentResolver); } } diff --git a/backend/src/main/java/sw_css/utils/annotation/JwtAuthorization.java b/backend/src/main/java/sw_css/utils/annotation/JwtAuthorization.java index 7efe054d..6a886fb5 100644 --- a/backend/src/main/java/sw_css/utils/annotation/JwtAuthorization.java +++ b/backend/src/main/java/sw_css/utils/annotation/JwtAuthorization.java @@ -5,7 +5,6 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; - @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) public @interface JwtAuthorization { From 3f8f58f440f6544812033d75612494472bd5a016 Mon Sep 17 00:00:00 2001 From: llddang Date: Wed, 4 Sep 2024 17:41:03 +0900 Subject: [PATCH 06/18] =?UTF-8?q?feat:=20config=20=ED=8F=B4=EB=8D=94=20?= =?UTF-8?q?=EC=B5=9C=EC=8B=A0=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #170 --- backend/src/main/resources/config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/main/resources/config b/backend/src/main/resources/config index f8b6ccd0..f34a016e 160000 --- a/backend/src/main/resources/config +++ b/backend/src/main/resources/config @@ -1 +1 @@ -Subproject commit f8b6ccd0232837bbda578eb6f953ec27ef55d8fb +Subproject commit f34a016ecd1faba0a310178b3d98780f908c0d28 From 4b56650282d6bbacb1aa95b37057edc08eeb5a6b Mon Sep 17 00:00:00 2001 From: llddang Date: Wed, 4 Sep 2024 17:41:48 +0900 Subject: [PATCH 07/18] =?UTF-8?q?feat:=20=ED=8C=8C=EC=9D=BC=EC=9D=84=20?= =?UTF-8?q?=EC=9D=B4=EC=9A=A9=ED=95=9C=20=EA=B5=90=EC=A7=81=EC=9B=90=20?= =?UTF-8?q?=EB=93=B1=EB=A1=9D=20api=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #170 --- .../admin/auth/api/RegisterController.java | 7 ++ .../application/AuthAdminQueryService.java | 112 ++++++++++++++++-- .../dto/request/RegisterRequest.java | 13 +- .../auth/exception/AdminAuthException.java | 19 +++ .../exception/AdminAuthExceptionType.java | 33 ++++++ .../sw_css/member/domain/FacultyMember.java | 3 + .../java/sw_css/member/domain/Member.java | 1 + 7 files changed, 173 insertions(+), 15 deletions(-) create mode 100644 backend/src/main/java/sw_css/admin/auth/exception/AdminAuthException.java create mode 100644 backend/src/main/java/sw_css/admin/auth/exception/AdminAuthExceptionType.java diff --git a/backend/src/main/java/sw_css/admin/auth/api/RegisterController.java b/backend/src/main/java/sw_css/admin/auth/api/RegisterController.java index 65f57a1e..03fae6ce 100644 --- a/backend/src/main/java/sw_css/admin/auth/api/RegisterController.java +++ b/backend/src/main/java/sw_css/admin/auth/api/RegisterController.java @@ -34,6 +34,13 @@ public ResponseEntity registerFaculty( } // TODO: excel을 이용한 다중 등록 + @PostMapping("/files") + public ResponseEntity registerFaculties( + @Admin FacultyMember facultyMember, + @RequestPart(value = "file") final MultipartFile file) { + authAdminQueryService.registerFaculties(file); + return ResponseEntity.created(URI.create("/admin/faculties")).build(); + } // TODO: root 권한자만 교직원 삭제 diff --git a/backend/src/main/java/sw_css/admin/auth/application/AuthAdminQueryService.java b/backend/src/main/java/sw_css/admin/auth/application/AuthAdminQueryService.java index c5e09283..f4165bc5 100644 --- a/backend/src/main/java/sw_css/admin/auth/application/AuthAdminQueryService.java +++ b/backend/src/main/java/sw_css/admin/auth/application/AuthAdminQueryService.java @@ -1,42 +1,138 @@ package sw_css.admin.auth.application; +import java.io.IOException; import java.util.ArrayList; import java.util.List; +import java.util.regex.Pattern; +import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import org.apache.commons.io.FilenameUtils; +import org.apache.poi.hssf.usermodel.HSSFWorkbook; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; import sw_css.admin.auth.application.dto.request.RegisterRequest; -import sw_css.admin.auth.domain.AdminRegisterExcelData; import sw_css.admin.auth.exception.AdminAuthException; import sw_css.admin.auth.exception.AdminAuthExceptionType; import sw_css.auth.application.AuthCheckDuplicateService; -import sw_css.auth.application.dto.response.CheckDuplicateResponse; +import sw_css.member.domain.FacultyMember; +import sw_css.member.domain.Member; +import sw_css.member.domain.embedded.EmailAddress; +import sw_css.member.domain.embedded.Password; +import sw_css.member.domain.embedded.RealName; import sw_css.member.domain.repository.FacultyMemberRepository; import sw_css.member.domain.repository.MemberRepository; @Service @RequiredArgsConstructor -@Transactional public class AuthAdminQueryService { private final MemberRepository memberRepository; private final FacultyMemberRepository facultyMemberRepository; private final AuthCheckDuplicateService authCheckDuplicateService; + @Value("${password.admin}") + private String password; + + @Transactional public Long registerFaculty(RegisterRequest request) { - isDuplicateEmail(request.email()); + checkIsDuplicateEmail(request.email()); + + final String encodedPassword = Password.encode(password); - final long memberId = memberRepository.save(request.toMember()).getId(); - facultyMemberRepository.save(request.toFacultyMember(memberId)); + final long memberId = memberRepository.save(request.toMember(encodedPassword)).getId(); + facultyMemberRepository.save(request.toFacultyMember(memberId, encodedPassword)); return memberId; } - private void isDuplicateEmail(String email) { - CheckDuplicateResponse.from(authCheckDuplicateService.isDuplicateEmail(email)); + public void registerFaculties(MultipartFile file) { + final String extension = FilenameUtils.getExtension(file.getOriginalFilename()); + if (extension == null || !(extension.equals("xlsx") || extension.equals("xls"))) { + throw new AdminAuthException(AdminAuthExceptionType.NO_MATCH_EXTENSION); + } + + final String encodedPassword = Password.encode(password); + final List failedData = new ArrayList<>(); + + final Workbook workbook = generateWorkbook(file, extension); + + final Sheet worksheet = workbook.getSheetAt(0); + for (int i = 1; i < worksheet.getPhysicalNumberOfRows(); i++) { + final Row row = worksheet.getRow(i); + + final String email = row.getCell(0).getStringCellValue(); + final String name = row.getCell(1).getStringCellValue(); + + if (isInvalidInput(email, name)) { + failedData.add(i + 1); + continue; + } + + saveFaculty(email, name, encodedPassword); + } + + checkFailedData(failedData); + } + + private void checkIsDuplicateEmail(String email) { + if (authCheckDuplicateService.isDuplicateEmail(email)) { + throw new AdminAuthException(AdminAuthExceptionType.MEMBER_EMAIL_DUPLICATE); + } + } + + private Workbook generateWorkbook(final MultipartFile file, String extension) { + try { + if (extension.equals("xlsx")) { + return new XSSFWorkbook(file.getInputStream()); + } + return new HSSFWorkbook(file.getInputStream()); + } catch (final IOException exception) { + throw new AdminAuthException(AdminAuthExceptionType.CANNOT_OPEN_FILE); + } + } + + private void saveFaculty(final String email, final String name, final String password) { + Member member = new Member(email, name, password, "01000000000", false); + + final long memberId = memberRepository.save(member).getId(); + member.setId(memberId); + + FacultyMember facultyMember = new FacultyMember(null, member); + facultyMemberRepository.save(facultyMember); + } + + private boolean isInvalidInput(final String email, final String name) { + if (Pattern.matches(EmailAddress.EMAIL_ADDRESS_REGEX, email) && + Pattern.matches(RealName.NAME_REGEX, name) && + !isDuplicateEmail(email)) { + return false; + } + return true; + } + + + private boolean isDuplicateEmail(String email) { + return authCheckDuplicateService.isDuplicateEmail(email); + } + + private void checkFailedData(final List failedData) { + if (failedData.isEmpty()) { + return; + } + + String ids = failedData.stream() + .map(Object::toString) + .collect(Collectors.joining(", ")); + AdminAuthExceptionType exceptionType = AdminAuthExceptionType.FAILED_REGISTER_FACULTY; + exceptionType.setErrorMessage(ids + "번째 줄의 관리자를 등록하는데 실패했습니다."); + + throw new AdminAuthException(exceptionType); } } diff --git a/backend/src/main/java/sw_css/admin/auth/application/dto/request/RegisterRequest.java b/backend/src/main/java/sw_css/admin/auth/application/dto/request/RegisterRequest.java index f7d23ad5..1e36be71 100644 --- a/backend/src/main/java/sw_css/admin/auth/application/dto/request/RegisterRequest.java +++ b/backend/src/main/java/sw_css/admin/auth/application/dto/request/RegisterRequest.java @@ -5,7 +5,6 @@ import sw_css.member.domain.FacultyMember; import sw_css.member.domain.Member; import sw_css.member.domain.embedded.EmailAddress; -import sw_css.member.domain.embedded.Password; import sw_css.member.domain.embedded.RealName; public record RegisterRequest( @@ -15,16 +14,16 @@ public record RegisterRequest( @Pattern(regexp = RealName.NAME_REGEX, message = RealName.NAME_INVALID) String name) { - public Member toMember() { - return new Member(email, name, Password.encode("asdf"), "01000000000", false); + public Member toMember(String password) { + return new Member(email, name, password, "01000000000", false); } - public Member toMember(Long memberId) { - return new Member(memberId, email, name, Password.encode("asdf"), "01000000000", false); + public Member toMember(Long memberId, String password) { + return new Member(memberId, email, name, password, "01000000000", false); } - public FacultyMember toFacultyMember(Long memberId) { - final Member member = toMember(memberId); + public FacultyMember toFacultyMember(Long memberId, String password) { + final Member member = toMember(memberId, password); return new FacultyMember(null, member); } } diff --git a/backend/src/main/java/sw_css/admin/auth/exception/AdminAuthException.java b/backend/src/main/java/sw_css/admin/auth/exception/AdminAuthException.java new file mode 100644 index 00000000..781ae3cc --- /dev/null +++ b/backend/src/main/java/sw_css/admin/auth/exception/AdminAuthException.java @@ -0,0 +1,19 @@ +package sw_css.admin.auth.exception; + + +import sw_css.base.BaseException; +import sw_css.base.BaseExceptionType; + +public class AdminAuthException extends BaseException { + private final AdminAuthExceptionType adminAuthExceptionType; + + public AdminAuthException(final AdminAuthExceptionType exceptionType) { + super(exceptionType.errorMessage()); + this.adminAuthExceptionType = exceptionType; + } + + @Override + public BaseExceptionType exceptionType() { + return adminAuthExceptionType; + } +} diff --git a/backend/src/main/java/sw_css/admin/auth/exception/AdminAuthExceptionType.java b/backend/src/main/java/sw_css/admin/auth/exception/AdminAuthExceptionType.java new file mode 100644 index 00000000..a42767e3 --- /dev/null +++ b/backend/src/main/java/sw_css/admin/auth/exception/AdminAuthExceptionType.java @@ -0,0 +1,33 @@ +package sw_css.admin.auth.exception; + +import lombok.Setter; +import org.springframework.http.HttpStatus; +import sw_css.base.BaseExceptionType; + +public enum AdminAuthExceptionType implements BaseExceptionType { + NO_MATCH_EXTENSION(HttpStatus.BAD_REQUEST, "파일 확장자가 올바르지 않습니다."), + CANNOT_OPEN_FILE(HttpStatus.BAD_REQUEST, "파일을 열 수 없습니다."), + FAILED_REGISTER_FACULTY(HttpStatus.BAD_REQUEST, ""), + MEMBER_EMAIL_DUPLICATE(HttpStatus.CONFLICT, "이메일이 중복됩니다."), + ; + + private final HttpStatus httpStatus; + @Setter + private String errorMessage; + + AdminAuthExceptionType(final HttpStatus httpStatus, final String errorMessage) { + this.httpStatus = httpStatus; + this.errorMessage = errorMessage; + } + + @Override + public HttpStatus httpStatus() { + return httpStatus; + } + + @Override + public String errorMessage() { + return errorMessage; + } + +} diff --git a/backend/src/main/java/sw_css/member/domain/FacultyMember.java b/backend/src/main/java/sw_css/member/domain/FacultyMember.java index cbadc4d3..79dbbc62 100644 --- a/backend/src/main/java/sw_css/member/domain/FacultyMember.java +++ b/backend/src/main/java/sw_css/member/domain/FacultyMember.java @@ -2,6 +2,8 @@ import jakarta.persistence.Entity; import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; @@ -17,6 +19,7 @@ @NoArgsConstructor(access = AccessLevel.PROTECTED) public class FacultyMember extends BaseEntity { @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @ManyToOne(fetch = FetchType.LAZY) diff --git a/backend/src/main/java/sw_css/member/domain/Member.java b/backend/src/main/java/sw_css/member/domain/Member.java index c60dcd39..7b9ceab9 100644 --- a/backend/src/main/java/sw_css/member/domain/Member.java +++ b/backend/src/main/java/sw_css/member/domain/Member.java @@ -19,6 +19,7 @@ @NoArgsConstructor(access = AccessLevel.PROTECTED) public class Member extends BaseEntity { @Id + @Setter(AccessLevel.PUBLIC) @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; From 6f061b20678548ff5e96435d05e6a80bf79baa68 Mon Sep 17 00:00:00 2001 From: llddang Date: Fri, 6 Sep 2024 14:21:50 +0900 Subject: [PATCH 08/18] =?UTF-8?q?feat:=20=EA=B5=90=EC=A7=81=EC=9B=90=20?= =?UTF-8?q?=ED=83=88=ED=87=B4=20api=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #170 --- .../admin/auth/api/RegisterController.java | 14 ++++++++++-- .../application/AuthAdminQueryService.java | 22 +++++++++++++++++-- .../dto/request/DeleteFacultyRequest.java | 6 +++++ ...quest.java => RegisterFacultyRequest.java} | 2 +- .../exception/AdminAuthExceptionType.java | 1 + .../java/sw_css/member/domain/Member.java | 1 + 6 files changed, 41 insertions(+), 5 deletions(-) create mode 100644 backend/src/main/java/sw_css/admin/auth/application/dto/request/DeleteFacultyRequest.java rename backend/src/main/java/sw_css/admin/auth/application/dto/request/{RegisterRequest.java => RegisterFacultyRequest.java} (96%) diff --git a/backend/src/main/java/sw_css/admin/auth/api/RegisterController.java b/backend/src/main/java/sw_css/admin/auth/api/RegisterController.java index 03fae6ce..fdcd4422 100644 --- a/backend/src/main/java/sw_css/admin/auth/api/RegisterController.java +++ b/backend/src/main/java/sw_css/admin/auth/api/RegisterController.java @@ -5,6 +5,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -12,9 +13,11 @@ import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import sw_css.admin.auth.application.AuthAdminQueryService; -import sw_css.admin.auth.application.dto.request.RegisterRequest; +import sw_css.admin.auth.application.dto.request.DeleteFacultyRequest; +import sw_css.admin.auth.application.dto.request.RegisterFacultyRequest; import sw_css.member.domain.FacultyMember; import sw_css.utils.annotation.Admin; +import sw_css.utils.annotation.SuperAdmin; @Validated @RequestMapping("/admin/auth") @@ -28,7 +31,7 @@ public class RegisterController { @PostMapping public ResponseEntity registerFaculty( @Admin FacultyMember facultyMember, - @RequestBody @Valid RegisterRequest request) { + @RequestBody @Valid RegisterFacultyRequest request) { Long memberId = authAdminQueryService.registerFaculty(request); return ResponseEntity.created(URI.create("/members/" + memberId)).build(); } @@ -43,5 +46,12 @@ public ResponseEntity registerFaculties( } // TODO: root 권한자만 교직원 삭제 + @DeleteMapping() + public ResponseEntity deleteFaculty( + @SuperAdmin FacultyMember facultyMember, + @RequestBody @Valid DeleteFacultyRequest request) { + authAdminQueryService.deleteFaculty(request.member_id()); + return ResponseEntity.noContent().build(); + } } diff --git a/backend/src/main/java/sw_css/admin/auth/application/AuthAdminQueryService.java b/backend/src/main/java/sw_css/admin/auth/application/AuthAdminQueryService.java index f4165bc5..3ad16af3 100644 --- a/backend/src/main/java/sw_css/admin/auth/application/AuthAdminQueryService.java +++ b/backend/src/main/java/sw_css/admin/auth/application/AuthAdminQueryService.java @@ -16,7 +16,7 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; -import sw_css.admin.auth.application.dto.request.RegisterRequest; +import sw_css.admin.auth.application.dto.request.RegisterFacultyRequest; import sw_css.admin.auth.exception.AdminAuthException; import sw_css.admin.auth.exception.AdminAuthExceptionType; import sw_css.auth.application.AuthCheckDuplicateService; @@ -40,7 +40,7 @@ public class AuthAdminQueryService { private String password; @Transactional - public Long registerFaculty(RegisterRequest request) { + public Long registerFaculty(RegisterFacultyRequest request) { checkIsDuplicateEmail(request.email()); final String encodedPassword = Password.encode(password); @@ -80,6 +80,16 @@ public void registerFaculties(MultipartFile file) { checkFailedData(failedData); } + @Transactional + public void deleteFaculty(Long memberId) { + Member member = memberRepository.findById(memberId) + .orElseThrow(() -> new AdminAuthException(AdminAuthExceptionType.MEMBER_NOT_FOUND)); + checkIsMemberDeleted(member); + + member.setDeleted(true); + memberRepository.save(member); + } + private void checkIsDuplicateEmail(String email) { if (authCheckDuplicateService.isDuplicateEmail(email)) { throw new AdminAuthException(AdminAuthExceptionType.MEMBER_EMAIL_DUPLICATE); @@ -135,4 +145,12 @@ private void checkFailedData(final List failedData) { throw new AdminAuthException(exceptionType); } + private void checkIsMemberDeleted(final Member member) { + if (!member.isDeleted()) { + return; + } + throw new AdminAuthException(AdminAuthExceptionType.MEMBER_NOT_FOUND); + + } + } diff --git a/backend/src/main/java/sw_css/admin/auth/application/dto/request/DeleteFacultyRequest.java b/backend/src/main/java/sw_css/admin/auth/application/dto/request/DeleteFacultyRequest.java new file mode 100644 index 00000000..8927bace --- /dev/null +++ b/backend/src/main/java/sw_css/admin/auth/application/dto/request/DeleteFacultyRequest.java @@ -0,0 +1,6 @@ +package sw_css.admin.auth.application.dto.request; + +public record DeleteFacultyRequest( + Long member_id +) { +} diff --git a/backend/src/main/java/sw_css/admin/auth/application/dto/request/RegisterRequest.java b/backend/src/main/java/sw_css/admin/auth/application/dto/request/RegisterFacultyRequest.java similarity index 96% rename from backend/src/main/java/sw_css/admin/auth/application/dto/request/RegisterRequest.java rename to backend/src/main/java/sw_css/admin/auth/application/dto/request/RegisterFacultyRequest.java index 1e36be71..21125da4 100644 --- a/backend/src/main/java/sw_css/admin/auth/application/dto/request/RegisterRequest.java +++ b/backend/src/main/java/sw_css/admin/auth/application/dto/request/RegisterFacultyRequest.java @@ -7,7 +7,7 @@ import sw_css.member.domain.embedded.EmailAddress; import sw_css.member.domain.embedded.RealName; -public record RegisterRequest( +public record RegisterFacultyRequest( @Email(message = "이메일 형식을 확인해주세요.") @Pattern(regexp = EmailAddress.EMAIL_ADDRESS_REGEX, message = EmailAddress.EMAIL_ADDRESS_INVALID) String email, diff --git a/backend/src/main/java/sw_css/admin/auth/exception/AdminAuthExceptionType.java b/backend/src/main/java/sw_css/admin/auth/exception/AdminAuthExceptionType.java index a42767e3..2ae597c1 100644 --- a/backend/src/main/java/sw_css/admin/auth/exception/AdminAuthExceptionType.java +++ b/backend/src/main/java/sw_css/admin/auth/exception/AdminAuthExceptionType.java @@ -9,6 +9,7 @@ public enum AdminAuthExceptionType implements BaseExceptionType { CANNOT_OPEN_FILE(HttpStatus.BAD_REQUEST, "파일을 열 수 없습니다."), FAILED_REGISTER_FACULTY(HttpStatus.BAD_REQUEST, ""), MEMBER_EMAIL_DUPLICATE(HttpStatus.CONFLICT, "이메일이 중복됩니다."), + MEMBER_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 회원을 찾을 수 없습니다."), ; private final HttpStatus httpStatus; diff --git a/backend/src/main/java/sw_css/member/domain/Member.java b/backend/src/main/java/sw_css/member/domain/Member.java index 7b9ceab9..6ad000b3 100644 --- a/backend/src/main/java/sw_css/member/domain/Member.java +++ b/backend/src/main/java/sw_css/member/domain/Member.java @@ -36,6 +36,7 @@ public class Member extends BaseEntity { @Column(nullable = false) private String phoneNumber; + @Setter(AccessLevel.PUBLIC) @Column(nullable = false) private boolean isDeleted; From 1c93a56ab32043febabc6cf5613ec9e84f9cd214 Mon Sep 17 00:00:00 2001 From: llddang Date: Fri, 6 Sep 2024 14:27:55 +0900 Subject: [PATCH 09/18] =?UTF-8?q?feat:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20?= =?UTF-8?q?=ED=95=A0=20=EB=95=8C=20deleted=20=EB=90=9C=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=EC=9E=90=EC=9D=B8=EC=A7=80=20=EA=B2=80=EC=A6=9D?= =?UTF-8?q?=ED=95=98=EB=8A=94=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #179 --- .../auth/application/AuthSignInService.java | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/backend/src/main/java/sw_css/auth/application/AuthSignInService.java b/backend/src/main/java/sw_css/auth/application/AuthSignInService.java index 7297aa56..ea0a4bb8 100644 --- a/backend/src/main/java/sw_css/auth/application/AuthSignInService.java +++ b/backend/src/main/java/sw_css/auth/application/AuthSignInService.java @@ -37,21 +37,18 @@ public SignInResponse signIn(String email, String rawPassword) { Member member = memberRepository.findByEmail(email) .orElseThrow(() -> new AuthException(AuthExceptionType.MEMBER_EMAIL_NOT_FOUND)); - if (member.isWrongPassword(rawPassword)) { - throw new AuthException(AuthExceptionType.MEMBER_WRONG_ID_OR_PASSWORD); - } + checkIsMemberDeleted(member); + checkIsValidPassword(member, rawPassword); Object memberDetail = loadMemberDetail(member); if (memberDetail instanceof StudentMember studentMember) { String role = Role.ROLE_MEMBER.toString(); - String accessToken = jwtTokenProvider.createToken(member.getId(), role); return SignInResponse.of(member, studentMember.getId(), role, false, accessToken); - + } else if (memberDetail instanceof FacultyMember facultyMember) { String role = Role.ROLE_ADMIN.toString(); - String accessToken = jwtTokenProvider.createToken(member.getId(), role); return SignInResponse.of(member, facultyMember.getId(), role, true, accessToken); @@ -116,4 +113,18 @@ private static Boolean isAvailableSpecialCharacter(int i) { String availableSpecialCharacters = "!@#%^&*"; return availableSpecialCharacters.indexOf(c) != -1; } + + private void checkIsMemberDeleted(Member member) { + if (!member.isDeleted()) { + return; + } + throw new AuthException(AuthExceptionType.MEMBER_NOT_FOUND); + } + + private void checkIsValidPassword(Member member, String password) { + if (!member.isWrongPassword(password)) { + return; + } + throw new AuthException(AuthExceptionType.MEMBER_WRONG_ID_OR_PASSWORD); + } } From 31d470e06a7fecacfc47047c49fcac0a132a0432 Mon Sep 17 00:00:00 2001 From: llddang Date: Fri, 6 Sep 2024 14:45:42 +0900 Subject: [PATCH 10/18] =?UTF-8?q?feat:=20=EC=9D=B4=EB=A9=94=EC=9D=BC=20?= =?UTF-8?q?=EB=B0=8F=20=EC=A0=84=ED=99=94=EB=B2=88=ED=98=B8=20=EC=A4=91?= =?UTF-8?q?=EB=B3=B5=ED=99=95=EC=9D=B8=20api=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #170 --- backend/src/docs/asciidoc/index.adoc | 28 ----------- .../sw_css/auth/api/SignUpController.java | 12 ----- .../AuthCheckDuplicateService.java | 4 -- .../auth/application/AuthSignUpService.java | 8 ---- .../restdocs/docs/SignUpApiDocsTest.java | 48 ------------------- 5 files changed, 100 deletions(-) diff --git a/backend/src/docs/asciidoc/index.adoc b/backend/src/docs/asciidoc/index.adoc index 9e7ff50b..2758deaa 100644 --- a/backend/src/docs/asciidoc/index.adoc +++ b/backend/src/docs/asciidoc/index.adoc @@ -261,20 +261,6 @@ include::{snippets}/auth-send-auth-code/http-response.adoc[] .Response Body include::{snippets}/auth-send-auth-code/response-fields.adoc[] -=== `GET`: 이메일 중복 확인 - -.HTTP Request -include::{snippets}/auth-check-duplicate-email/http-request.adoc[] - -.Path Parameters -include::{snippets}/auth-check-duplicate-email/query-parameters.adoc[] - -.HTTP Response -include::{snippets}/auth-check-duplicate-email/http-response.adoc[] - -.Response Body -include::{snippets}/auth-check-duplicate-email/response-fields.adoc[] - === `GET`: 학번 중복 확인 .HTTP Request @@ -289,20 +275,6 @@ include::{snippets}/auth-check-duplicate-student-id/http-response.adoc[] .Response Body include::{snippets}/auth-check-duplicate-student-id/response-fields.adoc[] -=== `GET`: 전화번호 중복 확인 - -.HTTP Request -include::{snippets}/auth-check-duplicate-phone-number/http-request.adoc[] - -.Path Parameters -include::{snippets}/auth-check-duplicate-phone-number/query-parameters.adoc[] - -.HTTP Response -include::{snippets}/auth-check-duplicate-phone-number/http-response.adoc[] - -.Response Body -include::{snippets}/auth-check-duplicate-phone-number/response-fields.adoc[] - === `POST`: 로그인 .HTTP Request diff --git a/backend/src/main/java/sw_css/auth/api/SignUpController.java b/backend/src/main/java/sw_css/auth/api/SignUpController.java index 5a1ebbd6..a8994117 100644 --- a/backend/src/main/java/sw_css/auth/api/SignUpController.java +++ b/backend/src/main/java/sw_css/auth/api/SignUpController.java @@ -40,21 +40,9 @@ public ResponseEntity sendAuthCode(@RequestBody @Valid Sen return ResponseEntity.ok(authEmailService.emailAuth(request.email())); } - @GetMapping("/exists/email") - public ResponseEntity checkDuplicateEmail( - @RequestParam(value = "email") @NotBlank final String email) { - return ResponseEntity.ok(authSignUpService.isDuplicateEmail(email)); - } - @GetMapping("/exists/student-id") public ResponseEntity checkDuplicateStudentId( @RequestParam(value = "student_id") @NotBlank final String studentId) { return ResponseEntity.ok(authSignUpService.isDuplicateStudentId(studentId)); } - - @GetMapping("/exists/phone-number") - public ResponseEntity checkDuplicatePhoneNumber( - @RequestParam(value = "phone_number") @NotBlank final String phoneNumber) { - return ResponseEntity.ok(authSignUpService.isDuplicatePhoneNumber(phoneNumber)); - } } diff --git a/backend/src/main/java/sw_css/auth/application/AuthCheckDuplicateService.java b/backend/src/main/java/sw_css/auth/application/AuthCheckDuplicateService.java index 1457af5a..e126cb93 100644 --- a/backend/src/main/java/sw_css/auth/application/AuthCheckDuplicateService.java +++ b/backend/src/main/java/sw_css/auth/application/AuthCheckDuplicateService.java @@ -21,8 +21,4 @@ public boolean isDuplicateStudentID(String studentIdStr) { Long studentId = Long.parseLong(studentIdStr); return studentMemberRepository.existsById(studentId); } - - public boolean isDuplicatePhoneNumber(String phoneNumber) { - return memberRepository.existsByPhoneNumber(phoneNumber); - } } diff --git a/backend/src/main/java/sw_css/auth/application/AuthSignUpService.java b/backend/src/main/java/sw_css/auth/application/AuthSignUpService.java index 45a4e57f..1c9e2313 100644 --- a/backend/src/main/java/sw_css/auth/application/AuthSignUpService.java +++ b/backend/src/main/java/sw_css/auth/application/AuthSignUpService.java @@ -49,18 +49,10 @@ public long signUp(SignUpRequest request) { return memberId; } - public CheckDuplicateResponse isDuplicateEmail(String email) { - return CheckDuplicateResponse.from(authCheckDuplicateService.isDuplicateEmail(email)); - } - public CheckDuplicateResponse isDuplicateStudentId(String studentId) { return CheckDuplicateResponse.from(authCheckDuplicateService.isDuplicateStudentID(studentId)); } - public CheckDuplicateResponse isDuplicatePhoneNumber(String phoneNumber) { - return CheckDuplicateResponse.from(authCheckDuplicateService.isDuplicatePhoneNumber(phoneNumber)); - } - private void checkIsDuplicateEmail(String email) { if (authCheckDuplicateService.isDuplicateEmail(email)) { throw new AuthException(AuthExceptionType.MEMBER_EMAIL_DUPLICATE); diff --git a/backend/src/test/java/sw_css/restdocs/docs/SignUpApiDocsTest.java b/backend/src/test/java/sw_css/restdocs/docs/SignUpApiDocsTest.java index e255db4f..cc44dbb2 100644 --- a/backend/src/test/java/sw_css/restdocs/docs/SignUpApiDocsTest.java +++ b/backend/src/test/java/sw_css/restdocs/docs/SignUpApiDocsTest.java @@ -99,30 +99,6 @@ public void sendAuthCodeMail() throws Exception { .andDo(document("auth-send-auth-code", requestFieldsSnippet, responseFieldsSnippet)); } - @Test - @DisplayName("[성공] 중복하는 이메일인지 확인할 수 있다.") - public void checkDuplicateEmail() throws Exception { - // given - final QueryParametersSnippet queryParameters = queryParameters( - parameterWithName("email").description("부산대학교 이메일") - ); - final ResponseFieldsSnippet responseBodySnippet = responseFields( - fieldWithPath("is_duplicate").type(JsonFieldType.BOOLEAN).description("중복 여부")); - - final String email = "ddang@pusan.ac.kr"; - final boolean isDuplicate = false; - final CheckDuplicateResponse response = new CheckDuplicateResponse(isDuplicate); - - //when - when(authSignUpService.isDuplicateEmail(email)).thenReturn(response); - - //then - mockMvc.perform(RestDocumentationRequestBuilders.get("/sign-up/exists/email") - .param("email", email)) - .andExpect(status().isOk()) - .andDo(document("auth-check-duplicate-email", queryParameters, responseBodySnippet)); - } - @Test @DisplayName("[성공] 중복하는 학번인지 확인할 수 있다.") public void checkDuplicateStudentId() throws Exception { @@ -146,28 +122,4 @@ public void checkDuplicateStudentId() throws Exception { .andExpect(status().isOk()) .andDo(document("auth-check-duplicate-student-id", queryParameters, responseBodySnippet)); } - - @Test - @DisplayName("[성공] 중복하는 전화번호인지 확인할 수 있다.") - public void checkDuplicatePhoneNumber() throws Exception { - // given - final QueryParametersSnippet queryParameters = queryParameters( - parameterWithName("phone_number").description("전화번호") - ); - final ResponseFieldsSnippet responseBodySnippet = responseFields( - fieldWithPath("is_duplicate").type(JsonFieldType.BOOLEAN).description("중복 여부")); - - final String phoneNumber = "01012341234"; - final boolean isDuplicate = false; - final CheckDuplicateResponse response = new CheckDuplicateResponse(isDuplicate); - - //when - when(authSignUpService.isDuplicatePhoneNumber(phoneNumber)).thenReturn(response); - - //then - mockMvc.perform(RestDocumentationRequestBuilders.get("/sign-up/exists/phone-number") - .param("phone_number", phoneNumber)) - .andExpect(status().isOk()) - .andDo(document("auth-check-duplicate-phone-number", queryParameters, responseBodySnippet)); - } } From 0a1625ed2e6348dbc513824cee329934db33de7b Mon Sep 17 00:00:00 2001 From: llddang Date: Fri, 6 Sep 2024 15:20:34 +0900 Subject: [PATCH 11/18] =?UTF-8?q?feat:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=EB=B0=8F=20=EC=9D=B4=EB=A9=94=EC=9D=BC=20=EC=9D=B8?= =?UTF-8?q?=EC=A6=9D=EC=8B=9C=20=ED=83=88=ED=87=B4=ED=95=9C=20=ED=9A=8C?= =?UTF-8?q?=EC=9B=90=EC=9D=B8=EC=A7=80=20=ED=99=95=EC=9D=B8=ED=95=98?= =?UTF-8?q?=EB=8A=94=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #170 --- .../auth/application/AuthEmailService.java | 9 ++- .../auth/application/AuthSignUpService.java | 59 ++++++++++++++++--- .../sw_css/member/domain/StudentMember.java | 2 + .../domain/repository/MemberRepository.java | 2 - 4 files changed, 60 insertions(+), 12 deletions(-) diff --git a/backend/src/main/java/sw_css/auth/application/AuthEmailService.java b/backend/src/main/java/sw_css/auth/application/AuthEmailService.java index 336daf02..7f7d0770 100644 --- a/backend/src/main/java/sw_css/auth/application/AuthEmailService.java +++ b/backend/src/main/java/sw_css/auth/application/AuthEmailService.java @@ -10,6 +10,8 @@ import sw_css.auth.domain.repository.EmailAuthRedisRepository; import sw_css.auth.exception.AuthException; import sw_css.auth.exception.AuthExceptionType; +import sw_css.member.domain.Member; +import sw_css.member.domain.repository.MemberRepository; import sw_css.utils.MailUtil; @Service @@ -23,6 +25,7 @@ public class AuthEmailService { private final MailUtil mailUtil; private final EmailAuthRedisRepository emailAuthRedisRepository; private final AuthCheckDuplicateService authCheckDuplicateService; + private final MemberRepository memberRepository; public SendAuthCodeResponse emailAuth(String email) { checkIsDuplicateEmail(email); @@ -59,8 +62,10 @@ private void sendAuthCode(String email, String authCode) { } private void checkIsDuplicateEmail(String email) { - if (authCheckDuplicateService.isDuplicateEmail(email)) { - throw new AuthException(AuthExceptionType.MEMBER_EMAIL_DUPLICATE); + Member member = memberRepository.findByEmail(email).orElse(null); + if (member == null || member.isDeleted()) { + return; } + throw new AuthException(AuthExceptionType.MEMBER_EMAIL_DUPLICATE); } } diff --git a/backend/src/main/java/sw_css/auth/application/AuthSignUpService.java b/backend/src/main/java/sw_css/auth/application/AuthSignUpService.java index 1c9e2313..8d2445a6 100644 --- a/backend/src/main/java/sw_css/auth/application/AuthSignUpService.java +++ b/backend/src/main/java/sw_css/auth/application/AuthSignUpService.java @@ -11,6 +11,8 @@ import sw_css.auth.exception.AuthExceptionType; import sw_css.major.domain.Major; import sw_css.major.domain.repository.MajorRepository; +import sw_css.member.domain.CareerType; +import sw_css.member.domain.Member; import sw_css.member.domain.StudentMember; import sw_css.member.domain.repository.MemberRepository; import sw_css.member.domain.repository.StudentMemberRepository; @@ -27,11 +29,19 @@ public class AuthSignUpService { private final EmailAuthRedisRepository emailAuthRedisRepository; public long signUp(SignUpRequest request) { + Member member = memberRepository.findByEmail(request.email()).orElse(null); + if (member != null) { + if (!member.isDeleted()) { + throw new AuthException(AuthExceptionType.MEMBER_EMAIL_DUPLICATE); + } + + return saveExistMember(request, member); + } + checkIsDuplicateEmail(request.email()); checkIsDuplicateStudentId(request.student_id()); - String actualAuthCode = loadActualAuthCode(request.email()); - checkAuthCodeMatch(request.auth_code(), actualAuthCode); + checkIsValidAuthCode(request.email(), request.auth_code()); Major major = majorRepository.findById(request.major_id()) .orElseThrow(() -> new AuthException(AuthExceptionType.MAJOR_NOT_EXIST)); @@ -53,6 +63,40 @@ public CheckDuplicateResponse isDuplicateStudentId(String studentId) { return CheckDuplicateResponse.from(authCheckDuplicateService.isDuplicateStudentID(studentId)); } + private long saveExistMember(SignUpRequest request, Member oldMember) { + checkIsValidAuthCode(request.email(), request.auth_code()); + + Major major = majorRepository.findById(request.major_id()) + .orElseThrow(() -> new AuthException(AuthExceptionType.MAJOR_NOT_EXIST)); + Major minor = request.minor_id() == null ? null : majorRepository.findById(request.minor_id()) + .orElseThrow(() -> new AuthException(AuthExceptionType.MAJOR_NOT_EXIST)); + Major doubleMinor = + request.double_major_id() == null ? null : majorRepository.findById(request.double_major_id()) + .orElseThrow(() -> new AuthException(AuthExceptionType.MAJOR_NOT_EXIST)); + + Member newMember = request.toMember(); + newMember.setId(oldMember.getId()); + memberRepository.save(newMember); + + StudentMember oldStudentMember = studentMemberRepository.findByMemberId(newMember.getId()).orElse(null); + if (oldStudentMember == null) { + final StudentMember newStudentMember = request.toStudentMember(newMember.getId(), major, minor, + doubleMinor); + studentMemberRepository.save(newStudentMember); + } else { + final CareerType careerType = CareerType.valueOf(request.career()); + + oldStudentMember.setMajor(major); + oldStudentMember.setMinor(minor); + oldStudentMember.setDoubleMajor(doubleMinor); + oldStudentMember.setCareer(careerType); + oldStudentMember.setCareerDetail(request.career_detail()); + studentMemberRepository.save(oldStudentMember); + } + + return newMember.getId(); + } + private void checkIsDuplicateEmail(String email) { if (authCheckDuplicateService.isDuplicateEmail(email)) { throw new AuthException(AuthExceptionType.MEMBER_EMAIL_DUPLICATE); @@ -65,12 +109,6 @@ private void checkIsDuplicateStudentId(String studentId) { } } - private void checkIsDuplicatePhoneNumber(String phoneNumber) { - if (authCheckDuplicateService.isDuplicatePhoneNumber(phoneNumber)) { - throw new AuthException(AuthExceptionType.MEMBER_PHONE_NUMBER_DUPLICATE); - } - } - private void checkAuthCodeMatch(String requestAuthCode, String actualAuthCode) { if (!actualAuthCode.equals(requestAuthCode)) { throw new AuthException(AuthExceptionType.AUTH_CODE_MISMATCH); @@ -82,4 +120,9 @@ private String loadActualAuthCode(@Email String email) { .orElseThrow(() -> new AuthException(AuthExceptionType.AUTH_CODE_EXPIRED)) .getAuthCode(); } + + private void checkIsValidAuthCode(String email, String authCode) { + String actualAuthCode = loadActualAuthCode(email); + checkAuthCodeMatch(authCode, actualAuthCode); + } } diff --git a/backend/src/main/java/sw_css/member/domain/StudentMember.java b/backend/src/main/java/sw_css/member/domain/StudentMember.java index 5ce14c84..6a5122dc 100644 --- a/backend/src/main/java/sw_css/member/domain/StudentMember.java +++ b/backend/src/main/java/sw_css/member/domain/StudentMember.java @@ -12,10 +12,12 @@ import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; +import lombok.Setter; import sw_css.base.BaseEntity; import sw_css.major.domain.Major; @Entity +@Setter(AccessLevel.PUBLIC) @Getter @AllArgsConstructor @NoArgsConstructor(access = AccessLevel.PROTECTED) diff --git a/backend/src/main/java/sw_css/member/domain/repository/MemberRepository.java b/backend/src/main/java/sw_css/member/domain/repository/MemberRepository.java index 702e7dd2..cd603ac2 100644 --- a/backend/src/main/java/sw_css/member/domain/repository/MemberRepository.java +++ b/backend/src/main/java/sw_css/member/domain/repository/MemberRepository.java @@ -7,7 +7,5 @@ public interface MemberRepository extends JpaRepository { boolean existsByEmail(String email); - boolean existsByPhoneNumber(String phoneNumber); - Optional findByEmail(String email); } From d3ba960f117c45288e1fd3de212e9c01490adb1f Mon Sep 17 00:00:00 2001 From: llddang Date: Fri, 6 Sep 2024 15:29:08 +0900 Subject: [PATCH 12/18] =?UTF-8?q?feat:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20=ED=9A=8C=EC=9B=90=EC=9D=84=20=EC=B0=BE?= =?UTF-8?q?=EC=A7=80=20=EB=AA=BB=ED=95=A0=20=EB=95=8C=EC=9D=98=20=EB=AC=B8?= =?UTF-8?q?=EA=B5=AC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #170 --- .../java/sw_css/auth/application/AuthSignInService.java | 8 ++++---- .../java/sw_css/auth/exception/AuthExceptionType.java | 1 + 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/backend/src/main/java/sw_css/auth/application/AuthSignInService.java b/backend/src/main/java/sw_css/auth/application/AuthSignInService.java index ea0a4bb8..421202e7 100644 --- a/backend/src/main/java/sw_css/auth/application/AuthSignInService.java +++ b/backend/src/main/java/sw_css/auth/application/AuthSignInService.java @@ -35,7 +35,7 @@ public class AuthSignInService { public SignInResponse signIn(String email, String rawPassword) { Member member = memberRepository.findByEmail(email) - .orElseThrow(() -> new AuthException(AuthExceptionType.MEMBER_EMAIL_NOT_FOUND)); + .orElseThrow(() -> new AuthException(AuthExceptionType.MEMBER_NOT_REGISTER)); checkIsMemberDeleted(member); checkIsValidPassword(member, rawPassword); @@ -46,7 +46,7 @@ public SignInResponse signIn(String email, String rawPassword) { String accessToken = jwtTokenProvider.createToken(member.getId(), role); return SignInResponse.of(member, studentMember.getId(), role, false, accessToken); - + } else if (memberDetail instanceof FacultyMember facultyMember) { String role = Role.ROLE_ADMIN.toString(); String accessToken = jwtTokenProvider.createToken(member.getId(), role); @@ -54,7 +54,7 @@ public SignInResponse signIn(String email, String rawPassword) { return SignInResponse.of(member, facultyMember.getId(), role, true, accessToken); } - throw new AuthException(AuthExceptionType.MEMBER_EMAIL_NOT_FOUND); + throw new AuthException(AuthExceptionType.MEMBER_NOT_REGISTER); } public void resetPassword(String email, String name) { @@ -118,7 +118,7 @@ private void checkIsMemberDeleted(Member member) { if (!member.isDeleted()) { return; } - throw new AuthException(AuthExceptionType.MEMBER_NOT_FOUND); + throw new AuthException(AuthExceptionType.MEMBER_NOT_REGISTER); } private void checkIsValidPassword(Member member, String password) { diff --git a/backend/src/main/java/sw_css/auth/exception/AuthExceptionType.java b/backend/src/main/java/sw_css/auth/exception/AuthExceptionType.java index 91813c38..84175248 100644 --- a/backend/src/main/java/sw_css/auth/exception/AuthExceptionType.java +++ b/backend/src/main/java/sw_css/auth/exception/AuthExceptionType.java @@ -13,6 +13,7 @@ public enum AuthExceptionType implements BaseExceptionType { MEMBER_EMAIL_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 회원의 이메일을 찾을 수 없습니다."), MEMBER_WRONG_ID_OR_PASSWORD(HttpStatus.BAD_REQUEST, "아이디 혹은 비밀번호가 잘못되었습니다."), MEMBER_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 회원의 정보를 찾을 수 없습니다."), + MEMBER_NOT_REGISTER(HttpStatus.BAD_REQUEST, "등록되지 않은 회원입니다."), MEMBER_NOT_MATCH_EMAIL_AND_NAME(HttpStatus.BAD_REQUEST, "회원의 이메일과 이름이 일치하지 않습니다."); private final HttpStatus httpStatus; From ca78cc01eaf5c627ea754da1a80ab0292885794d Mon Sep 17 00:00:00 2001 From: llddang Date: Fri, 6 Sep 2024 15:55:14 +0900 Subject: [PATCH 13/18] =?UTF-8?q?feat:=20=EA=B5=90=EC=A7=81=EC=9B=90=20?= =?UTF-8?q?=EB=8B=A8=EC=9D=BC/=EB=8B=A4=EC=A4=91=20=EB=93=B1=EB=A1=9D=20?= =?UTF-8?q?=EC=8B=9C=20deleted=20=EB=90=9C=20=ED=9A=8C=EC=9B=90=EC=9D=B8?= =?UTF-8?q?=EC=A7=80=20=EA=B2=80=EC=A6=9D=ED=95=98=EB=8A=94=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #170 --- .../admin/auth/api/RegisterController.java | 3 -- .../application/AuthAdminQueryService.java | 39 ++++++++++++++----- 2 files changed, 29 insertions(+), 13 deletions(-) diff --git a/backend/src/main/java/sw_css/admin/auth/api/RegisterController.java b/backend/src/main/java/sw_css/admin/auth/api/RegisterController.java index fdcd4422..79c99cd0 100644 --- a/backend/src/main/java/sw_css/admin/auth/api/RegisterController.java +++ b/backend/src/main/java/sw_css/admin/auth/api/RegisterController.java @@ -27,7 +27,6 @@ public class RegisterController { private final AuthAdminQueryService authAdminQueryService; - // TODO: 단일 등록 @PostMapping public ResponseEntity registerFaculty( @Admin FacultyMember facultyMember, @@ -36,7 +35,6 @@ public ResponseEntity registerFaculty( return ResponseEntity.created(URI.create("/members/" + memberId)).build(); } - // TODO: excel을 이용한 다중 등록 @PostMapping("/files") public ResponseEntity registerFaculties( @Admin FacultyMember facultyMember, @@ -45,7 +43,6 @@ public ResponseEntity registerFaculties( return ResponseEntity.created(URI.create("/admin/faculties")).build(); } - // TODO: root 권한자만 교직원 삭제 @DeleteMapping() public ResponseEntity deleteFaculty( @SuperAdmin FacultyMember facultyMember, diff --git a/backend/src/main/java/sw_css/admin/auth/application/AuthAdminQueryService.java b/backend/src/main/java/sw_css/admin/auth/application/AuthAdminQueryService.java index 3ad16af3..a6a9dcd6 100644 --- a/backend/src/main/java/sw_css/admin/auth/application/AuthAdminQueryService.java +++ b/backend/src/main/java/sw_css/admin/auth/application/AuthAdminQueryService.java @@ -3,6 +3,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.List; +import java.util.Objects; import java.util.regex.Pattern; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; @@ -41,10 +42,15 @@ public class AuthAdminQueryService { @Transactional public Long registerFaculty(RegisterFacultyRequest request) { - checkIsDuplicateEmail(request.email()); - final String encodedPassword = Password.encode(password); + Member member = memberRepository.findByEmail(request.email()).orElse(null); + if (member != null && !member.isDeleted()) { + throw new AdminAuthException(AdminAuthExceptionType.MEMBER_EMAIL_DUPLICATE); + } else if (member != null) { + return saveExistFaculty(request.email(), request.name(), member, encodedPassword); + } + final long memberId = memberRepository.save(request.toMember(encodedPassword)).getId(); facultyMemberRepository.save(request.toFacultyMember(memberId, encodedPassword)); @@ -74,7 +80,14 @@ public void registerFaculties(MultipartFile file) { continue; } - saveFaculty(email, name, encodedPassword); + Member member = memberRepository.findByEmail(email).orElse(null); + if (member == null) { + saveFaculty(email, name, encodedPassword); + } else if (member.isDeleted()) { + saveExistFaculty(email, name, member, encodedPassword); + } else { + failedData.add(i + 1); + } } checkFailedData(failedData); @@ -90,6 +103,18 @@ public void deleteFaculty(Long memberId) { memberRepository.save(member); } + private long saveExistFaculty(String email, String name, Member oldMember, String password) { + Member newMember = new Member(email, name, password, "01000000000", false); + newMember.setId(oldMember.getId()); + memberRepository.save(newMember); + + FacultyMember facultyMember = facultyMemberRepository.findByMemberId(newMember.getId()).orElse(null); + facultyMemberRepository.save(Objects.requireNonNullElseGet(facultyMember, + () -> new FacultyMember(null, newMember))); + + return newMember.getId(); + } + private void checkIsDuplicateEmail(String email) { if (authCheckDuplicateService.isDuplicateEmail(email)) { throw new AdminAuthException(AdminAuthExceptionType.MEMBER_EMAIL_DUPLICATE); @@ -119,18 +144,12 @@ private void saveFaculty(final String email, final String name, final String pas private boolean isInvalidInput(final String email, final String name) { if (Pattern.matches(EmailAddress.EMAIL_ADDRESS_REGEX, email) && - Pattern.matches(RealName.NAME_REGEX, name) && - !isDuplicateEmail(email)) { + Pattern.matches(RealName.NAME_REGEX, name)) { return false; } return true; } - - private boolean isDuplicateEmail(String email) { - return authCheckDuplicateService.isDuplicateEmail(email); - } - private void checkFailedData(final List failedData) { if (failedData.isEmpty()) { return; From dac1bfd00b042440ad4cf2ca021b59269e1c4217 Mon Sep 17 00:00:00 2001 From: llddang Date: Fri, 6 Sep 2024 15:57:25 +0900 Subject: [PATCH 14/18] =?UTF-8?q?refactor:=20=EA=B4=80=EB=A6=AC=EC=9E=90?= =?UTF-8?q?=20=EB=93=B1=EB=A1=9D=20=EB=B0=8F=20=EC=82=AD=EC=A0=9C=20api?= =?UTF-8?q?=EC=9D=98=20=EC=BB=A8=ED=8A=B8=EB=A1=A4=EB=9F=AC=20=EB=AA=85=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #170 --- .../api/{RegisterController.java => AdminAuthController.java} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename backend/src/main/java/sw_css/admin/auth/api/{RegisterController.java => AdminAuthController.java} (98%) diff --git a/backend/src/main/java/sw_css/admin/auth/api/RegisterController.java b/backend/src/main/java/sw_css/admin/auth/api/AdminAuthController.java similarity index 98% rename from backend/src/main/java/sw_css/admin/auth/api/RegisterController.java rename to backend/src/main/java/sw_css/admin/auth/api/AdminAuthController.java index 79c99cd0..a1e003ae 100644 --- a/backend/src/main/java/sw_css/admin/auth/api/RegisterController.java +++ b/backend/src/main/java/sw_css/admin/auth/api/AdminAuthController.java @@ -23,7 +23,7 @@ @RequestMapping("/admin/auth") @RestController @RequiredArgsConstructor -public class RegisterController { +public class AdminAuthController { private final AuthAdminQueryService authAdminQueryService; From 098a48816636693f3401116725ce996a1e1feaa1 Mon Sep 17 00:00:00 2001 From: llddang Date: Sat, 7 Sep 2024 14:51:53 +0900 Subject: [PATCH 15/18] =?UTF-8?q?refactor:=20=EA=B4=80=EB=A6=AC=EC=9E=90?= =?UTF-8?q?=20=EA=B6=8C=ED=95=9C=20=EA=B4=80=EB=A0=A8=20=EC=BB=A8=ED=8A=B8?= =?UTF-8?q?=EB=A1=A4=EB=9F=AC=20=EB=AA=85=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #170 --- .../sw_css/admin/auth/api/AdminAuthController.java | 10 +++++----- ...minQueryService.java => AdminAuthQueryService.java} | 7 +++++-- 2 files changed, 10 insertions(+), 7 deletions(-) rename backend/src/main/java/sw_css/admin/auth/application/{AuthAdminQueryService.java => AdminAuthQueryService.java} (97%) diff --git a/backend/src/main/java/sw_css/admin/auth/api/AdminAuthController.java b/backend/src/main/java/sw_css/admin/auth/api/AdminAuthController.java index a1e003ae..697f8461 100644 --- a/backend/src/main/java/sw_css/admin/auth/api/AdminAuthController.java +++ b/backend/src/main/java/sw_css/admin/auth/api/AdminAuthController.java @@ -12,7 +12,7 @@ import org.springframework.web.bind.annotation.RequestPart; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; -import sw_css.admin.auth.application.AuthAdminQueryService; +import sw_css.admin.auth.application.AdminAuthQueryService; import sw_css.admin.auth.application.dto.request.DeleteFacultyRequest; import sw_css.admin.auth.application.dto.request.RegisterFacultyRequest; import sw_css.member.domain.FacultyMember; @@ -25,13 +25,13 @@ @RequiredArgsConstructor public class AdminAuthController { - private final AuthAdminQueryService authAdminQueryService; + private final AdminAuthQueryService adminAuthQueryService; @PostMapping public ResponseEntity registerFaculty( @Admin FacultyMember facultyMember, @RequestBody @Valid RegisterFacultyRequest request) { - Long memberId = authAdminQueryService.registerFaculty(request); + Long memberId = adminAuthQueryService.registerFaculty(request); return ResponseEntity.created(URI.create("/members/" + memberId)).build(); } @@ -39,7 +39,7 @@ public ResponseEntity registerFaculty( public ResponseEntity registerFaculties( @Admin FacultyMember facultyMember, @RequestPart(value = "file") final MultipartFile file) { - authAdminQueryService.registerFaculties(file); + adminAuthQueryService.registerFaculties(file); return ResponseEntity.created(URI.create("/admin/faculties")).build(); } @@ -47,7 +47,7 @@ public ResponseEntity registerFaculties( public ResponseEntity deleteFaculty( @SuperAdmin FacultyMember facultyMember, @RequestBody @Valid DeleteFacultyRequest request) { - authAdminQueryService.deleteFaculty(request.member_id()); + adminAuthQueryService.deleteFaculty(request.member_id()); return ResponseEntity.noContent().build(); } diff --git a/backend/src/main/java/sw_css/admin/auth/application/AuthAdminQueryService.java b/backend/src/main/java/sw_css/admin/auth/application/AdminAuthQueryService.java similarity index 97% rename from backend/src/main/java/sw_css/admin/auth/application/AuthAdminQueryService.java rename to backend/src/main/java/sw_css/admin/auth/application/AdminAuthQueryService.java index a6a9dcd6..70c2d738 100644 --- a/backend/src/main/java/sw_css/admin/auth/application/AuthAdminQueryService.java +++ b/backend/src/main/java/sw_css/admin/auth/application/AdminAuthQueryService.java @@ -31,7 +31,7 @@ @Service @RequiredArgsConstructor -public class AuthAdminQueryService { +public class AdminAuthQueryService { private final MemberRepository memberRepository; private final FacultyMemberRepository facultyMemberRepository; @@ -95,8 +95,11 @@ public void registerFaculties(MultipartFile file) { @Transactional public void deleteFaculty(Long memberId) { - Member member = memberRepository.findById(memberId) + FacultyMember facultyMember = facultyMemberRepository.findById(memberId) .orElseThrow(() -> new AdminAuthException(AdminAuthExceptionType.MEMBER_NOT_FOUND)); + + Member member = facultyMember.getMember(); + checkIsMemberDeleted(member); member.setDeleted(true); From 67f3b002abf013b7e2b64755e045b960ff69d17f Mon Sep 17 00:00:00 2001 From: llddang Date: Sat, 7 Sep 2024 15:24:05 +0900 Subject: [PATCH 16/18] =?UTF-8?q?feat:=20=EA=B4=80=EB=A6=AC=EC=9E=90=20?= =?UTF-8?q?=EB=93=B1=EB=A1=9D/=EC=82=AD=EC=A0=9C=20=EA=B4=80=EB=A0=A8=20ap?= =?UTF-8?q?i=20test=20=EC=BD=94=EB=93=9C=20=EC=9E=91=EC=84=B1=20=EB=B0=8F?= =?UTF-8?q?=20rest=20docs=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #170 --- backend/src/docs/asciidoc/index.adoc | 35 ++++++ .../java/sw_css/restdocs/RestDocsTest.java | 12 ++ .../docs/admin/AdminAuthApiDocsTest.java | 110 ++++++++++++++++++ 3 files changed, 157 insertions(+) create mode 100644 backend/src/test/java/sw_css/restdocs/docs/admin/AdminAuthApiDocsTest.java diff --git a/backend/src/docs/asciidoc/index.adoc b/backend/src/docs/asciidoc/index.adoc index 2758deaa..95e19532 100644 --- a/backend/src/docs/asciidoc/index.adoc +++ b/backend/src/docs/asciidoc/index.adoc @@ -300,3 +300,38 @@ include::{snippets}/auth-reset-password/request-fields.adoc[] .HTTP Response include::{snippets}/auth-reset-password/http-response.adoc[] +== 관리자 인증 + +=== `POST`: 관리자 단일 등록 + +.HTTP Request +include::{snippets}/admin-auth-register/http-request.adoc[] + +.Request Body +include::{snippets}/admin-auth-register/request-body.adoc[] + +.HTTP Response +include::{snippets}/admin-auth-register/http-response.adoc[] + +=== `POST`: 파일을 이용한 관리자 다중 등록 + +.HTTP Request +include::{snippets}/admin-auth-register-by-file/http-request.adoc[] + +.Request Body +include::{snippets}/admin-auth-register-by-file/request-parts.adoc[] + +.HTTP Response +include::{snippets}/admin-auth-register-by-file/http-response.adoc[] + +=== `DELETE`: 관리자 삭제 + +.HTTP Request +include::{snippets}/admin-auth-delete/http-request.adoc[] + +.Request Body +include::{snippets}/admin-auth-delete/request-body.adoc[] + +.HTTP Response +include::{snippets}/admin-auth-delete/http-response.adoc[] + diff --git a/backend/src/test/java/sw_css/restdocs/RestDocsTest.java b/backend/src/test/java/sw_css/restdocs/RestDocsTest.java index a508ce9a..e2584948 100644 --- a/backend/src/test/java/sw_css/restdocs/RestDocsTest.java +++ b/backend/src/test/java/sw_css/restdocs/RestDocsTest.java @@ -14,6 +14,7 @@ import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.filter.CharacterEncodingFilter; +import sw_css.admin.auth.application.AdminAuthQueryService; import sw_css.admin.member.application.MemberAdminQueryService; import sw_css.admin.milestone.application.MilestoneHistoryAdminCommandService; import sw_css.admin.milestone.application.MilestoneHistoryAdminQueryService; @@ -28,7 +29,9 @@ import sw_css.milestone.application.MilestoneHistoryCommandService; import sw_css.milestone.application.MilestoneHistoryQueryService; import sw_css.milestone.application.MilestoneQueryService; +import sw_css.utils.JwtToken.AdminArgumentResolver; import sw_css.utils.JwtToken.JwtAuthorizationArgumentResolver; +import sw_css.utils.JwtToken.SuperAdminArgumentResolver; @Import(RestDocsConfiguration.class) @ExtendWith(RestDocumentationExtension.class) @@ -73,9 +76,18 @@ public abstract class RestDocsTest extends ApiTestHelper { @MockBean protected AuthSignInService authSignInService; + @MockBean + protected AdminAuthQueryService adminAuthQueryService; + @MockBean protected JwtAuthorizationArgumentResolver jwtAuthorizationArgumentResolver; + @MockBean + protected AdminArgumentResolver adminArgumentResolver; + + @MockBean + protected SuperAdminArgumentResolver superAdminArgumentResolver; + @Autowired protected RestDocumentationResultHandler restDocs; diff --git a/backend/src/test/java/sw_css/restdocs/docs/admin/AdminAuthApiDocsTest.java b/backend/src/test/java/sw_css/restdocs/docs/admin/AdminAuthApiDocsTest.java new file mode 100644 index 00000000..ecaaaaaa --- /dev/null +++ b/backend/src/test/java/sw_css/restdocs/docs/admin/AdminAuthApiDocsTest.java @@ -0,0 +1,110 @@ +package sw_css.restdocs.docs.admin; + +import static org.apache.tomcat.util.http.fileupload.FileUploadBase.MULTIPART_FORM_DATA; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.when; +import static org.springframework.http.MediaType.APPLICATION_JSON; +import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.multipart; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post; +import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields; +import static org.springframework.restdocs.request.RequestDocumentation.partWithName; +import static org.springframework.restdocs.request.RequestDocumentation.requestParts; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.http.HttpHeaders; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders; +import org.springframework.restdocs.payload.JsonFieldType; +import org.springframework.restdocs.payload.RequestFieldsSnippet; +import org.springframework.restdocs.request.RequestPartsSnippet; +import sw_css.admin.auth.api.AdminAuthController; +import sw_css.admin.auth.application.dto.request.DeleteFacultyRequest; +import sw_css.admin.auth.application.dto.request.RegisterFacultyRequest; +import sw_css.restdocs.RestDocsTest; + +@WebMvcTest(AdminAuthController.class) +public class AdminAuthApiDocsTest extends RestDocsTest { + + @Test + @DisplayName("[성공] 관리자 단일 등록을 할 수 있다.") + public void registerFacultyMemberInfo() throws Exception { + // given + final RequestFieldsSnippet requestFieldsSnippet = requestFields( + fieldWithPath("email").type(JsonFieldType.STRING).description("부산대학교 이메일"), + fieldWithPath("name").type(JsonFieldType.STRING).description("실명") + ); + + final String email = "root@pusan.ac.kr"; + final String name = "관리자"; + RegisterFacultyRequest request = new RegisterFacultyRequest(email, name); + + final String token = "Bearer AccessToken"; + + // when + when(adminAuthQueryService.registerFaculty(request)).thenReturn(1L); + + // then + mockMvc.perform(post("/admin/auth") + .contentType(APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request)) + .header(HttpHeaders.AUTHORIZATION, token)) + .andExpect(status().isCreated()) + .andDo(document("admin-auth-register", requestFieldsSnippet)); + } + + @Test + @DisplayName("[성공] 관리자 단중 등록을 할 수 있다.") + public void registerFaculties() throws Exception { + // given + final RequestPartsSnippet requestPartsSnippet = requestParts( + partWithName("file").description("일괄 등록할 관리자 이메일 및 이름이 담긴 엑셀 파일(.xls, .xlsx)") + ); + + final MockMultipartFile request = new MockMultipartFile("file", "test.xls", "multipart/form-data", + "example".getBytes()); + + final String token = "Bearer AccessToken"; + + // when + doNothing().when(adminAuthQueryService).registerFaculties(request); + + // then + mockMvc.perform(multipart("/admin/auth/files").file(request) + .contentType(MULTIPART_FORM_DATA) + .accept(APPLICATION_JSON) + .characterEncoding("UTF-8") + .header(HttpHeaders.AUTHORIZATION, token)) + .andExpect(status().isCreated()) + .andDo(document("admin-auth-register-by-file", requestPartsSnippet)); + } + + @Test + @DisplayName("[성공] 관리자 단중 등록을 할 수 있다.") + public void deleteFaculty() throws Exception { + // given + final RequestFieldsSnippet requestFieldsSnippet = requestFields( + fieldWithPath("member_id").type(JsonFieldType.NUMBER).description("관리자의 id") + ); + + final long faculty_id = 1L; + DeleteFacultyRequest request = new DeleteFacultyRequest(faculty_id); + + final String token = "Bearer AccessToken"; + + // when + doNothing().when(adminAuthQueryService).deleteFaculty(request.member_id()); + + // then + mockMvc.perform(RestDocumentationRequestBuilders.delete("/admin/auth") + .contentType(APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request)) + .header(HttpHeaders.AUTHORIZATION, token)) + .andExpect(status().isNoContent()) + .andDo(document("admin-auth-delete", requestFieldsSnippet)); + } +} From e62b873357b53a44584f9e82b391a4fe89dedd46 Mon Sep 17 00:00:00 2001 From: llddang Date: Wed, 11 Sep 2024 17:17:47 +0900 Subject: [PATCH 17/18] =?UTF-8?q?refactor:=20=EC=BD=94=EB=93=9C=EB=A6=AC?= =?UTF-8?q?=EB=B7=B0=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - soft delete 에 해당하는 SQLRestriction 사용하기 - 조회를 뜻하는 queryService에서 commandService로 서비스명 수정 #170 --- .../admin/auth/api/AdminAuthController.java | 10 ++-- ...vice.java => AdminAuthCommandService.java} | 51 ++++++------------- .../auth/application/AuthEmailService.java | 9 ++-- .../auth/application/AuthSignUpService.java | 45 ---------------- .../java/sw_css/member/domain/Member.java | 2 + backend/src/main/resources/test-data.sql | 7 --- .../java/sw_css/restdocs/RestDocsTest.java | 8 ++- .../docs/admin/AdminAuthApiDocsTest.java | 11 ++-- 8 files changed, 37 insertions(+), 106 deletions(-) rename backend/src/main/java/sw_css/admin/auth/application/{AdminAuthQueryService.java => AdminAuthCommandService.java} (78%) diff --git a/backend/src/main/java/sw_css/admin/auth/api/AdminAuthController.java b/backend/src/main/java/sw_css/admin/auth/api/AdminAuthController.java index 697f8461..b6e30916 100644 --- a/backend/src/main/java/sw_css/admin/auth/api/AdminAuthController.java +++ b/backend/src/main/java/sw_css/admin/auth/api/AdminAuthController.java @@ -12,7 +12,7 @@ import org.springframework.web.bind.annotation.RequestPart; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; -import sw_css.admin.auth.application.AdminAuthQueryService; +import sw_css.admin.auth.application.AdminAuthCommandService; import sw_css.admin.auth.application.dto.request.DeleteFacultyRequest; import sw_css.admin.auth.application.dto.request.RegisterFacultyRequest; import sw_css.member.domain.FacultyMember; @@ -25,13 +25,13 @@ @RequiredArgsConstructor public class AdminAuthController { - private final AdminAuthQueryService adminAuthQueryService; + private final AdminAuthCommandService adminAuthCommandService; @PostMapping public ResponseEntity registerFaculty( @Admin FacultyMember facultyMember, @RequestBody @Valid RegisterFacultyRequest request) { - Long memberId = adminAuthQueryService.registerFaculty(request); + Long memberId = adminAuthCommandService.registerFaculty(request); return ResponseEntity.created(URI.create("/members/" + memberId)).build(); } @@ -39,7 +39,7 @@ public ResponseEntity registerFaculty( public ResponseEntity registerFaculties( @Admin FacultyMember facultyMember, @RequestPart(value = "file") final MultipartFile file) { - adminAuthQueryService.registerFaculties(file); + adminAuthCommandService.registerFaculties(file); return ResponseEntity.created(URI.create("/admin/faculties")).build(); } @@ -47,7 +47,7 @@ public ResponseEntity registerFaculties( public ResponseEntity deleteFaculty( @SuperAdmin FacultyMember facultyMember, @RequestBody @Valid DeleteFacultyRequest request) { - adminAuthQueryService.deleteFaculty(request.member_id()); + adminAuthCommandService.deleteFaculty(request.member_id()); return ResponseEntity.noContent().build(); } diff --git a/backend/src/main/java/sw_css/admin/auth/application/AdminAuthQueryService.java b/backend/src/main/java/sw_css/admin/auth/application/AdminAuthCommandService.java similarity index 78% rename from backend/src/main/java/sw_css/admin/auth/application/AdminAuthQueryService.java rename to backend/src/main/java/sw_css/admin/auth/application/AdminAuthCommandService.java index 70c2d738..54458814 100644 --- a/backend/src/main/java/sw_css/admin/auth/application/AdminAuthQueryService.java +++ b/backend/src/main/java/sw_css/admin/auth/application/AdminAuthCommandService.java @@ -3,7 +3,6 @@ import java.io.IOException; import java.util.ArrayList; import java.util.List; -import java.util.Objects; import java.util.regex.Pattern; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; @@ -31,7 +30,7 @@ @Service @RequiredArgsConstructor -public class AdminAuthQueryService { +public class AdminAuthCommandService { private final MemberRepository memberRepository; private final FacultyMemberRepository facultyMemberRepository; @@ -42,14 +41,9 @@ public class AdminAuthQueryService { @Transactional public Long registerFaculty(RegisterFacultyRequest request) { - final String encodedPassword = Password.encode(password); + validateDuplicateEmail(request.email()); - Member member = memberRepository.findByEmail(request.email()).orElse(null); - if (member != null && !member.isDeleted()) { - throw new AdminAuthException(AdminAuthExceptionType.MEMBER_EMAIL_DUPLICATE); - } else if (member != null) { - return saveExistFaculty(request.email(), request.name(), member, encodedPassword); - } + final String encodedPassword = Password.encode(password); final long memberId = memberRepository.save(request.toMember(encodedPassword)).getId(); facultyMemberRepository.save(request.toFacultyMember(memberId, encodedPassword)); @@ -80,14 +74,7 @@ public void registerFaculties(MultipartFile file) { continue; } - Member member = memberRepository.findByEmail(email).orElse(null); - if (member == null) { - saveFaculty(email, name, encodedPassword); - } else if (member.isDeleted()) { - saveExistFaculty(email, name, member, encodedPassword); - } else { - failedData.add(i + 1); - } + saveFaculty(email, name, encodedPassword); } checkFailedData(failedData); @@ -106,24 +93,6 @@ public void deleteFaculty(Long memberId) { memberRepository.save(member); } - private long saveExistFaculty(String email, String name, Member oldMember, String password) { - Member newMember = new Member(email, name, password, "01000000000", false); - newMember.setId(oldMember.getId()); - memberRepository.save(newMember); - - FacultyMember facultyMember = facultyMemberRepository.findByMemberId(newMember.getId()).orElse(null); - facultyMemberRepository.save(Objects.requireNonNullElseGet(facultyMember, - () -> new FacultyMember(null, newMember))); - - return newMember.getId(); - } - - private void checkIsDuplicateEmail(String email) { - if (authCheckDuplicateService.isDuplicateEmail(email)) { - throw new AdminAuthException(AdminAuthExceptionType.MEMBER_EMAIL_DUPLICATE); - } - } - private Workbook generateWorkbook(final MultipartFile file, String extension) { try { if (extension.equals("xlsx")) { @@ -145,9 +114,19 @@ private void saveFaculty(final String email, final String name, final String pas facultyMemberRepository.save(facultyMember); } + private void validateDuplicateEmail(String email) { + if (authCheckDuplicateService.isDuplicateEmail(email)) { + throw new AdminAuthException(AdminAuthExceptionType.MEMBER_EMAIL_DUPLICATE); + } + } + + private boolean isDuplicateEmail(String email) { + return authCheckDuplicateService.isDuplicateEmail(email); + } + private boolean isInvalidInput(final String email, final String name) { if (Pattern.matches(EmailAddress.EMAIL_ADDRESS_REGEX, email) && - Pattern.matches(RealName.NAME_REGEX, name)) { + Pattern.matches(RealName.NAME_REGEX, name) && !isDuplicateEmail(email)) { return false; } return true; diff --git a/backend/src/main/java/sw_css/auth/application/AuthEmailService.java b/backend/src/main/java/sw_css/auth/application/AuthEmailService.java index 7f7d0770..033cb25d 100644 --- a/backend/src/main/java/sw_css/auth/application/AuthEmailService.java +++ b/backend/src/main/java/sw_css/auth/application/AuthEmailService.java @@ -1,5 +1,6 @@ package sw_css.auth.application; +import jakarta.transaction.Transactional; import java.util.ArrayList; import java.util.List; import java.util.Random; @@ -10,12 +11,12 @@ import sw_css.auth.domain.repository.EmailAuthRedisRepository; import sw_css.auth.exception.AuthException; import sw_css.auth.exception.AuthExceptionType; -import sw_css.member.domain.Member; import sw_css.member.domain.repository.MemberRepository; import sw_css.utils.MailUtil; @Service @RequiredArgsConstructor +@Transactional(rollbackOn = Exception.class) public class AuthEmailService { public static final int EMAIL_EXPIRED_SECONDS = 600; // 10분 public static final int AUTH_CODE_LENGTH = 10; @@ -62,10 +63,8 @@ private void sendAuthCode(String email, String authCode) { } private void checkIsDuplicateEmail(String email) { - Member member = memberRepository.findByEmail(email).orElse(null); - if (member == null || member.isDeleted()) { - return; + if (authCheckDuplicateService.isDuplicateEmail(email)) { + throw new AuthException(AuthExceptionType.MEMBER_EMAIL_DUPLICATE); } - throw new AuthException(AuthExceptionType.MEMBER_EMAIL_DUPLICATE); } } diff --git a/backend/src/main/java/sw_css/auth/application/AuthSignUpService.java b/backend/src/main/java/sw_css/auth/application/AuthSignUpService.java index 8d2445a6..fffec24d 100644 --- a/backend/src/main/java/sw_css/auth/application/AuthSignUpService.java +++ b/backend/src/main/java/sw_css/auth/application/AuthSignUpService.java @@ -11,8 +11,6 @@ import sw_css.auth.exception.AuthExceptionType; import sw_css.major.domain.Major; import sw_css.major.domain.repository.MajorRepository; -import sw_css.member.domain.CareerType; -import sw_css.member.domain.Member; import sw_css.member.domain.StudentMember; import sw_css.member.domain.repository.MemberRepository; import sw_css.member.domain.repository.StudentMemberRepository; @@ -29,15 +27,6 @@ public class AuthSignUpService { private final EmailAuthRedisRepository emailAuthRedisRepository; public long signUp(SignUpRequest request) { - Member member = memberRepository.findByEmail(request.email()).orElse(null); - if (member != null) { - if (!member.isDeleted()) { - throw new AuthException(AuthExceptionType.MEMBER_EMAIL_DUPLICATE); - } - - return saveExistMember(request, member); - } - checkIsDuplicateEmail(request.email()); checkIsDuplicateStudentId(request.student_id()); @@ -63,40 +52,6 @@ public CheckDuplicateResponse isDuplicateStudentId(String studentId) { return CheckDuplicateResponse.from(authCheckDuplicateService.isDuplicateStudentID(studentId)); } - private long saveExistMember(SignUpRequest request, Member oldMember) { - checkIsValidAuthCode(request.email(), request.auth_code()); - - Major major = majorRepository.findById(request.major_id()) - .orElseThrow(() -> new AuthException(AuthExceptionType.MAJOR_NOT_EXIST)); - Major minor = request.minor_id() == null ? null : majorRepository.findById(request.minor_id()) - .orElseThrow(() -> new AuthException(AuthExceptionType.MAJOR_NOT_EXIST)); - Major doubleMinor = - request.double_major_id() == null ? null : majorRepository.findById(request.double_major_id()) - .orElseThrow(() -> new AuthException(AuthExceptionType.MAJOR_NOT_EXIST)); - - Member newMember = request.toMember(); - newMember.setId(oldMember.getId()); - memberRepository.save(newMember); - - StudentMember oldStudentMember = studentMemberRepository.findByMemberId(newMember.getId()).orElse(null); - if (oldStudentMember == null) { - final StudentMember newStudentMember = request.toStudentMember(newMember.getId(), major, minor, - doubleMinor); - studentMemberRepository.save(newStudentMember); - } else { - final CareerType careerType = CareerType.valueOf(request.career()); - - oldStudentMember.setMajor(major); - oldStudentMember.setMinor(minor); - oldStudentMember.setDoubleMajor(doubleMinor); - oldStudentMember.setCareer(careerType); - oldStudentMember.setCareerDetail(request.career_detail()); - studentMemberRepository.save(oldStudentMember); - } - - return newMember.getId(); - } - private void checkIsDuplicateEmail(String email) { if (authCheckDuplicateService.isDuplicateEmail(email)) { throw new AuthException(AuthExceptionType.MEMBER_EMAIL_DUPLICATE); diff --git a/backend/src/main/java/sw_css/member/domain/Member.java b/backend/src/main/java/sw_css/member/domain/Member.java index 6ad000b3..235a0132 100644 --- a/backend/src/main/java/sw_css/member/domain/Member.java +++ b/backend/src/main/java/sw_css/member/domain/Member.java @@ -10,6 +10,7 @@ import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +import org.hibernate.annotations.SQLRestriction; import sw_css.base.BaseEntity; import sw_css.member.domain.embedded.Password; @@ -17,6 +18,7 @@ @Getter @AllArgsConstructor @NoArgsConstructor(access = AccessLevel.PROTECTED) +@SQLRestriction("is_deleted = false") public class Member extends BaseEntity { @Id @Setter(AccessLevel.PUBLIC) diff --git a/backend/src/main/resources/test-data.sql b/backend/src/main/resources/test-data.sql index 44b13d71..82e6aca9 100644 --- a/backend/src/main/resources/test-data.sql +++ b/backend/src/main/resources/test-data.sql @@ -6,13 +6,6 @@ values ('admin@pusan.ac.kr', '관리자', '$2a$10$YyiOL/E5WjKrZPkB6eQSK.PwZtAO.z insert into faculty_member (member_id) values (1); -insert into member (email, name, password, phone_number, is_deleted) -values ('songsy405@pusan.ac.kr', '송세연', '$2a$10$YyiOL/E5WjKrZPkB6eQSK.PwZtAO.z3JimFbq/Ky3u3rFf3XTGrWK', '01000000000', - false); - -insert into student_member (id, member_id, major_id, minor_id, double_major_id, career, career_detail) -values (202055558, 2, 1, null, null, 'EMPLOYMENT_COMPANY', 'IT 기업 개발자'); - insert into member (email, name, password, phone_number, is_deleted) values ( 'ddang@pusan.ac.kr', '이다은', '$2a$10$YyiOL/E5WjKrZPkB6eQSK.PwZtAO.z3JimFbq/Ky3u3rFf3XTGrWK', '01000000000' , false); diff --git a/backend/src/test/java/sw_css/restdocs/RestDocsTest.java b/backend/src/test/java/sw_css/restdocs/RestDocsTest.java index e2584948..3b35c8e2 100644 --- a/backend/src/test/java/sw_css/restdocs/RestDocsTest.java +++ b/backend/src/test/java/sw_css/restdocs/RestDocsTest.java @@ -14,7 +14,7 @@ import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.filter.CharacterEncodingFilter; -import sw_css.admin.auth.application.AdminAuthQueryService; +import sw_css.admin.auth.application.AdminAuthCommandService; import sw_css.admin.member.application.MemberAdminQueryService; import sw_css.admin.milestone.application.MilestoneHistoryAdminCommandService; import sw_css.admin.milestone.application.MilestoneHistoryAdminQueryService; @@ -26,6 +26,7 @@ import sw_css.helper.ApiTestHelper; import sw_css.major.application.MajorQueryService; import sw_css.member.application.MemberQueryService; +import sw_css.member.domain.repository.MemberRepository; import sw_css.milestone.application.MilestoneHistoryCommandService; import sw_css.milestone.application.MilestoneHistoryQueryService; import sw_css.milestone.application.MilestoneQueryService; @@ -77,7 +78,10 @@ public abstract class RestDocsTest extends ApiTestHelper { protected AuthSignInService authSignInService; @MockBean - protected AdminAuthQueryService adminAuthQueryService; + protected AdminAuthCommandService adminAuthCommandService; + + @MockBean + protected MemberRepository memberRepository; @MockBean protected JwtAuthorizationArgumentResolver jwtAuthorizationArgumentResolver; diff --git a/backend/src/test/java/sw_css/restdocs/docs/admin/AdminAuthApiDocsTest.java b/backend/src/test/java/sw_css/restdocs/docs/admin/AdminAuthApiDocsTest.java index ecaaaaaa..72eccf52 100644 --- a/backend/src/test/java/sw_css/restdocs/docs/admin/AdminAuthApiDocsTest.java +++ b/backend/src/test/java/sw_css/restdocs/docs/admin/AdminAuthApiDocsTest.java @@ -29,7 +29,6 @@ @WebMvcTest(AdminAuthController.class) public class AdminAuthApiDocsTest extends RestDocsTest { - @Test @DisplayName("[성공] 관리자 단일 등록을 할 수 있다.") public void registerFacultyMemberInfo() throws Exception { @@ -46,7 +45,7 @@ public void registerFacultyMemberInfo() throws Exception { final String token = "Bearer AccessToken"; // when - when(adminAuthQueryService.registerFaculty(request)).thenReturn(1L); + when(adminAuthCommandService.registerFaculty(request)).thenReturn(1L); // then mockMvc.perform(post("/admin/auth") @@ -58,7 +57,7 @@ public void registerFacultyMemberInfo() throws Exception { } @Test - @DisplayName("[성공] 관리자 단중 등록을 할 수 있다.") + @DisplayName("[성공] 관리자 다중 등록을 할 수 있다.") public void registerFaculties() throws Exception { // given final RequestPartsSnippet requestPartsSnippet = requestParts( @@ -71,7 +70,7 @@ public void registerFaculties() throws Exception { final String token = "Bearer AccessToken"; // when - doNothing().when(adminAuthQueryService).registerFaculties(request); + doNothing().when(adminAuthCommandService).registerFaculties(request); // then mockMvc.perform(multipart("/admin/auth/files").file(request) @@ -84,7 +83,7 @@ public void registerFaculties() throws Exception { } @Test - @DisplayName("[성공] 관리자 단중 등록을 할 수 있다.") + @DisplayName("[성공] 관리자 삭제할 수 있다.") public void deleteFaculty() throws Exception { // given final RequestFieldsSnippet requestFieldsSnippet = requestFields( @@ -97,7 +96,7 @@ public void deleteFaculty() throws Exception { final String token = "Bearer AccessToken"; // when - doNothing().when(adminAuthQueryService).deleteFaculty(request.member_id()); + doNothing().when(adminAuthCommandService).deleteFaculty(request.member_id()); // then mockMvc.perform(RestDocumentationRequestBuilders.delete("/admin/auth") From 9428736fb89f2511ea3863660e53928b1ea37957 Mon Sep 17 00:00:00 2001 From: llddang Date: Wed, 11 Sep 2024 17:27:00 +0900 Subject: [PATCH 18/18] =?UTF-8?q?refactor:=20=EC=BD=94=EB=93=9C=EB=A6=AC?= =?UTF-8?q?=EB=B7=B0=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - setter 삭제 #170 --- .../admin/auth/application/AdminAuthCommandService.java | 5 ++--- backend/src/main/java/sw_css/member/domain/Member.java | 1 - .../src/main/java/sw_css/member/domain/StudentMember.java | 2 -- 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/backend/src/main/java/sw_css/admin/auth/application/AdminAuthCommandService.java b/backend/src/main/java/sw_css/admin/auth/application/AdminAuthCommandService.java index 54458814..97a40999 100644 --- a/backend/src/main/java/sw_css/admin/auth/application/AdminAuthCommandService.java +++ b/backend/src/main/java/sw_css/admin/auth/application/AdminAuthCommandService.java @@ -107,10 +107,9 @@ private Workbook generateWorkbook(final MultipartFile file, String extension) { private void saveFaculty(final String email, final String name, final String password) { Member member = new Member(email, name, password, "01000000000", false); - final long memberId = memberRepository.save(member).getId(); - member.setId(memberId); + final Member savedMember = memberRepository.save(member); - FacultyMember facultyMember = new FacultyMember(null, member); + FacultyMember facultyMember = new FacultyMember(null, savedMember); facultyMemberRepository.save(facultyMember); } diff --git a/backend/src/main/java/sw_css/member/domain/Member.java b/backend/src/main/java/sw_css/member/domain/Member.java index 235a0132..28c6abc5 100644 --- a/backend/src/main/java/sw_css/member/domain/Member.java +++ b/backend/src/main/java/sw_css/member/domain/Member.java @@ -21,7 +21,6 @@ @SQLRestriction("is_deleted = false") public class Member extends BaseEntity { @Id - @Setter(AccessLevel.PUBLIC) @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; diff --git a/backend/src/main/java/sw_css/member/domain/StudentMember.java b/backend/src/main/java/sw_css/member/domain/StudentMember.java index 6a5122dc..5ce14c84 100644 --- a/backend/src/main/java/sw_css/member/domain/StudentMember.java +++ b/backend/src/main/java/sw_css/member/domain/StudentMember.java @@ -12,12 +12,10 @@ import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; -import lombok.Setter; import sw_css.base.BaseEntity; import sw_css.major.domain.Major; @Entity -@Setter(AccessLevel.PUBLIC) @Getter @AllArgsConstructor @NoArgsConstructor(access = AccessLevel.PROTECTED)