Skip to content
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.run_us.server.domains.crew.controller;

import com.run_us.server.domains.crew.controller.model.enums.CrewHttpResponseCode;
import com.run_us.server.domains.crew.controller.model.request.CreateJoinRequest;
import com.run_us.server.domains.crew.controller.model.request.ReviewJoinRequest;
import com.run_us.server.domains.crew.controller.model.response.*;
Expand All @@ -9,10 +8,10 @@
import com.run_us.server.domains.crew.controller.model.request.CreateCrewRequest;
import com.run_us.server.domains.crew.controller.model.response.CreateCrewResponse;
import com.run_us.server.domains.crew.service.usecase.CreateCrewUseCase;
import com.run_us.server.domains.crew.service.usecase.CrewMemberUseCase;
import com.run_us.server.global.common.SuccessResponse;

import com.run_us.server.global.security.annotation.CurrentUser;
import com.run_us.server.global.security.principal.UserPrincipal;
import jakarta.validation.Valid;
import lombok.extern.slf4j.Slf4j;
import lombok.RequiredArgsConstructor;
Expand All @@ -30,6 +29,7 @@
public class CrewController {
private final CrewJoinUseCase crewJoinUseCase;
private final CreateCrewUseCase createCrewUseCase;
private final CrewMemberUseCase crewMemberUseCase;


@PostMapping
Expand All @@ -47,81 +47,98 @@ public ResponseEntity<SuccessResponse<CreateCrewResponse>> createCrew(
@PostMapping("/{crewPublicId}/join-requests")
public ResponseEntity<SuccessResponse<CreateJoinRequestResponse>> requestJoin(
@PathVariable String crewPublicId,
@CurrentUser UserPrincipal userPrincipal,
@CurrentUser String currentUserPublicId,
@Valid @RequestBody CreateJoinRequest request
) {
log.info("action=request_join crewPublicId={} userPublicId={}", crewPublicId, userPrincipal.getPublicId());
CrewJoinRequestInternalResponse response = crewJoinUseCase.createJoinRequest(crewPublicId, userPrincipal.getInternalId(), request);
return ResponseEntity.ok(
SuccessResponse.of(
CrewHttpResponseCode.JOIN_REQUEST_CREATED,
response.toPublicCreateResponse(userPrincipal.getPublicId())));
log.info("action=request_join crewPublicId={} userPublicId={}", crewPublicId, currentUserPublicId);
SuccessResponse<CreateJoinRequestResponse> response = crewJoinUseCase.createJoinRequest(crewPublicId, currentUserPublicId, request);
return ResponseEntity.status(HttpStatus.CREATED).body(response);
}

@DeleteMapping("/{crewPublicId}/join-requests")
public ResponseEntity<SuccessResponse<CancelJoinRequestResponse>> cancelJoinRequest(
@PathVariable String crewPublicId,
@CurrentUser UserPrincipal userPrincipal
@CurrentUser String currentUserPublicId
) {
log.info("action=cancel_join_request crewPublicId={} userPublicId={}", crewPublicId, userPrincipal.getPublicId());
CrewJoinRequestInternalResponse response = crewJoinUseCase.cancelJoinRequest(crewPublicId, userPrincipal.getInternalId());
return ResponseEntity.ok(
SuccessResponse.of(
CrewHttpResponseCode.JOIN_REQUEST_CANCELLED,
response.toPublicCancelResponse()
)
);
log.info("action=cancel_join_request crewPublicId={} userPublicId={}", crewPublicId, currentUserPublicId);
SuccessResponse<CancelJoinRequestResponse> response = crewJoinUseCase.cancelJoinRequest(crewPublicId, currentUserPublicId);
return ResponseEntity.status(HttpStatus.OK).body(response);
}

@GetMapping("/{crewPublicId}/join-requests")
public ResponseEntity<SuccessResponse<List<FetchJoinRequestResponse>>> getJoinRequests(
@PathVariable String crewPublicId,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int limit,
@CurrentUser UserPrincipal userPrincipal
@CurrentUser String currentUserPublicId
) {
log.info("action=get_join_requests_start crewPublicId={} page={} limit={}", crewPublicId, page, limit);

List<FetchJoinRequestResponse> responses = crewJoinUseCase.getJoinRequests(
SuccessResponse<List<FetchJoinRequestResponse>> responses = crewJoinUseCase.getJoinRequests(
crewPublicId,
PageRequest.of(page, limit),
userPrincipal.getInternalId()
currentUserPublicId
);

log.info("action=get_join_requests_complete crewPublicId={} page={} limit={} result_count={}",
crewPublicId, page, limit, responses.size());
return ResponseEntity.ok(
SuccessResponse.of(
CrewHttpResponseCode.JOIN_REQUEST_FETCHED,
responses
)
);
crewPublicId, page, limit, responses.getPayload().size());
return ResponseEntity.status(HttpStatus.OK).body(responses);
}

@PutMapping("/{crewPublicId}/join-requests/{requestId}")
public ResponseEntity<SuccessResponse<ReviewJoinRequestResponse>> reviewJoinRequest(
@PathVariable String crewPublicId,
@PathVariable Integer requestId,
@CurrentUser UserPrincipal userPrincipal,
@CurrentUser String currentUserPublicId,
@Valid @RequestBody ReviewJoinRequest request
) {
log.info("action=process_join_request_start crewPublicId={} requestId={} status={}",
crewPublicId, requestId, request.getStatus());

ReviewJoinRequestResponse response = crewJoinUseCase.reviewJoinRequest(
SuccessResponse<ReviewJoinRequestResponse> response = crewJoinUseCase.reviewJoinRequest(
crewPublicId,
requestId,
CrewJoinRequestStatus.valueOf(request.getStatus()),
userPrincipal.getInternalId()
currentUserPublicId
);

log.info("action=process_join_request_complete crewPublicId={} requestId={}",
crewPublicId, requestId);
return ResponseEntity.ok(
SuccessResponse.of(
CrewHttpResponseCode.JOIN_REQUEST_REVIEWED,
response
)
);
return ResponseEntity.status(HttpStatus.OK).body(response);
}

@GetMapping("/{crewPublicId}/members")
public ResponseEntity<SuccessResponse<List<FetchMemberResponse>>> getMembers(
@PathVariable String crewPublicId,
@CurrentUser String currentUserPublicId,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int limit
) {
log.info("action=get_members_request_start crewPublicId={} currentUserPublicId={}",
crewPublicId, currentUserPublicId);

SuccessResponse<List<FetchMemberResponse>> response =
crewMemberUseCase.getMembers(crewPublicId, currentUserPublicId, PageRequest.of(page, limit));

log.info("action=get_members_request_end crewPublicId={} currentUserPublicId={}",
crewPublicId, currentUserPublicId);
return ResponseEntity.status(HttpStatus.OK).body(response);
}

@DeleteMapping("/{crewPublicId}/members/{userPublicId}")
public ResponseEntity<SuccessResponse<KickMemberResponse>> kickMember(
@PathVariable String crewPublicId,
@PathVariable String userPublicId,
@CurrentUser String currentUserPublicId
) {
log.info("action=remove_member_request_start crewPublicId={} userPublicId={} currentUserPublicId={}",
crewPublicId, userPublicId, currentUserPublicId);

SuccessResponse<KickMemberResponse> response =
crewMemberUseCase.kickMember(crewPublicId, currentUserPublicId, userPublicId);

log.info("action=remove_member_request_end crewPublicId={} userPublicId={} currentUserPublicId={}",
crewPublicId, userPublicId, currentUserPublicId);
return ResponseEntity.status(HttpStatus.OK).body(response);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ public enum CrewErrorCode implements CustomResponseCode {
SUSPENDED_CREW("CEH4004", "Suspended crew", "Suspended crew", HttpStatus.BAD_REQUEST),
INVALID_JOIN_REQUEST_STATUS("CEH4005", "Invalid join request status", "Invalid join request status", HttpStatus.BAD_REQUEST),
JOIN_REQUEST_ALREADY_PROCESSED("CEH4006", "Join request already processed", "Join request already processed", HttpStatus.BAD_REQUEST),
NOT_CREW_MEMBER("CEH4007", "User is not a crew member", "User is not a crew member", HttpStatus.BAD_REQUEST),


// 403
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ public enum CrewHttpResponseCode implements CustomResponseCode {
JOIN_REQUEST_CANCELLED("CSH2008", HttpStatus.OK, "크루 가입 요청 취소 성공", "크루 가입 요청 취소 성공"),
JOIN_REQUEST_FETCHED("CSH2009", HttpStatus.OK, "크루 가입 요청 조회 성공", "크루 가입 요청 조회 성공"),
JOIN_REQUEST_REVIEWED("CSH2010", HttpStatus.OK, "크루 가입 요청 처리 성공", "크루 가입 요청 처리 성공"),

KICK_MEMBER_SUCCESS("CSH2031", HttpStatus.OK, "크루 멤버 추방 성공", "크루 멤버 추방 성공"),
GET_MEMBERS_SUCCESS("CSH2032", HttpStatus.OK, "크루 멤버 리스트 조회 성공", "크루 멤버 리스트 조회 성공"),
;

private final String code;
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.run_us.server.domains.crew.controller.model.response;

import com.run_us.server.domains.crew.domain.CrewMembership;
import com.run_us.server.domains.user.domain.User;
import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
public class FetchMemberResponse {
private String nickname;
private String publicId;
private String profileImg;
private String role;
private int runningDistance;

public static FetchMemberResponse from(User user, CrewMembership crewMembership, int runningDistance) {
return new FetchMemberResponse(
user.getProfile().getNickname(),
user.getPublicId(),
user.getProfile().getImgUrl(),
crewMembership.getRole().name(),
runningDistance
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.run_us.server.domains.crew.controller.model.response;

import lombok.Builder;
import lombok.Getter;

@Getter
@Builder
public class KickMemberResponse {
private String userPublicId;
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package com.run_us.server.domains.crew.controller.model.response;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;

@Getter
@AllArgsConstructor
@Builder
public class ReviewJoinRequestResponse {
private Integer requestId;
}
5 changes: 5 additions & 0 deletions src/main/java/com/run_us/server/domains/crew/domain/Crew.java
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,11 @@ public void addMember(Integer userId) {
this.memberCount++;
}

public void removeMember(Integer userId) {
this.crewMemberships.removeIf(membership -> membership.getUserId().equals(userId));
this.memberCount--;
}

@Override
public void prePersist() {
this.publicId = TSID.Factory.getTsid().toString();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.run_us.server.domains.crew.domain;

import com.run_us.server.global.common.resolver.DomainPrincipal;

public class CrewPrincipal extends DomainPrincipal {
public CrewPrincipal(String publicId, Integer internalId) {
super(publicId, internalId);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,14 @@ public CrewJoinRequest reviewJoinRequest(Crew crew, Integer requestId, CrewJoinR
crew.getPublicId(), requestId);
return request;
}

@Transactional
public void removeMember(Crew crew, Integer targetUserId) {
log.debug("action=remove_member_start crewPublicId={} targetUserId={}", crew.getPublicId(), targetUserId);

crew.removeMember(targetUserId);
crewRepository.save(crew);

log.debug("action=remove_member_end crewPublicId={} targetUserId={}", crew.getPublicId(), targetUserId);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -68,4 +68,28 @@ public void validateCanReviewJoinRequest(Integer userId, CrewJoinRequestStatus s
throw new CrewException(CrewErrorCode.SUSPENDED_CREW);
}
}

public void validateCanKickMember(Integer userId, Integer targetMemberId, Crew crew) {
if(!crew.getOwner().getId().equals(userId)) {
throw new CrewException(CrewErrorCode.CREW_NOT_FOUND);
}

if (!crewRepository.existsMembershipByCrewIdAndUserId(crew.getId(), targetMemberId)) {
throw new CrewException(CrewErrorCode.NOT_CREW_MEMBER);
}

if(!crew.isActive()) {
throw new CrewException(CrewErrorCode.SUSPENDED_CREW);
}
}

public void validateCanFetchMembers(Integer userId, Crew crew) {
if(!crew.getOwner().getId().equals(userId)) {
throw new CrewException(CrewErrorCode.CREW_NOT_FOUND);
}

if(!crew.isActive()) {
throw new CrewException(CrewErrorCode.SUSPENDED_CREW);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package com.run_us.server.domains.crew.service.resolver;

import com.run_us.server.domains.crew.controller.model.enums.CrewErrorCode;
import com.run_us.server.domains.crew.controller.model.enums.CrewException;
import com.run_us.server.domains.crew.domain.Crew;
import com.run_us.server.domains.crew.domain.CrewPrincipal;
import com.run_us.server.domains.crew.repository.CrewRepository;
import com.run_us.server.global.common.cache.InMemoryCache;
import com.run_us.server.global.common.resolver.DomainIdResolver;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import java.time.Duration;

@Service
@RequiredArgsConstructor
public class CrewIdResolver implements DomainIdResolver<CrewPrincipal> {
private final CrewRepository crewRepository;
private final InMemoryCache<String, CrewPrincipal> principalCache;

private static final Duration CACHE_TTL = Duration.ofMinutes(30);

@Override
public CrewPrincipal resolve(String publicId) {
return principalCache.get(publicId)
.orElseGet(() -> loadAndCacheCrewPrincipal(publicId));
}

@Override
public CrewPrincipal resolve(Integer internalId) {
Crew crew = crewRepository.findById(internalId)
.orElseThrow(() -> new CrewException(CrewErrorCode.CREW_NOT_FOUND));
return new CrewPrincipal(crew.getPublicId(), internalId);
}

private CrewPrincipal loadAndCacheCrewPrincipal(String publicId) {
Crew crew = crewRepository.findByPublicId(publicId)
.orElseThrow(() -> new CrewException(CrewErrorCode.CREW_NOT_FOUND));

CrewPrincipal principal = new CrewPrincipal(publicId, crew.getId());
principalCache.put(publicId, principal, CACHE_TTL);
return principal;
}
}
Loading
Loading