Skip to content

Commit

Permalink
[FIX] 마일스톤 등록 안 되는 이슈 해결 (#188)
Browse files Browse the repository at this point in the history
* Feature/#173 깃 액션 빌드조건 변경 (#174)

* feat: 백엔드 조건 변경

#173

* feat: 프론트 조건 변경

#173

* Feature/#175 test 및 application 수정 (#176)

* feat: config 폴더 업데이트

#175

* feat: application 수정

#175

* feat: test 주석 처리

#175

* feat: config submodule 업데이트

#175

* feat: application 수정

#175

* Feature/#175 test 및 application 수정 (#178)

* feat: config 폴더 업데이트

#175

* feat: application 수정

#175

* feat: test 주석 처리

#175

* feat: config submodule 업데이트

#175

* feat: application 수정

#175

* feat: application test 주석처리

#175

* Feature/#182 출시이후 기능 안보이도록 수정 (#183)

* RELEASE (#177)

* Feature/#173 깃 액션 빌드조건 변경 (#174)

* feat: 백엔드 조건 변경

#173

* feat: 프론트 조건 변경

#173

* Feature/#175 test 및 application 수정 (#176)

* feat: config 폴더 업데이트

#175

* feat: application 수정

#175

* feat: test 주석 처리

#175

* feat: config submodule 업데이트

#175

* feat: application 수정

#175

* release (#179)

* Feature/#173 깃 액션 빌드조건 변경 (#174)

* feat: 백엔드 조건 변경

#173

* feat: 프론트 조건 변경

#173

* Feature/#175 test 및 application 수정 (#176)

* feat: config 폴더 업데이트

#175

* feat: application 수정

#175

* feat: test 주석 처리

#175

* feat: config submodule 업데이트

#175

* feat: application 수정

#175

* Feature/#175 test 및 application 수정 (#178)

* feat: config 폴더 업데이트

#175

* feat: application 수정

#175

* feat: test 주석 처리

#175

* feat: config submodule 업데이트

#175

* feat: application 수정

#175

* feat: application test 주석처리

#175

* Develop (#180)

* Feature/#173 깃 액션 빌드조건 변경 (#174)

* feat: 백엔드 조건 변경

#173

* feat: 프론트 조건 변경

#173

* Feature/#175 test 및 application 수정 (#176)

* feat: config 폴더 업데이트

#175

* feat: application 수정

#175

* feat: test 주석 처리

#175

* feat: config submodule 업데이트

#175

* feat: application 수정

#175

* Feature/#175 test 및 application 수정 (#178)

* feat: config 폴더 업데이트

#175

* feat: application 수정

#175

* feat: test 주석 처리

#175

* feat: config submodule 업데이트

#175

* feat: application 수정

#175

* feat: application test 주석처리

#175

* fix: mixed-content 에러 해결

* feat: 메인 페이지의 마일스톤 api 연결 및 mock 데이터 삭제

#182

* refactor: 서버 api 구조 변경

#182

* feat: test-data.sql의 데이터 수정

#182

* feat: config submodule 폴더 최신화

#182

* feat: 메인페이지의 외부 링크 변경

#182

* feat: 해커톤 개발중인 기능이라고 표시

#182

* feat: 팀 빌딩 구현 중인 기능으로 표시

#182

* feat: title 의 제목 글씨 크기 28px 로 축소

#182

* feat: 관리자 페이지로 이동할 수 있는 버튼 생성

#182

* feat: 메인 페이지의 외부 링크 명 변경

#182

* Feature/#170 관리자 관련 api 구현 (#185)

* feat: 관리자 계정들의 id 자동 생성되도록 수정

#170

* feat: super admin 어노테이션 구현

#170

* feat: 관리자 계정의 어노테이션 구현

#170

* feat: 관리자 단일 등록 api 구현

#170

* feat: Admin & SuperAdmin annotation 등록

#170

* feat: config 폴더 최신화

#170

* feat: 파일을 이용한 교직원 등록 api 구현

#170

* feat: 교직원 탈퇴 api 구현

#170

* feat: 로그인 할 때 deleted 된 사용자인지 검증하는 로직 추가

#179

* feat: 이메일 및 전화번호 중복확인 api 삭제

#170

* feat: 회원가입 및 이메일 인증시 탈퇴한 회원인지 확인하는 로직 추가

#170

* feat: 로그인 관련 회원을 찾지 못할 때의 문구 수정

#170

* feat: 교직원 단일/다중 등록 시 deleted 된 회원인지 검증하는 로직 추가

#170

* refactor: 관리자 등록 및 삭제 api의 컨트롤러 명 수정

#170

* refactor: 관리자 권한 관련 컨트롤러 명 수정

#170

* feat: 관리자 등록/삭제 관련 api test 코드 작성 및 rest docs 작성

#170

* refactor: 코드리뷰 반영

- soft delete 에 해당하는 SQLRestriction 사용하기
- 조회를 뜻하는 queryService에서 commandService로 서비스명 수정
#170

* refactor: 코드리뷰 반영

- setter 삭제
#170

* Feature/#186 마일스톤 권한 설정 (#187)

* refactor: jwt argument Resolver 명 변경

#186

* refactor: jwt annotation 명 수정

#186

* refactor: jwt annotation 명 수정

#186

* feat: student argument resolver 추가

#186

* feat: 마일스톤 등록 시, 해당 회원 권환 확인

#186

* feat: 학생만 마일스톤 삭제할 수 있도록 권한 설정

#186

* feat: 마일스톤 조회 권한 설정

#186

* feat: 마일스톤 점수 조회 api 불러오는 것 권한 설정

#186

* feat: 관리자의 마일스톤 권한 설정

#186

* feat: 프론트 오류 수정 및 api 에 authentication 추가

#186

* feat: api 호출 시 Authorization 붙여서 보내도록 수정

#186

* fix: page 폴더 바깥에서 next/header 사용할 수 없어서 직접 token 넣어주도록 변경

#186
  • Loading branch information
llddang authored Sep 12, 2024
1 parent 57fd3ab commit abf565f
Show file tree
Hide file tree
Showing 46 changed files with 1,003 additions and 314 deletions.
63 changes: 35 additions & 28 deletions backend/src/docs/asciidoc/index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -328,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[]

Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
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.DeleteMapping;
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.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;
import sw_css.utils.annotation.AdminInterface;
import sw_css.utils.annotation.SuperAdminInterface;

@Validated
@RequestMapping("/admin/auth")
@RestController
@RequiredArgsConstructor
public class AdminAuthController {

private final AdminAuthCommandService adminAuthCommandService;

@PostMapping
public ResponseEntity<Void> registerFaculty(
@AdminInterface FacultyMember facultyMember,
@RequestBody @Valid RegisterFacultyRequest request) {
Long memberId = adminAuthCommandService.registerFaculty(request);
return ResponseEntity.created(URI.create("/members/" + memberId)).build();
}

@PostMapping("/files")
public ResponseEntity<Void> registerFaculties(
@AdminInterface FacultyMember facultyMember,
@RequestPart(value = "file") final MultipartFile file) {
adminAuthCommandService.registerFaculties(file);
return ResponseEntity.created(URI.create("/admin/faculties")).build();
}

@DeleteMapping()
public ResponseEntity<Void> deleteFaculty(
@SuperAdminInterface FacultyMember facultyMember,
@RequestBody @Valid DeleteFacultyRequest request) {
adminAuthCommandService.deleteFaculty(request.member_id());
return ResponseEntity.noContent().build();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
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.RegisterFacultyRequest;
import sw_css.admin.auth.exception.AdminAuthException;
import sw_css.admin.auth.exception.AdminAuthExceptionType;
import sw_css.auth.application.AuthCheckDuplicateService;
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
public class AdminAuthCommandService {

private final MemberRepository memberRepository;
private final FacultyMemberRepository facultyMemberRepository;
private final AuthCheckDuplicateService authCheckDuplicateService;

@Value("${password.admin}")
private String password;

@Transactional
public Long registerFaculty(RegisterFacultyRequest request) {
validateDuplicateEmail(request.email());

final String encodedPassword = Password.encode(password);

final long memberId = memberRepository.save(request.toMember(encodedPassword)).getId();
facultyMemberRepository.save(request.toFacultyMember(memberId, encodedPassword));

return memberId;
}

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<Integer> 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);
}

@Transactional
public void deleteFaculty(Long memberId) {
FacultyMember facultyMember = facultyMemberRepository.findById(memberId)
.orElseThrow(() -> new AdminAuthException(AdminAuthExceptionType.MEMBER_NOT_FOUND));

Member member = facultyMember.getMember();

checkIsMemberDeleted(member);

member.setDeleted(true);
memberRepository.save(member);
}

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 Member savedMember = memberRepository.save(member);

FacultyMember facultyMember = new FacultyMember(null, savedMember);
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) && !isDuplicateEmail(email)) {
return false;
}
return true;
}

private void checkFailedData(final List<Integer> 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);
}

private void checkIsMemberDeleted(final Member member) {
if (!member.isDeleted()) {
return;
}
throw new AdminAuthException(AdminAuthExceptionType.MEMBER_NOT_FOUND);

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package sw_css.admin.auth.application.dto.request;

public record DeleteFacultyRequest(
Long member_id
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
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.RealName;

public record RegisterFacultyRequest(
@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(String password) {
return new Member(email, name, password, "01000000000", false);
}

public Member toMember(Long memberId, String password) {
return new Member(memberId, email, name, password, "01000000000", false);
}

public FacultyMember toFacultyMember(Long memberId, String password) {
final Member member = toMember(memberId, password);
return new FacultyMember(null, member);
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
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, "이메일이 중복됩니다."),
MEMBER_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 회원을 찾을 수 없습니다."),
;

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;
}

}
Loading

0 comments on commit abf565f

Please sign in to comment.