Skip to content

Commit

Permalink
[FEAT #30] 기숙사 인증요청, 기숙사 인증요청 처리 로직 구현
Browse files Browse the repository at this point in the history
  • Loading branch information
sharpie1330 committed Nov 3, 2023
1 parent 8305fd6 commit 6cab792
Show file tree
Hide file tree
Showing 8 changed files with 283 additions and 6 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.duktown.domain.dormCert.controller;

import com.duktown.domain.dormCert.dto.DormCertDto;
import com.duktown.domain.dormCert.service.DormCertService;
import com.duktown.global.security.service.CustomUserDetails;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.validation.Valid;

@RestController
@RequiredArgsConstructor
@RequestMapping("/dormCerts")
public class DormCertController {
private final DormCertService dormCertService;

@PostMapping
public ResponseEntity<Void> createDormCert(
@AuthenticationPrincipal CustomUserDetails customUserDetails,
@Valid @RequestPart(value = "certRequest") DormCertDto.UserCertRequest request,
@RequestPart (value = "certImg") MultipartFile certImg
) {
dormCertService.createDormCert(customUserDetails.getId(), request, certImg);
return ResponseEntity.ok().build();
}

@PostMapping("/check/{dormCertId}")
public ResponseEntity<DormCertDto.CertResponse> checkDormCert(
@AuthenticationPrincipal CustomUserDetails customUserDetails,
@PathVariable("dormCertId") Long dormCertId,
@RequestParam("approve") Boolean approve
) {
return ResponseEntity.ok(
dormCertService.checkDormCert(customUserDetails.getId(), dormCertId, approve)
);
}
}
66 changes: 66 additions & 0 deletions src/main/java/com/duktown/domain/dormCert/dto/DormCertDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package com.duktown.domain.dormCert.dto;

import com.duktown.domain.dormCert.entity.DormCert;
import com.duktown.domain.user.entity.User;
import com.duktown.global.type.CertRequestType;
import com.duktown.global.type.HallName;
import com.duktown.global.type.RoleType;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;

public class DormCertDto {

@Getter
@NoArgsConstructor
@AllArgsConstructor
public static class UserCertRequest{
// image는 requestParam MultipartFile로 입력받음

@NotNull(message = "인증요청 타입은 필수 값입니다.")
@Min(value = 0, message = "인증요청 타입 값은 0부터 2 사이의 값이어야 합니다.")
@Max(value = 2, message = "인증요청 타입 값은 0부터 2 사이의 값이어야 합니다.")
private Integer certRequestType;

@NotBlank(message = "학번은 필수 값입니다.")
private String studentId;

@NotNull(message = "기숙사 관명은 필수 값입니다.")
private Integer hallName;

@NotNull(message = "기숙사 층은 필수 값입니다.")
private Integer floorNumber;

@NotNull(message = "기숙사 동은 필수 값입니다.")
private Integer buildingNumber;

@NotNull(message = "기숙사 호수는 필수 값입니다.")
private Integer roomNumber;

public DormCert toEntity(User user, CertRequestType certRequestType, HallName hallName, String imgUrl) {
// 최초 등록 시 certified는 null
return DormCert.builder()
.user(user)
.certRequestType(certRequestType)
.imgUrl(imgUrl)
.studentId(this.studentId)
.hallName(hallName)
.floorNumber(this.floorNumber)
.buildingNumber(this.buildingNumber)
.roomNumber(this.roomNumber)
.build();
}
}

@Getter
@AllArgsConstructor
public static class CertResponse {
private Boolean certified;
private RoleType resultRoleType;
}
}
26 changes: 23 additions & 3 deletions src/main/java/com/duktown/domain/dormCert/entity/DormCert.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.duktown.domain.dormCert.entity;

import com.duktown.domain.user.entity.User;
import com.duktown.global.type.CertRequestType;
import com.duktown.global.type.HallName;
import lombok.AllArgsConstructor;
import lombok.Builder;
Expand Down Expand Up @@ -31,18 +32,37 @@ public class DormCert {
@JoinColumn(name = "user_id", nullable = false)
private User user;

// 인증 종류
@Enumerated(STRING)
@Column(nullable = false)
private String imgUrl;
private CertRequestType certRequestType;

private Boolean certified;
// 이미지
@Column(nullable = false)
private String imgUrl;

// 학번
@Column(nullable = false)
private String studentId;

// 기숙사 정보
@Enumerated(value = STRING)
@Column(nullable = false)
private HallName hallName;

@Column(nullable = false)
private String unit;
private Integer floorNumber;

@Column(nullable = false)
private Integer buildingNumber;

@Column(nullable = false)
private Integer roomNumber;

// 인증 여부
private Boolean certified;

public void update(Boolean certified){
this.certified = certified;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.duktown.domain.dormCert.entity;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface DormCertRepository extends JpaRepository<DormCert, Long> {
}
112 changes: 112 additions & 0 deletions src/main/java/com/duktown/domain/dormCert/service/DormCertService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package com.duktown.domain.dormCert.service;

import com.duktown.domain.dormCert.dto.DormCertDto;
import com.duktown.domain.dormCert.entity.DormCert;
import com.duktown.domain.dormCert.entity.DormCertRepository;
import com.duktown.domain.user.entity.User;
import com.duktown.domain.user.entity.UserRepository;
import com.duktown.global.exception.CustomException;
import com.duktown.global.type.CertRequestType;
import com.duktown.global.type.HallName;
import com.duktown.global.type.RoleType;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.Arrays;

import static com.duktown.global.exception.CustomErrorType.*;

@Service
@RequiredArgsConstructor
@Transactional
public class DormCertService {
private final UserRepository userRepository;
private final DormCertRepository dormCertRepository;

public void createDormCert(Long userId, DormCertDto.UserCertRequest request, MultipartFile certImg){
User user = userRepository.findById(userId).orElseThrow(() -> new CustomException(USER_NOT_FOUND));

// TODO: AWS S3 Service 사용할 것
// 파일 업로드(로컬 폴더)
String absolutePath = Paths.get("").toAbsolutePath() + "/certImages";
File localSaveFolder = new File(absolutePath);

if (!localSaveFolder.exists()) {
try {
localSaveFolder.mkdir();
} catch (Exception e) {
throw new CustomException(CERT_IMG_FOLDER_CREATION_ERROR);
}
}

String filename = certImg.getOriginalFilename();
File destination = new File(absolutePath + "/" + filename);

try{
certImg.transferTo(destination);
} catch (IOException e){
throw new CustomException(CERT_IMG_UPLOAD_ERROR);
}

// 인증 요청 타입 찾기
CertRequestType certRequestType = Arrays.stream(CertRequestType.values())
.filter(type -> type.getValue() == request.getCertRequestType())
.findAny().orElseThrow(() -> new CustomException(INVALID_CERT_REQUEST_TYPE_VALUE));

// 기숙사 관명 찾기
HallName hallName = Arrays.stream(HallName.values())
.filter(hn -> hn.getValue() == request.getHallName())
.findAny().orElseThrow(() -> new CustomException(INVALID_HALL_NAME_VALUE));

DormCert dormCert = request.toEntity(user, certRequestType, hallName, destination.getPath());
dormCertRepository.save(dormCert);
}

public DormCertDto.CertResponse checkDormCert(Long userId, Long dormCertId, Boolean approve){
User user = userRepository.findById(userId).orElseThrow(() -> new CustomException(USER_NOT_FOUND));

// 자기 자신 인증요청 처리 불가
if (!user.getId().equals(userId)) {
throw new CustomException(SELF_DORM_CERT_NOT_ALLOWED);
}

DormCert findDormCert = dormCertRepository.findById(dormCertId).orElseThrow(() -> new CustomException(DORM_CERT_NOT_FOUND));

// 이미 처리된 인증요청인 경우
if (findDormCert.getCertified() != null) {
throw new CustomException(CERT_ALREADY_CHECKED);
}

// 인증요청 타입으로 사용자 권한 찾기
RoleType roleType = Arrays.stream(RoleType.values())
.filter(role -> role.getKey().equals(findDormCert.getCertRequestType().getRoleKey()))
.findAny().orElseThrow(() -> new CustomException(INVALID_CERT_REQUEST_TYPE_VALUE));

RoleType resultRoleType;

// 승인 시
if(approve){
user.updateRoleType(roleType);
userRepository.save(user);

findDormCert.update(true);
dormCertRepository.save(findDormCert);

resultRoleType = user.getRoleType();
}
// 미승인 시
else {
findDormCert.update(false);
dormCertRepository.save(findDormCert);

resultRoleType = user.getRoleType();
}

return new DormCertDto.CertResponse(approve, resultRoleType);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,14 @@ public enum CustomErrorType {
POST_NOT_FOUND(NOT_FOUND, 50001, "존재하지 않는 게시글입니다."),
INVALID_POST_CATEGORY_VALUE(BAD_REQUEST,50002, "잘못된 카테고리입니다."),

// (6xxxx)
// DormCert (6xxxx) TODO: 수정
INVALID_CERT_REQUEST_TYPE_VALUE(BAD_REQUEST, 60001, "잘못된 인증요청 타입을 입력했습니다."),
INVALID_HALL_NAME_VALUE(BAD_REQUEST, 60002, "잘못된 기숙사 관명을 입력했습니다."),
CERT_IMG_UPLOAD_ERROR(INTERNAL_SERVER_ERROR, 60003, "파일 업로드에 실패했습니다."),
DORM_CERT_NOT_FOUND(NOT_FOUND, 60004, "존재하지 않는 기숙사 인증 요청입니다."),
SELF_DORM_CERT_NOT_ALLOWED(FORBIDDEN, 60005, "자기 자신의 인증요청은 처리할 수 없습니다."),
CERT_ALREADY_CHECKED(CONFLICT, 60006, "이미 처리된 인증요청입니다."),
CERT_IMG_FOLDER_CREATION_ERROR(INTERNAL_SERVER_ERROR, 60007, "인증 이미지 저장 폴더 생성에 실패했습니다."), // TODO: s3 service 사용 시 삭제

// Comment(7xxxx)
COMMENT_NOT_FOUND(NOT_FOUND, 70001, "존재하지 않는 댓글입니다."),
Expand Down
12 changes: 10 additions & 2 deletions src/main/java/com/duktown/global/security/SecurityConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,18 @@ public class SecurityConfig {
private final JwtAccessDeniedHandler jwtAccessDeniedHandler;
private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;

// TODO: 인증에 예외를 둘 로직 추가
// TODO: 인증에 예외를 둘 엔드포인트 추가
private static final String[] AUTH_WHITE_LIST = {
"/auth/signup",
"/auth/email-duplicate",
"/auth/id-duplicate"
};

// TODO: 관리자만 접근 가능한 엔드포인트 추가
private static final String[] MANAGER_ENDPOINT_LIST = {
"/dormCerts/check/**"
};

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {

Expand Down Expand Up @@ -70,6 +75,9 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
// whitelist에 있는 endpoint만 인증 제외
Arrays.stream(AUTH_WHITE_LIST)
.forEach(authWhiteListElem -> auth.mvcMatchers(authWhiteListElem).permitAll());
// managerEndpointList에 있는 endPoint는 관리자 역할만 접근 가능
Arrays.stream(MANAGER_ENDPOINT_LIST)
.forEach(melElem -> auth.mvcMatchers(melElem).hasRole("MANAGER"));
auth.anyRequest().authenticated();
});

Expand All @@ -85,4 +93,4 @@ public WebSecurityCustomizer webSecurityCustomizer() {
public HttpFirewall defaultHttpFirewall() {
return new DefaultHttpFirewall();
}
}
}
16 changes: 16 additions & 0 deletions src/main/java/com/duktown/global/type/CertRequestType.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.duktown.global.type;

import lombok.Getter;
import lombok.RequiredArgsConstructor;

@Getter
@RequiredArgsConstructor
public enum CertRequestType {
DORM_STUDENT(0, "ROLE_DORM_STUDENT", "기숙사생 인증"),
DORM_COUNCIL(1, "ROLE_DORM_COUNCIL", "사생회 인증"),
MANAGER(2, "ROLE_MANGER", "관리자 인증");

private final int value;
private final String roleKey;
private final String description;
}

0 comments on commit 6cab792

Please sign in to comment.