Skip to content

Commit

Permalink
Merge pull request #141 from Team-B1ND/Feature/#135
Browse files Browse the repository at this point in the history
Feature/#135
  • Loading branch information
dongchandev authored Dec 21, 2024
2 parents 8c8b806 + 0ff3b45 commit 0fb1ed8
Show file tree
Hide file tree
Showing 24 changed files with 786 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package b1nd.dodam.restapi.auth.infrastructure.security;

import b1nd.dodam.core.exception.global.GlobalExceptionCode;
import b1nd.dodam.restapi.auth.infrastructure.security.filter.DivisionPermissionInterceptor;
import b1nd.dodam.restapi.support.exception.ErrorResponseSender;
import b1nd.dodam.restapi.auth.infrastructure.security.filter.BroadcastMemberFilter;
import b1nd.dodam.restapi.auth.infrastructure.security.filter.TokenExceptionFilter;
Expand Down Expand Up @@ -31,6 +32,7 @@ class SecurityConfig {
private final TokenFilter tokenFilter;
private final TokenExceptionFilter tokenExceptionFilter;
private final BroadcastMemberFilter broadcastMemberFilter;
private final DivisionPermissionInterceptor divisionPermissionFilter;
private final ErrorResponseSender errorResponseSender;

@Bean
Expand Down Expand Up @@ -108,6 +110,12 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
.requestMatchers(PATCH, "/recruit/**").hasRole(TEACHER)
.requestMatchers(DELETE, "/recruit/**").hasRole(TEACHER)

.requestMatchers(POST, "/divisions").hasAnyRole(ADMIN, TEACHER)
.requestMatchers(POST, "/divisions/{id}/members/**").authenticated()
.requestMatchers(GET, "/divisions/**").authenticated()
.requestMatchers(PATCH, "/divisions/**").authenticated()
.requestMatchers(DELETE, "/divisions/**").authenticated()

.anyRequest().authenticated()
.and()
.exceptionHandling()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package b1nd.dodam.restapi.auth.infrastructure.security.filter;

import b1nd.dodam.core.exception.global.GlobalExceptionCode;
import b1nd.dodam.domain.rds.division.entity.Division;
import b1nd.dodam.domain.rds.division.entity.DivisionMember;
import b1nd.dodam.domain.rds.division.enumeration.DivisionPermission;
import b1nd.dodam.domain.rds.division.service.DivisionMemberService;
import b1nd.dodam.domain.rds.division.service.DivisionService;
import b1nd.dodam.domain.rds.member.entity.Member;
import b1nd.dodam.domain.rds.member.enumeration.MemberRole;
import b1nd.dodam.restapi.auth.infrastructure.security.support.MemberAuthenticationHolder;
import b1nd.dodam.restapi.support.exception.ErrorResponseSender;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.HandlerMapping;

import java.util.Map;

import static org.springframework.http.HttpMethod.GET;

@Component
@RequiredArgsConstructor
public class DivisionPermissionInterceptor implements HandlerInterceptor {
private final MemberAuthenticationHolder memberAuthenticationHolder;
private final ErrorResponseSender errorResponseSender;
private final DivisionService divisionService;
private final DivisionMemberService divisionMemberService;

public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
final String method = request.getMethod();
Member member = memberAuthenticationHolder.current();
if (isAdminOrTeacher(member)) return true;
if (GET.matches(method)) return true;
Map<String, String> pathVariables = (Map<String, String>) request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
Long id = Long.valueOf(pathVariables.get("id"));
Division division = divisionService.getById(id);
DivisionMember divisionMember = divisionMemberService.getByDivisionAndMember(division, member);
if (divisionMember == null) {
errorResponseSender.send(response, GlobalExceptionCode.INVALID_PERMISSION);
return false;
}
if(divisionMember.getPermission() != DivisionPermission.ADMIN){
errorResponseSender.send(response, GlobalExceptionCode.INVALID_PERMISSION);
return false;
}
return true;
}

private boolean isAdminOrTeacher(Member member) {
return member.getRole() == MemberRole.ADMIN || member.getRole() == MemberRole.TEACHER;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package b1nd.dodam.restapi.division.application;

import b1nd.dodam.domain.rds.division.entity.Division;
import b1nd.dodam.domain.rds.division.enumeration.DivisionPermission;
import b1nd.dodam.domain.rds.division.service.DivisionMemberService;
import b1nd.dodam.domain.rds.division.service.DivisionService;
import b1nd.dodam.domain.rds.member.repository.MemberRepository;
import b1nd.dodam.domain.rds.member.repository.StudentRepository;
import b1nd.dodam.domain.rds.support.enumeration.ApprovalStatus;
import b1nd.dodam.restapi.auth.infrastructure.security.support.MemberAuthenticationHolder;
import b1nd.dodam.restapi.division.application.data.res.DivisionMemberCountRes;
import b1nd.dodam.restapi.division.application.data.res.DivisionMemberRes;
import b1nd.dodam.restapi.support.data.Response;
import b1nd.dodam.restapi.support.data.ResponseData;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Component
@RequiredArgsConstructor
@Transactional(rollbackFor = Exception.class)
public class DivisionMemberUseCase {
private final DivisionMemberService divisionMemberService;
private final DivisionService divisionService;
private final MemberRepository memberRepository;
private final MemberAuthenticationHolder authHolder;
private final StudentRepository studentRepository;

public Response addMember(Long id, List<String> memberIdList){
divisionMemberService.saveWithBuild(
divisionService.getById(id),
memberIdList.stream().map(memberRepository::getById).toList(),
ApprovalStatus.ALLOWED,
DivisionPermission.READER
);
return Response.created("조직에 멤버 추가 성공");
}

public Response expelMemberFromDivision(List<Long> idList){
divisionMemberService.deleteByIds(idList);
return Response.ok("조직 내 멤버 추방");
}

public Response applyDivision(Long id){
divisionMemberService.saveWithBuild(
divisionService.getById(id),
List.of(authHolder.current()),
ApprovalStatus.PENDING,
DivisionPermission.READER
);
return Response.ok("조직에 가입 신청 성공");
}

public Response handleDivisionApplication(List<Long> idList, ApprovalStatus status) {
divisionMemberService.modifyDivisionMembers(divisionMemberService.getByIds(idList), status);
return Response.ok("조직 수락/거절/취소 성공");
}

@Transactional(readOnly = true)
public ResponseData<List<DivisionMemberRes>> getDivisionMemberByDivisionId(Long id, ApprovalStatus status) {
Division division = divisionService.getById(id);
List<DivisionMemberRes> response = divisionMemberService.getByDivisionAndStatus(division, status)
.parallelStream()
.map(dm -> DivisionMemberRes.of(dm, studentRepository.getByMember(dm.getMember())))
.toList();
return ResponseData.ok("조직 내 멤버 조회 성공", response);
}

@Transactional(readOnly = true)
public ResponseData<DivisionMemberCountRes> getDivisionMemberCount(Long id, ApprovalStatus status){
Division division = divisionService.getById(id);
return ResponseData.ok(
"조직의 상태별 멤버 수 조회 성공",
new DivisionMemberCountRes(divisionMemberService.getCountByDivision(division, status))
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package b1nd.dodam.restapi.division.application;

import b1nd.dodam.domain.rds.division.entity.Division;
import b1nd.dodam.domain.rds.division.entity.DivisionMember;
import b1nd.dodam.domain.rds.division.enumeration.DivisionPermission;
import b1nd.dodam.domain.rds.division.service.DivisionMemberService;
import b1nd.dodam.domain.rds.division.service.DivisionService;
import b1nd.dodam.domain.rds.support.enumeration.ApprovalStatus;
import b1nd.dodam.restapi.auth.infrastructure.security.support.MemberAuthenticationHolder;
import b1nd.dodam.restapi.division.application.data.req.ManageDivisionReq;
import b1nd.dodam.restapi.division.application.data.res.DivisionDetailRes;
import b1nd.dodam.restapi.division.application.data.res.DivisionOverviewRes;
import b1nd.dodam.restapi.support.data.Response;
import b1nd.dodam.restapi.support.data.ResponseData;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Component
@RequiredArgsConstructor
@Transactional(rollbackFor = Exception.class)
public class DivisionUseCase {
private final DivisionService divisionService;
private final DivisionMemberService divisionMemberService;
private final MemberAuthenticationHolder authHolder;

public Response organize(ManageDivisionReq req){
divisionService.checkIsNotDuplicateName(req.name());
Division division = req.toEntity();
divisionService.save(division);
divisionMemberService.saveWithBuild(
division,
List.of(authHolder.current()),
ApprovalStatus.ALLOWED,
DivisionPermission.ADMIN
);
return Response.created("조직 구성 성공");
}

public Response modify(Long id, ManageDivisionReq req){
Division division = getDivisionByIdWithValidateAdmin(id);
division.modify(req.name());
divisionService.save(division);
return Response.noContent("조직 정보 수정 성공");
}

public Response delete(Long id){
divisionMemberService.deleteByDivision(getDivisionByIdWithValidateAdmin(id));
divisionService.delete(id);
return Response.noContent("조직 삭제 성공");
}

@Transactional(readOnly = true)
public ResponseData<DivisionDetailRes> getDetail(Long id){
Division division = divisionService.getById(id);
DivisionMember divisionMember = divisionMemberService
.getByDivisionAndMemberAndStatus(division, authHolder.current(), ApprovalStatus.ALLOWED);
return ResponseData.ok("id로 조직 조회 성공", DivisionDetailRes.of(division, divisionMember));
}

@Transactional(readOnly = true)
public ResponseData<List<DivisionOverviewRes>> getMyDivisions(Long lastId, int limit){
List<Division> divisions = divisionService.getByMember(authHolder.current(), lastId, limit);
return ResponseData.ok("내 조직 조회 성공", DivisionOverviewRes.of(divisions));
}

@Transactional(readOnly = true)
public ResponseData<List<DivisionOverviewRes>> getDivisions(Long lastId, int limit){
return ResponseData.ok("전체 조직 조회 성공", DivisionOverviewRes.of(divisionService.getAll(lastId, limit)));
}

private Division getDivisionByIdWithValidateAdmin(Long id){
Division division = divisionService.getById(id);
divisionMemberService.validateIsAdminInDivision(division, authHolder.current());
return division;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package b1nd.dodam.restapi.division.application.data.req;

import b1nd.dodam.domain.rds.division.entity.Division;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;

public record ManageDivisionReq(
@NotBlank String name,
@NotNull String description
) {
public Division toEntity() {
return Division.builder()
.name(name)
.description(description)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package b1nd.dodam.restapi.division.application.data.res;

import b1nd.dodam.domain.rds.division.entity.Division;
import b1nd.dodam.domain.rds.division.entity.DivisionMember;
import b1nd.dodam.domain.rds.division.enumeration.DivisionPermission;

import java.util.Optional;

public record DivisionDetailRes(
Long id,
String divisionName,
String description,
DivisionPermission myPermission
) {
static public DivisionDetailRes of(Division division, DivisionMember dm) {
return new DivisionDetailRes(
division.getId(),
division.getName(),
division.getDescription(),
Optional.of(dm).map(DivisionMember::getPermission).orElse(null)
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package b1nd.dodam.restapi.division.application.data.res;

public record DivisionMemberCountRes(
Long count
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package b1nd.dodam.restapi.division.application.data.res;

import b1nd.dodam.domain.rds.division.entity.DivisionMember;
import b1nd.dodam.domain.rds.division.enumeration.DivisionPermission;
import b1nd.dodam.domain.rds.member.entity.Member;
import b1nd.dodam.domain.rds.member.entity.Student;

import java.util.List;
import java.util.Optional;

public record DivisionMemberRes(
Long id,
String memberName,
String profileImage,
DivisionPermission permission,
Integer grade,
Integer room,
Integer number
) {

static public DivisionMemberRes of(DivisionMember divisionMember, Student student){
Member member = divisionMember.getMember();
return new DivisionMemberRes(
divisionMember.getId(),
member.getName(),
member.getProfileImage(),
divisionMember.getPermission(),
Optional.ofNullable(student).map(Student::getGrade).orElse(null),
Optional.ofNullable(student).map(Student::getRoom).orElse(null),
Optional.ofNullable(student).map(Student::getNumber).orElse(null)
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package b1nd.dodam.restapi.division.application.data.res;

import b1nd.dodam.domain.rds.division.entity.Division;

import java.util.List;

public record DivisionOverviewRes(
Long id,
String name
) {
public static List<DivisionOverviewRes> of(List<Division> divisions) {
return divisions.stream()
.map(division -> new DivisionOverviewRes(division.getId(), division.getName()))
.toList();
}
}
Loading

0 comments on commit 0fb1ed8

Please sign in to comment.