Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import org.springframework.web.bind.annotation.RequestParam;

import com.genius.herewe.business.crew.dto.CrewCreateRequest;
import com.genius.herewe.business.crew.dto.CrewLeaderTransferRequest;
import com.genius.herewe.business.crew.dto.CrewMemberResponse;
import com.genius.herewe.business.crew.dto.CrewModifyRequest;
import com.genius.herewe.business.crew.dto.CrewPreviewResponse;
Expand Down Expand Up @@ -418,7 +419,7 @@ CommonResponse inviteCrew(
{
"resultCode": 400,
"code": "LEADER_CANNOT_EXPEL",
"message"; "CREW LEADER는 CREW에서 탈퇴할 수 없습니다."
"message": "CREW LEADER는 CREW에서 탈퇴할 수 없습니다."
}
"""
)
Expand All @@ -435,7 +436,7 @@ CommonResponse inviteCrew(
{
"resultCode": 403,
"code": "LEADER_PERMISSION_DENIED",
"message"; "CREW LEADER의 권한이 필요합니다."
"message": "CREW LEADER의 권한이 필요합니다."
}
"""
)
Expand All @@ -457,7 +458,7 @@ CommonResponse inviteCrew(
{
"resultCode": 404,
"code": "MEMBER_NOT_FOUND",
"message"; "사용자를 찾을 수 없습니다."
"message": "사용자를 찾을 수 없습니다."
}
"""
),
Expand All @@ -467,7 +468,7 @@ CommonResponse inviteCrew(
{
"resultCode": 404,
"code": "CREW_NOT_FOUND",
"message"; "해당 CREW를 찾을 수 없습니다."
"message": "해당 CREW를 찾을 수 없습니다."
}
"""
),
Expand All @@ -477,7 +478,7 @@ CommonResponse inviteCrew(
{
"resultCode": 404,
"code": "MEMBER_NOT_FOUND",
"message"; "사용자를 찾을 수 없습니다."
"message": "사용자를 찾을 수 없습니다."
}
"""
)
Expand All @@ -488,5 +489,66 @@ CommonResponse inviteCrew(
CommonResponse expelCrew(
@HereWeUser User user, @PathVariable Long crewId,
@RequestParam(name = "nickname") String nickname);

@Operation(summary = "크루 리더 양도 API", description = "크루 멤버에 대하여 크루 리더를 양도하는 API")
@ApiResponses({
@ApiResponse(
responseCode = "200",
description = "크루 리더 양도 성공"
),
@ApiResponse(
responseCode = "403",
description = """
1. 요청자가 크루 리더가 아닌 경우
2. 대상자가 크루 멤버가 아닌 경우
""",
content = @Content(
schema = @Schema(implementation = ExceptionResponse.class),
examples = {
@ExampleObject(
name = "1. 요청자가 크루 리더가 아닌 경우",
value = """
{
"resultCode": 403,
"code": "LEADER_PERMISSION_DENIED",
"message": "CREW LEADER의 권한이 필요합니다."
}
"""
),
@ExampleObject(
name = "2. 대상자가 크루 멤버가 아닌 경우",
value = """
{
"resultCode": 403,
"code": "CREW_MEMBERSHIP_REQUIRED",
"message": "크루 멤버에게만 허용된 작업입니다. 크루에 참여 후 재시도해주세요."
}
"""
)
}
)
),
@ApiResponse(
responseCode = "404",
description = "닉네임을 통해 대상자를 찾을 수 없는 경우",
content = @Content(
schema = @Schema(implementation = ExceptionResponse.class),
examples = {
@ExampleObject(
name = "닉네임을 통해 대상자를 찾을 수 없는 경우",
value = """
{
"resultCode": 404,
"code": "MEMBER_NOT_FOUND",
"message": "사용자를 찾을 수 없습니다."
}
"""
)
}
)
)
})
CommonResponse handOverLeader(@HereWeUser User user, @PathVariable Long crewId,
@RequestBody @Valid CrewLeaderTransferRequest transferRequest);
}

Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import com.genius.herewe.business.crew.dto.CrewCreateRequest;
import com.genius.herewe.business.crew.dto.CrewExpelRequest;
import com.genius.herewe.business.crew.dto.CrewIdResponse;
import com.genius.herewe.business.crew.dto.CrewLeaderTransferRequest;
import com.genius.herewe.business.crew.dto.CrewMemberResponse;
import com.genius.herewe.business.crew.dto.CrewModifyRequest;
import com.genius.herewe.business.crew.dto.CrewPreviewResponse;
Expand All @@ -43,7 +44,7 @@ public class CrewController implements CrewApi {

@GetMapping("/profile/{crewId}")
public SingleResponse<CrewProfileResponse> inquiryCrewProfile(@HereWeUser User user,
@PathVariable Long crewId) {
@PathVariable Long crewId) {
CrewProfileResponse crewProfileResponse = crewFacade.inquiryCrewProfile(user.getId(), crewId);
return new SingleResponse<>(HttpStatus.OK, crewProfileResponse);
}
Expand Down Expand Up @@ -126,4 +127,19 @@ public CommonResponse expelCrew(

return CommonResponse.ok();
}

@DeleteMapping("/{crewId}/members/me")
public CommonResponse quitCrew(@HereWeUser User user,
@PathVariable Long crewId) {
crewFacade.quitCrew(user.getId(), crewId);
return CommonResponse.ok();
}

@PatchMapping("/{crewId}/members/leader")
public CommonResponse handOverLeader(@HereWeUser User user, @PathVariable Long crewId,
@RequestBody @Valid CrewLeaderTransferRequest transferRequest) {

crewFacade.handoverLeader(crewId, user.getId(), transferRequest.nickname());
return CommonResponse.ok();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@ public static CrewMember createByRole(CrewRole role) {
return new CrewMember(role, LocalDate.now());
}

//== 비지니스 로직 ==//
public void updateRole(CrewRole crewRole) {
this.role = crewRole;
}

//== 연관관계 편의 메서드 ==//
public void joinCrew(User user, Crew crew) {
this.user = user;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.genius.herewe.business.crew.dto;

import jakarta.validation.constraints.NotBlank;

public record CrewLeaderTransferRequest(
@NotBlank
String nickname
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,8 @@ public interface CrewFacade {
void deleteCrew(Long crewId);

void expelCrew(Long userId, CrewExpelRequest expelRequest);

void quitCrew(Long userId, Long crewId);

void handoverLeader(Long crewId, Long userId, String targetNickname);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import static com.genius.herewe.core.global.exception.ErrorCode.*;

import java.util.Objects;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
Expand Down Expand Up @@ -125,4 +127,40 @@ public void expelCrew(Long userId, CrewExpelRequest expelRequest) {
crewMemberService.delete(crewMember);
crew.updateParticipantCount(-1);
}

@Override
@Transactional
public void quitCrew(Long userId, Long crewId) {
User user = userService.findById(userId);
Crew crew = crewService.findById(crewId);

CrewMember crewMember = crewMemberService.find(userId, crewId);
if (crewMember.getRole() == CrewRole.LEADER) {
throw new BusinessException(LEADER_CANNOT_EXPEL);
}

crewMemberService.delete(crewMember);
crew.updateParticipantCount(-1);
}

@Override
@Transactional
public void handoverLeader(Long crewId, Long userId, String targetNickname) {
User leader = userService.findById(userId);
User newLeader = userService.findByNickname(targetNickname)
.orElseThrow(() -> new BusinessException(MEMBER_NOT_FOUND));

CrewMember originLeaderJoinInfo = crewMemberService.find(leader.getId(), crewId);
if (originLeaderJoinInfo.getRole() != CrewRole.LEADER) {
throw new BusinessException(LEADER_PERMISSION_DENIED);
}
if (Objects.equals(leader.getId(), newLeader.getId())) {
throw new BusinessException(LEADER_ALREADY_ASSIGNED);
}
CrewMember newLeaderJoinInfo = crewMemberService.findOptional(newLeader.getId(), crewId)
.orElseThrow(() -> new BusinessException(CREW_MEMBERSHIP_REQUIRED));

originLeaderJoinInfo.updateRole(CrewRole.MEMBER);
newLeaderJoinInfo.updateRole(CrewRole.LEADER);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public enum ErrorCode {
CREW_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 CREW를 찾을 수 없습니다."),
LEADER_PERMISSION_DENIED(HttpStatus.FORBIDDEN, "CREW LEADER의 권한이 필요합니다."),
LEADER_CANNOT_EXPEL(HttpStatus.BAD_REQUEST, "CREW LEADER는 CREW에서 탈퇴할 수 없습니다."),
LEADER_ALREADY_ASSIGNED(HttpStatus.BAD_REQUEST, "이미 LEADER인 사용자에 대해서는 LEADER 양도가 불가능합니다."),

CREW_JOIN_INFO_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 크루에 대한 참여 정보가 없습니다."),
ALREADY_JOINED_CREW(HttpStatus.BAD_REQUEST, "이미 참여한 크루입니다."),
Expand All @@ -54,10 +55,10 @@ public enum ErrorCode {
JWT_NOT_VALID(HttpStatus.UNAUTHORIZED, "JWT가 유효하지 않습니다."),
JWT_NOT_FOUND_IN_HEADER(HttpStatus.UNAUTHORIZED, "Header에서 JWT를 찾을 수 없습니다."),
JWT_NOT_FOUND_IN_COOKIE(HttpStatus.UNAUTHORIZED, "Cookie에서 JWT를 찾을 수 없습니다."),
REFRESH_NOT_FOUND_IN_DB(HttpStatus.NOT_FOUND, "DB에서 사용자의 Refresh token 정보를 찾을 수 없습니다."),
REFRESH_NOT_FOUND_IN_DB(HttpStatus.UNAUTHORIZED, "DB에서 사용자의 Refresh token 정보를 찾을 수 없습니다."),
TOKEN_HIJACKED(HttpStatus.UNAUTHORIZED, "토큰 탈취가 감지되었습니다. 다시 로그인해주세요."),

REGISTRATION_TOKEN_NOT_FOUND(HttpStatus.NOT_FOUND, "회원가입 토큰을 찾을 수 없습니다."),
TOKEN_NOT_FOUND_IN_REDIS(HttpStatus.UNAUTHORIZED, "Redis에서 저장되어 있는 토큰을 찾을 수 없습니다."),

FILE_NOT_EXIST(HttpStatus.NOT_FOUND, "해당 파일이 존재하지 않습니다."),
FILE_INVALID(HttpStatus.BAD_REQUEST, "파일의 형태가 유효하지 않습니다."),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
import com.genius.herewe.core.security.handler.OAuth2FailureHandler;
import com.genius.herewe.core.security.handler.OAuth2SuccessHandler;
import com.genius.herewe.core.security.service.CustomOAuth2Service;
import com.genius.herewe.core.security.service.JwtFacade;
import com.genius.herewe.core.security.service.JwtManager;

import lombok.RequiredArgsConstructor;

Expand All @@ -37,7 +37,7 @@ public class SecurityConfig {
private final OAuth2SuccessHandler oAuth2SuccessHandler;
private final OAuth2FailureHandler oAuth2FailureHandler;

private final JwtFacade jwtFacade;
private final JwtManager jwtManager;

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
Expand All @@ -59,7 +59,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
// JWT
.sessionManagement(configurer -> configurer
.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.addFilterBefore(new JwtAuthenticationFilter(jwtFacade), UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(new JwtAuthenticationFilter(jwtManager), UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(new AuthenticationExceptionFilter(), JwtAuthenticationFilter.class)

// OAuth2
Expand Down
Loading
Loading