diff --git a/src/main/java/com/run_us/server/domains/crew/controller/CrewController.java b/src/main/java/com/run_us/server/domains/crew/controller/CrewController.java index 70abb5e9..abbb2067 100644 --- a/src/main/java/com/run_us/server/domains/crew/controller/CrewController.java +++ b/src/main/java/com/run_us/server/domains/crew/controller/CrewController.java @@ -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.*; @@ -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; @@ -30,6 +29,7 @@ public class CrewController { private final CrewJoinUseCase crewJoinUseCase; private final CreateCrewUseCase createCrewUseCase; + private final CrewMemberUseCase crewMemberUseCase; @PostMapping @@ -47,30 +47,22 @@ public ResponseEntity> createCrew( @PostMapping("/{crewPublicId}/join-requests") public ResponseEntity> 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 response = crewJoinUseCase.createJoinRequest(crewPublicId, currentUserPublicId, request); + return ResponseEntity.status(HttpStatus.CREATED).body(response); } @DeleteMapping("/{crewPublicId}/join-requests") public ResponseEntity> 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 response = crewJoinUseCase.cancelJoinRequest(crewPublicId, currentUserPublicId); + return ResponseEntity.status(HttpStatus.OK).body(response); } @GetMapping("/{crewPublicId}/join-requests") @@ -78,50 +70,75 @@ public ResponseEntity>> getJoinRe @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 responses = crewJoinUseCase.getJoinRequests( + SuccessResponse> 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> 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 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>> 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> 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> kickMember( + @PathVariable String crewPublicId, + @PathVariable String userPublicId, + @CurrentUser String currentUserPublicId + ) { + log.info("action=remove_member_request_start crewPublicId={} userPublicId={} currentUserPublicId={}", + crewPublicId, userPublicId, currentUserPublicId); + + SuccessResponse 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); } } diff --git a/src/main/java/com/run_us/server/domains/crew/controller/model/enums/CrewErrorCode.java b/src/main/java/com/run_us/server/domains/crew/controller/model/enums/CrewErrorCode.java index 1037f806..478d2cfc 100644 --- a/src/main/java/com/run_us/server/domains/crew/controller/model/enums/CrewErrorCode.java +++ b/src/main/java/com/run_us/server/domains/crew/controller/model/enums/CrewErrorCode.java @@ -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 diff --git a/src/main/java/com/run_us/server/domains/crew/controller/model/enums/CrewHttpResponseCode.java b/src/main/java/com/run_us/server/domains/crew/controller/model/enums/CrewHttpResponseCode.java index abed730b..048912f9 100644 --- a/src/main/java/com/run_us/server/domains/crew/controller/model/enums/CrewHttpResponseCode.java +++ b/src/main/java/com/run_us/server/domains/crew/controller/model/enums/CrewHttpResponseCode.java @@ -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; diff --git a/src/main/java/com/run_us/server/domains/crew/controller/model/response/CrewJoinRequestInternalResponse.java b/src/main/java/com/run_us/server/domains/crew/controller/model/response/CrewJoinRequestInternalResponse.java deleted file mode 100644 index 98ca9500..00000000 --- a/src/main/java/com/run_us/server/domains/crew/controller/model/response/CrewJoinRequestInternalResponse.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.run_us.server.domains.crew.controller.model.response; - -import com.run_us.server.domains.crew.domain.enums.CrewJoinRequestStatus; -import lombok.Builder; -import lombok.Getter; - -import java.time.ZonedDateTime; - -@Getter -@Builder -public class CrewJoinRequestInternalResponse { - private final Integer id; - private final String crewPublicId; - private final Integer userInternalId; - private final CrewJoinRequestStatus status; - private final ZonedDateTime requestedAt; - - public CreateJoinRequestResponse toPublicCreateResponse(String userPublicId) { - return CreateJoinRequestResponse.builder() - .requestId(this.id) - .crewPublicId(this.crewPublicId) - .userPublicId(userPublicId) - .status(this.status) - .requestedAt(this.requestedAt) - .build(); - } - - public CancelJoinRequestResponse toPublicCancelResponse() { - return CancelJoinRequestResponse.builder() - .requestId(this.id) - .build(); - } -} diff --git a/src/main/java/com/run_us/server/domains/crew/controller/model/response/FetchMemberResponse.java b/src/main/java/com/run_us/server/domains/crew/controller/model/response/FetchMemberResponse.java new file mode 100644 index 00000000..b3fc3384 --- /dev/null +++ b/src/main/java/com/run_us/server/domains/crew/controller/model/response/FetchMemberResponse.java @@ -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 + ); + } +} diff --git a/src/main/java/com/run_us/server/domains/crew/controller/model/response/KickMemberResponse.java b/src/main/java/com/run_us/server/domains/crew/controller/model/response/KickMemberResponse.java new file mode 100644 index 00000000..77e70e4c --- /dev/null +++ b/src/main/java/com/run_us/server/domains/crew/controller/model/response/KickMemberResponse.java @@ -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; +} diff --git a/src/main/java/com/run_us/server/domains/crew/controller/model/response/ReviewJoinRequestResponse.java b/src/main/java/com/run_us/server/domains/crew/controller/model/response/ReviewJoinRequestResponse.java index 4954aad6..6dd43e9d 100644 --- a/src/main/java/com/run_us/server/domains/crew/controller/model/response/ReviewJoinRequestResponse.java +++ b/src/main/java/com/run_us/server/domains/crew/controller/model/response/ReviewJoinRequestResponse.java @@ -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; } diff --git a/src/main/java/com/run_us/server/domains/crew/domain/Crew.java b/src/main/java/com/run_us/server/domains/crew/domain/Crew.java index bb40a49c..f286bb87 100644 --- a/src/main/java/com/run_us/server/domains/crew/domain/Crew.java +++ b/src/main/java/com/run_us/server/domains/crew/domain/Crew.java @@ -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(); diff --git a/src/main/java/com/run_us/server/domains/crew/domain/CrewPrincipal.java b/src/main/java/com/run_us/server/domains/crew/domain/CrewPrincipal.java new file mode 100644 index 00000000..f859856c --- /dev/null +++ b/src/main/java/com/run_us/server/domains/crew/domain/CrewPrincipal.java @@ -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); + } +} diff --git a/src/main/java/com/run_us/server/domains/crew/repository/CrewRepository.java b/src/main/java/com/run_us/server/domains/crew/repository/CrewRepository.java index afc03d15..f861442a 100644 --- a/src/main/java/com/run_us/server/domains/crew/repository/CrewRepository.java +++ b/src/main/java/com/run_us/server/domains/crew/repository/CrewRepository.java @@ -1,13 +1,12 @@ package com.run_us.server.domains.crew.repository; import com.run_us.server.domains.crew.domain.Crew; -import com.run_us.server.domains.crew.domain.CrewJoinRequest; +import com.run_us.server.domains.crew.domain.CrewMembership; + import org.springframework.data.domain.PageRequest; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; -import java.time.LocalDateTime; import java.util.List; import java.util.Optional; @@ -17,4 +16,12 @@ public interface CrewRepository extends JpaRepository { @Query("SELECT EXISTS (SELECT 1 FROM Crew c JOIN c.crewMemberships m " + "WHERE c.id = :crewId AND m.userId = :userId)") boolean existsMembershipByCrewIdAndUserId(Integer crewId, Integer userId); + + @Query("SELECT m FROM Crew c JOIN c.crewMemberships m " + + "WHERE c.id = :crewId " + + "ORDER BY m.joinedAt DESC") + List findMembershipsByCrewId( + Integer crewId, + PageRequest pageRequest + ); } \ No newline at end of file diff --git a/src/main/java/com/run_us/server/domains/crew/service/CrewService.java b/src/main/java/com/run_us/server/domains/crew/service/CrewService.java index 0f49f207..62a01b81 100644 --- a/src/main/java/com/run_us/server/domains/crew/service/CrewService.java +++ b/src/main/java/com/run_us/server/domains/crew/service/CrewService.java @@ -4,6 +4,7 @@ import com.run_us.server.domains.crew.controller.model.enums.CrewErrorCode; import com.run_us.server.domains.crew.domain.Crew; import com.run_us.server.domains.crew.domain.CrewJoinRequest; +import com.run_us.server.domains.crew.domain.CrewMembership; import com.run_us.server.domains.crew.domain.enums.CrewJoinRequestStatus; import com.run_us.server.domains.crew.domain.enums.CrewJoinType; import com.run_us.server.domains.crew.repository.CrewJoinRequestRepository; @@ -36,6 +37,12 @@ public List getJoinRequests(Crew crew, PageRequest pageRequest) return crewJoinRequestRepository.findAllByCrewId(crew.getId(), pageRequest).getContent(); } + @Transactional(readOnly = true) + public List getMemberships(Crew crew, PageRequest pageRequest) { + log.debug("action=get_memberships_start crewId={}", crew.getPublicId()); + return crewRepository.findMembershipsByCrewId(crew.getId(), pageRequest); + } + @Transactional public CrewJoinRequest createJoinRequest(Crew crew, Integer userInternalId, String answer) { log.debug("action=create_join_request crewPublicId={} userInternalId={}", crew.getPublicId(), userInternalId); @@ -77,4 +84,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); + } } \ No newline at end of file diff --git a/src/main/java/com/run_us/server/domains/crew/service/CrewValidator.java b/src/main/java/com/run_us/server/domains/crew/service/CrewValidator.java index 01d833f0..158e0a0d 100644 --- a/src/main/java/com/run_us/server/domains/crew/service/CrewValidator.java +++ b/src/main/java/com/run_us/server/domains/crew/service/CrewValidator.java @@ -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); + } + } } \ No newline at end of file diff --git a/src/main/java/com/run_us/server/domains/crew/service/resolver/CrewIdResolver.java b/src/main/java/com/run_us/server/domains/crew/service/resolver/CrewIdResolver.java new file mode 100644 index 00000000..f095202f --- /dev/null +++ b/src/main/java/com/run_us/server/domains/crew/service/resolver/CrewIdResolver.java @@ -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 { + private final CrewRepository crewRepository; + private final InMemoryCache 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; + } +} diff --git a/src/main/java/com/run_us/server/domains/crew/service/usecase/CrewJoinUseCase.java b/src/main/java/com/run_us/server/domains/crew/service/usecase/CrewJoinUseCase.java index de6423f7..48265b9a 100644 --- a/src/main/java/com/run_us/server/domains/crew/service/usecase/CrewJoinUseCase.java +++ b/src/main/java/com/run_us/server/domains/crew/service/usecase/CrewJoinUseCase.java @@ -1,17 +1,19 @@ package com.run_us.server.domains.crew.service.usecase; import com.run_us.server.domains.crew.controller.model.request.CreateJoinRequest; -import com.run_us.server.domains.crew.controller.model.response.CrewJoinRequestInternalResponse; +import com.run_us.server.domains.crew.controller.model.response.CancelJoinRequestResponse; +import com.run_us.server.domains.crew.controller.model.response.CreateJoinRequestResponse; import com.run_us.server.domains.crew.controller.model.response.FetchJoinRequestResponse; import com.run_us.server.domains.crew.controller.model.response.ReviewJoinRequestResponse; import com.run_us.server.domains.crew.domain.enums.CrewJoinRequestStatus; +import com.run_us.server.global.common.SuccessResponse; import org.springframework.data.domain.PageRequest; import java.util.List; public interface CrewJoinUseCase { - CrewJoinRequestInternalResponse createJoinRequest(String crewPublicId, Integer userId, CreateJoinRequest request); - CrewJoinRequestInternalResponse cancelJoinRequest(String crewPublicId, Integer userId); - List getJoinRequests(String crewPublicId, PageRequest pageRequest, Integer userId); - ReviewJoinRequestResponse reviewJoinRequest(String crewPublicId, Integer requestId, CrewJoinRequestStatus status, Integer userId); + SuccessResponse createJoinRequest(String crewPublicId, String userPublicId, CreateJoinRequest request); + SuccessResponse cancelJoinRequest(String crewPublicId, String userPublicId); + SuccessResponse> getJoinRequests(String crewPublicId, PageRequest pageRequest, String userPublicId); + SuccessResponse reviewJoinRequest(String crewPublicId, Integer requestId, CrewJoinRequestStatus status, String userPublicId); } diff --git a/src/main/java/com/run_us/server/domains/crew/service/usecase/CrewJoinUseCaseImpl.java b/src/main/java/com/run_us/server/domains/crew/service/usecase/CrewJoinUseCaseImpl.java index 367a9112..1dc79342 100644 --- a/src/main/java/com/run_us/server/domains/crew/service/usecase/CrewJoinUseCaseImpl.java +++ b/src/main/java/com/run_us/server/domains/crew/service/usecase/CrewJoinUseCaseImpl.java @@ -1,16 +1,23 @@ package com.run_us.server.domains.crew.service.usecase; +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.response.CrewJoinRequestInternalResponse; +import com.run_us.server.domains.crew.controller.model.response.CancelJoinRequestResponse; +import com.run_us.server.domains.crew.controller.model.response.CreateJoinRequestResponse; import com.run_us.server.domains.crew.controller.model.response.FetchJoinRequestResponse; import com.run_us.server.domains.crew.controller.model.response.ReviewJoinRequestResponse; import com.run_us.server.domains.crew.domain.Crew; import com.run_us.server.domains.crew.domain.CrewJoinRequest; +import com.run_us.server.domains.crew.domain.CrewPrincipal; import com.run_us.server.domains.crew.domain.enums.CrewJoinRequestStatus; import com.run_us.server.domains.crew.service.CrewService; import com.run_us.server.domains.crew.service.CrewValidator; +import com.run_us.server.domains.crew.service.resolver.CrewIdResolver; import com.run_us.server.domains.user.domain.User; +import com.run_us.server.domains.user.domain.UserPrincipal; import com.run_us.server.domains.user.service.UserService; +import com.run_us.server.domains.user.service.resolver.UserIdResolver; +import com.run_us.server.global.common.SuccessResponse; import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -25,89 +32,125 @@ @Service @RequiredArgsConstructor public class CrewJoinUseCaseImpl implements CrewJoinUseCase { + private final CrewService crewService; private final CrewValidator crewValidator; private final UserService userService; + private final CrewIdResolver crewIdResolver; + private final UserIdResolver userIdResolver; @Override @Transactional - public CrewJoinRequestInternalResponse createJoinRequest(String crewPublicId, Integer userInternalId, CreateJoinRequest request) { - log.debug("action=create_join_request_start crewPublicId={} userInternalId={}", crewPublicId, userInternalId); + public SuccessResponse createJoinRequest( + String crewPublicId, String userPublicId, CreateJoinRequest request) { + CrewPrincipal crewPrincipal = crewIdResolver.resolve(crewPublicId); + UserPrincipal userPrincipal = userIdResolver.resolve(userPublicId); - Crew crew = crewService.getCrewByPublicId(crewPublicId); - crewValidator.validateCanJoinCrew(userInternalId, crew); + log.info("action=create_join_request_start crewPublicId={} userPublicId={}", + crewPrincipal.getPublicId(), userPrincipal.getPublicId()); + + Crew crew = crewService.getCrewByPublicId(crewPrincipal.getPublicId()); + crewValidator.validateCanJoinCrew(userPrincipal.getInternalId(), crew); - CrewJoinRequest joinRequest = crewService.createJoinRequest(crew, userInternalId, request.getAnswer()); + CrewJoinRequest joinRequest = crewService.createJoinRequest(crew, + userPrincipal.getInternalId(), request.getAnswer()); - log.debug("action=create_join_request_end crewPublicId={} userInternalId={} status={}", - crewPublicId, userInternalId, joinRequest.getStatus()); + log.info("action=create_join_request_end crewPublicId={} userPublicId={} status={}", + crewPrincipal.getPublicId(), userPrincipal.getPublicId(), joinRequest.getStatus()); - return CrewJoinRequestInternalResponse.builder() - .crewPublicId(crewPublicId) - .userInternalId(userInternalId) + return SuccessResponse.of( + CrewHttpResponseCode.JOIN_REQUEST_CREATED, + CreateJoinRequestResponse.builder() + .crewPublicId(crewPrincipal.getPublicId()) + .userPublicId(userPrincipal.getPublicId()) .status(joinRequest.getStatus()) .requestedAt(joinRequest.getRequestedAt()) - .build(); + .requestId(joinRequest.getId()) + .build() + ); } @Override @Transactional - public CrewJoinRequestInternalResponse cancelJoinRequest(String crewPublicId, Integer userInternalId) { - log.debug("action=cancel_join_request_start crewPublicId={} userInternalId={}", crewPublicId, userInternalId); + public SuccessResponse cancelJoinRequest( + String crewPublicId, String userPublicId) { + CrewPrincipal crewPrincipal = crewIdResolver.resolve(crewPublicId); + UserPrincipal userPrincipal = userIdResolver.resolve(userPublicId); + + log.info("action=cancel_join_request_start crewPublicId={} userPublicId={}", + crewPrincipal.getPublicId(), userPrincipal.getPublicId()); Crew crew = crewService.getCrewByPublicId(crewPublicId); - crewService.cancelJoinRequest(crew, userInternalId); + crewService.cancelJoinRequest(crew, userPrincipal.getInternalId()); - log.debug("action=cancel_join_request_end crewPublicId={} userInternalId={}", crewPublicId, userInternalId); + log.info("action=cancel_join_request_end crewPublicId={} userPublicId={}", + crewPrincipal.getPublicId(), userPrincipal.getPublicId()); - return CrewJoinRequestInternalResponse.builder() - .crewPublicId(crewPublicId) - .userInternalId(userInternalId) - .status(null) - .requestedAt(null) - .build(); + return SuccessResponse.of( + CrewHttpResponseCode.JOIN_REQUEST_CANCELLED, + CancelJoinRequestResponse.builder() + .requestId(null) + .build() + ); } @Override @Transactional - public List getJoinRequests(String crewPublicId, PageRequest pageRequest, Integer userInternalId) { - log.debug("action=get_join_requests crewPublicId={} userInternalId={}", crewPublicId, userInternalId); + public SuccessResponse> getJoinRequests( + String crewPublicId, PageRequest pageRequest, String userPublicId) { + CrewPrincipal crewPrincipal = crewIdResolver.resolve(crewPublicId); + UserPrincipal userPrincipal = userIdResolver.resolve(userPublicId); + + log.info("action=get_join_requests crewPublicId={} userPublicId={}", + crewPrincipal.getPublicId(), userPrincipal.getPublicId()); Crew crew = crewService.getCrewByPublicId(crewPublicId); - crewValidator.validateCanFetchJoinRequests(userInternalId, crew); + crewValidator.validateCanFetchJoinRequests(userPrincipal.getInternalId(), crew); List joinRequests = crewService.getJoinRequests(crew, pageRequest); List userIds = joinRequests.stream() - .map(CrewJoinRequest::getUserId) - .collect(Collectors.toList()); + .map(CrewJoinRequest::getUserId) + .collect(Collectors.toList()); Map userMap = userService.getUserMapByIds(userIds); - return joinRequests.stream() - .map(request -> FetchJoinRequestResponse.from(request, userMap.get(request.getUserId()))) - .collect(Collectors.toList()); + return SuccessResponse.of( + CrewHttpResponseCode.JOIN_REQUEST_FETCHED, + joinRequests.stream() + .map( + request -> FetchJoinRequestResponse.from(request, userMap.get(request.getUserId()))) + .collect(Collectors.toList()) + ); } @Override @Transactional - public ReviewJoinRequestResponse reviewJoinRequest(String crewPublicId, Integer requestId, CrewJoinRequestStatus status, Integer userInternalId) { - log.debug("action=review_join_request_start crewPublicId={} requestId={} status={}", - crewPublicId, requestId, status); + public SuccessResponse reviewJoinRequest( + String crewPublicId, Integer requestId, CrewJoinRequestStatus status, String userPublicId) { + CrewPrincipal crewPrincipal = crewIdResolver.resolve(crewPublicId); + UserPrincipal userPrincipal = userIdResolver.resolve(userPublicId); + + log.info("action=review_join_request_start crewPublicId={} requestId={} status={}", + crewPrincipal.getPublicId(), requestId, status); Crew crew = crewService.getCrewByPublicId(crewPublicId); - crewValidator.validateCanReviewJoinRequest(userInternalId, status, crew); + crewValidator.validateCanReviewJoinRequest(userPrincipal.getInternalId(), status, crew); CrewJoinRequest joinRequest = crewService.reviewJoinRequest( - crew, - requestId, - status, - userInternalId + crew, + requestId, + status, + userPrincipal.getInternalId() ); - log.debug("action=review_join_request_end crewPublicId={} requestId={}", crewPublicId, requestId); + log.info("action=review_join_request_end crewPublicId={} requestId={}", + crewPrincipal.getPublicId(), requestId); - return new ReviewJoinRequestResponse( - joinRequest.getId() - ); + return SuccessResponse.of( + CrewHttpResponseCode.JOIN_REQUEST_REVIEWED, + ReviewJoinRequestResponse.builder() + .requestId(joinRequest.getId()) + .build() + ) ; } } \ No newline at end of file diff --git a/src/main/java/com/run_us/server/domains/crew/service/usecase/CrewMemberUseCase.java b/src/main/java/com/run_us/server/domains/crew/service/usecase/CrewMemberUseCase.java new file mode 100644 index 00000000..ece1623c --- /dev/null +++ b/src/main/java/com/run_us/server/domains/crew/service/usecase/CrewMemberUseCase.java @@ -0,0 +1,12 @@ +package com.run_us.server.domains.crew.service.usecase; + +import com.run_us.server.domains.crew.controller.model.response.FetchMemberResponse; +import com.run_us.server.domains.crew.controller.model.response.KickMemberResponse; +import com.run_us.server.global.common.SuccessResponse; +import java.util.List; +import org.springframework.data.domain.PageRequest; + +public interface CrewMemberUseCase { + SuccessResponse kickMember(String crewPublicId, String userPublicId, String memberPublicId); + SuccessResponse> getMembers(String crewPublicId, String userPublicId, PageRequest pageRequest); +} diff --git a/src/main/java/com/run_us/server/domains/crew/service/usecase/CrewMemberUseCaseImpl.java b/src/main/java/com/run_us/server/domains/crew/service/usecase/CrewMemberUseCaseImpl.java new file mode 100644 index 00000000..f16bac3f --- /dev/null +++ b/src/main/java/com/run_us/server/domains/crew/service/usecase/CrewMemberUseCaseImpl.java @@ -0,0 +1,158 @@ +package com.run_us.server.domains.crew.service.usecase; + +import com.run_us.server.domains.crew.controller.model.enums.CrewHttpResponseCode; +import com.run_us.server.domains.crew.controller.model.response.FetchMemberResponse; +import com.run_us.server.domains.crew.controller.model.response.KickMemberResponse; +import com.run_us.server.domains.crew.domain.Crew; +import com.run_us.server.domains.crew.domain.CrewMembership; +import com.run_us.server.domains.crew.domain.CrewPrincipal; +import com.run_us.server.domains.crew.service.CrewService; +import com.run_us.server.domains.crew.service.CrewValidator; +import com.run_us.server.domains.crew.service.resolver.CrewIdResolver; +import com.run_us.server.domains.running.record.service.RecordQueryService; +import com.run_us.server.domains.user.domain.User; +import com.run_us.server.domains.user.domain.UserPrincipal; +import com.run_us.server.domains.user.service.UserService; +import com.run_us.server.domains.user.service.resolver.UserIdResolver; +import com.run_us.server.global.common.SuccessResponse; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +import org.springframework.data.domain.PageRequest; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Slf4j +@Service +@RequiredArgsConstructor +public class CrewMemberUseCaseImpl implements CrewMemberUseCase { + private final CrewService crewService; + private final CrewValidator crewValidator; + private final CrewIdResolver crewIdResolver; + private final UserIdResolver userIdResolver; + private final UserService userService; + + @Override + @Transactional + public SuccessResponse kickMember( + String crewPublicId, String actionUserPublicId, String targetMemberPublicId) { + CrewPrincipal crewPrincipal = crewIdResolver.resolve(crewPublicId); + UserPrincipal actionUserPrincipal = userIdResolver.resolve(actionUserPublicId); + UserPrincipal targetMemberPrincipal = userIdResolver.resolve(targetMemberPublicId); + + log.info( + "action=kick_member_start crewPublicId={} actionUserPublicId={} targetMemberPublicId={}", + crewPrincipal.getPublicId(), actionUserPrincipal.getPublicId(), + targetMemberPrincipal.getPublicId()); + + Crew crew = crewService.getCrewByPublicId(crewPrincipal.getPublicId()); + crewValidator.validateCanKickMember( + actionUserPrincipal.getInternalId(), targetMemberPrincipal.getInternalId(), crew); + crewService.removeMember(crew, targetMemberPrincipal.getInternalId()); + + log.info( + "action=kick_member_end crewPublicId={} actionUserPublicId={} targetMemberPublicId={}", + crewPrincipal.getPublicId(), actionUserPrincipal.getPublicId(), + targetMemberPrincipal.getPublicId()); + + return SuccessResponse.of( + CrewHttpResponseCode.KICK_MEMBER_SUCCESS, + KickMemberResponse.builder() + .userPublicId(targetMemberPrincipal.getPublicId()) + .build() + ); + } + + @Override + @Transactional(readOnly = true) + public SuccessResponse> getMembers( + String crewPublicId, String userPublicId, PageRequest pageRequest){ + CrewPrincipal crewPrincipal = crewIdResolver.resolve(crewPublicId); + UserPrincipal userPrincipal = userIdResolver.resolve(userPublicId); + + log.info("action=get_members_start crewPublicId={} userPublicId={} page={} size={}", + crewPrincipal.getPublicId(), userPrincipal.getPublicId(), + pageRequest.getPageNumber(), pageRequest.getPageSize()); + + Crew crew = crewService.getCrewByPublicId(crewPrincipal.getPublicId()); + crewValidator.validateCanFetchMembers(userPrincipal.getInternalId(), crew); + + List memberResponses = getMemberResponses(crew, pageRequest); + + log.info("action=get_members_end crewPublicId={} userPublicId={}", + crewPrincipal.getPublicId(), userPrincipal.getPublicId()); + + return SuccessResponse.of( + CrewHttpResponseCode.GET_MEMBERS_SUCCESS, + memberResponses + ); + } + + private List getMemberResponses( + Crew crew, + PageRequest pageRequest + ) { + List memberships = crewService.getMemberships(crew, pageRequest); + + if (memberships.isEmpty()) { + return Collections.emptyList(); + } + + List userIds = getUserIds(memberships); + + Map userMap = userService.getUserMapByIds(userIds); + Map distanceMap = getUserTotalDistance(userMap); + + return mapToMemberResponses(memberships, userMap, distanceMap); + } + + private List getUserIds(List memberships) { + return memberships.stream() + .map(CrewMembership::getUserId) + .toList(); + } + + private Map getUserTotalDistance(Map userMap) { + return userMap.entrySet().parallelStream() + .collect(Collectors.toMap( + Map.Entry::getKey, + entry -> getDistanceOrDefault(entry.getValue()) + )); + } + + private int getDistanceOrDefault(User user) { + if (user == null || user.getProfile() == null) { + return 0; + } + Integer distance = user.getProfile().getTotalDistance(); + return distance != null ? distance : 0; + } + + private List mapToMemberResponses( + List memberships, + Map userMap, + Map distanceMap + ) { + return memberships.stream() + .map(membership -> createMemberResponse(membership, userMap, distanceMap)) + .toList(); + } + + private FetchMemberResponse createMemberResponse( + CrewMembership membership, + Map userMap, + Map distanceMap + ) { + Integer userId = membership.getUserId(); + return FetchMemberResponse.from( + userMap.get(userId), + membership, + distanceMap.getOrDefault(userId, 0) + ); + } +} diff --git a/src/main/java/com/run_us/server/domains/user/domain/UserPrincipal.java b/src/main/java/com/run_us/server/domains/user/domain/UserPrincipal.java new file mode 100644 index 00000000..d83a9325 --- /dev/null +++ b/src/main/java/com/run_us/server/domains/user/domain/UserPrincipal.java @@ -0,0 +1,9 @@ +package com.run_us.server.domains.user.domain; + +import com.run_us.server.global.common.resolver.DomainPrincipal; + +public class UserPrincipal extends DomainPrincipal { + public UserPrincipal(String publicId, Integer internalId) { + super(publicId, internalId); + } +} diff --git a/src/main/java/com/run_us/server/global/security/resolver/UserIdResolver.java b/src/main/java/com/run_us/server/domains/user/service/resolver/UserIdResolver.java similarity index 62% rename from src/main/java/com/run_us/server/global/security/resolver/UserIdResolver.java rename to src/main/java/com/run_us/server/domains/user/service/resolver/UserIdResolver.java index 07282d28..94cd355e 100644 --- a/src/main/java/com/run_us/server/global/security/resolver/UserIdResolver.java +++ b/src/main/java/com/run_us/server/domains/user/service/resolver/UserIdResolver.java @@ -1,29 +1,38 @@ -package com.run_us.server.global.security.resolver; +package com.run_us.server.domains.user.service.resolver; import com.run_us.server.domains.user.domain.User; +import com.run_us.server.domains.user.domain.UserPrincipal; import com.run_us.server.domains.user.exception.UserErrorCode; import com.run_us.server.domains.user.exception.UserException; import com.run_us.server.domains.user.repository.UserRepository; import com.run_us.server.global.common.cache.InMemoryCache; -import com.run_us.server.global.security.principal.UserPrincipal; +import com.run_us.server.global.common.resolver.DomainIdResolver; import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Component; +import org.springframework.stereotype.Service; import java.time.Duration; -@Component +@Service @RequiredArgsConstructor -public class UserIdResolver { - private final InMemoryCache principalCache; +public class UserIdResolver implements DomainIdResolver { private final UserRepository userRepository; + private final InMemoryCache principalCache; private static final Duration CACHE_TTL = Duration.ofMinutes(30); - public UserPrincipal resolveUser(String publicId) { + @Override + public UserPrincipal resolve(String publicId) { return principalCache.get(publicId) .orElseGet(() -> loadAndCacheUserPrincipal(publicId)); } + @Override + public UserPrincipal resolve(Integer internalId) { + User user = userRepository.findById(internalId) + .orElseThrow(() -> new UserException(UserErrorCode.USER_NOT_FOUND)); + return new UserPrincipal(user.getPublicId(), internalId); + } + private UserPrincipal loadAndCacheUserPrincipal(String publicId) { User user = userRepository.findByPublicId(publicId) .orElseThrow(() -> new UserException(UserErrorCode.USER_NOT_FOUND)); diff --git a/src/main/java/com/run_us/server/global/common/GlobalConst.java b/src/main/java/com/run_us/server/global/common/GlobalConst.java index a8522320..9ed44e44 100644 --- a/src/main/java/com/run_us/server/global/common/GlobalConst.java +++ b/src/main/java/com/run_us/server/global/common/GlobalConst.java @@ -3,8 +3,11 @@ import lombok.AccessLevel; import lombok.NoArgsConstructor; +import java.util.Set; import java.time.ZoneId; +import static com.run_us.server.global.common.SocketConst.WS_CONNECT_ENDPOINT; + @NoArgsConstructor(access = AccessLevel.PRIVATE) public final class GlobalConst { public static final String TIME_ZONE_ID = "Asia/Seoul"; @@ -13,4 +16,10 @@ public final class GlobalConst { public static final String WS_USER_AUTH_HEADER = "user-id"; public static final String SESSION_ATTRIBUTE_USER = "user-info"; + + public static final Set WHITE_LIST_PATHS = Set.of( + "/test/auth", + WS_CONNECT_ENDPOINT, + "/auth" + ); } diff --git a/src/main/java/com/run_us/server/global/common/resolver/DomainIdResolver.java b/src/main/java/com/run_us/server/global/common/resolver/DomainIdResolver.java new file mode 100644 index 00000000..9aba4a8e --- /dev/null +++ b/src/main/java/com/run_us/server/global/common/resolver/DomainIdResolver.java @@ -0,0 +1,6 @@ +package com.run_us.server.global.common.resolver; + +public interface DomainIdResolver { + T resolve(String publicId); + T resolve(Integer internalId); +} \ No newline at end of file diff --git a/src/main/java/com/run_us/server/global/common/resolver/DomainPrincipal.java b/src/main/java/com/run_us/server/global/common/resolver/DomainPrincipal.java new file mode 100644 index 00000000..78d183c5 --- /dev/null +++ b/src/main/java/com/run_us/server/global/common/resolver/DomainPrincipal.java @@ -0,0 +1,13 @@ +package com.run_us.server.global.common.resolver; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@EqualsAndHashCode +@RequiredArgsConstructor +public abstract class DomainPrincipal { + private final String publicId; + private final Integer internalId; +} \ No newline at end of file diff --git a/src/main/java/com/run_us/server/global/config/CacheConfig.java b/src/main/java/com/run_us/server/global/config/CacheConfig.java index 41ba821b..ee3d9b10 100644 --- a/src/main/java/com/run_us/server/global/config/CacheConfig.java +++ b/src/main/java/com/run_us/server/global/config/CacheConfig.java @@ -1,9 +1,11 @@ package com.run_us.server.global.config; +import com.run_us.server.domains.crew.domain.CrewPrincipal; +import com.run_us.server.domains.user.domain.UserPrincipal; import com.run_us.server.global.common.cache.InMemoryCache; import com.run_us.server.global.common.cache.SpringInMemoryCache; import com.run_us.server.domains.user.domain.TokenStatus; -import com.run_us.server.global.security.principal.UserPrincipal; +import com.run_us.server.global.common.resolver.DomainPrincipal; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -41,10 +43,22 @@ public InMemoryCache tokenStatusCache(TaskScheduler cacheCl } @Bean - public InMemoryCache userPrincipalCache(TaskScheduler cacheCleanupScheduler) { + public InMemoryCache userPrincipalCache( + TaskScheduler cacheCleanupScheduler + ) { return new SpringInMemoryCache<>( - cacheCleanupScheduler, - Duration.ofSeconds(cleanupIntervalSeconds) + cacheCleanupScheduler, + Duration.ofSeconds(cleanupIntervalSeconds) + ); + } + + @Bean + public InMemoryCache crewPrincipalCache( + TaskScheduler cacheCleanupScheduler + ) { + return new SpringInMemoryCache<>( + cacheCleanupScheduler, + Duration.ofSeconds(cleanupIntervalSeconds) ); } } \ No newline at end of file diff --git a/src/main/java/com/run_us/server/global/security/JwtAuthenticationFilter.java b/src/main/java/com/run_us/server/global/security/JwtAuthenticationFilter.java index 0a87fae7..b396036a 100644 --- a/src/main/java/com/run_us/server/global/security/JwtAuthenticationFilter.java +++ b/src/main/java/com/run_us/server/global/security/JwtAuthenticationFilter.java @@ -11,11 +11,15 @@ import jakarta.servlet.http.HttpServletResponse; import lombok.NonNull; import lombok.extern.slf4j.Slf4j; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.filter.OncePerRequestFilter; import java.io.IOException; +import java.util.Collections; -import static com.run_us.server.global.common.SocketConst.WS_CONNECT_ENDPOINT; +import static com.run_us.server.global.common.GlobalConst.WHITE_LIST_PATHS; @Slf4j public class JwtAuthenticationFilter extends OncePerRequestFilter { @@ -33,15 +37,23 @@ public JwtAuthenticationFilter(JwtService jwtService) { @Override protected void doFilterInternal(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull FilterChain filterChain) throws ServletException, IOException { - if (!hasValidAuthorizationHeader(request)) { - setErrorResponse(UserErrorCode.JWT_NOT_FOUND, response); - return; + try{ + if (!hasValidAuthorizationHeader(request)) { + setErrorResponse(UserErrorCode.JWT_NOT_FOUND, response); + return; + } + String jwt = extractToken(request); + if (!processToken(jwt, request, response)) { + return; + } + filterChain.doFilter(request, response); + } catch (Exception e) { + log.warn("[ERROR]JWT broken : {}", e.getMessage()); + setErrorResponse(UserErrorCode.JWT_BROKEN, response); } - String jwt = extractToken(request); - if (!processToken(jwt, request, response)) { - return; + finally { + SecurityContextHolder.clearContext(); } - filterChain.doFilter(request, response); } private boolean hasValidAuthorizationHeader(HttpServletRequest request) { @@ -56,6 +68,13 @@ private String extractToken(HttpServletRequest request) { private boolean processToken(String jwt, HttpServletRequest request, HttpServletResponse response) throws IOException { try { String publicId = jwtService.getUserIdFromAccessToken(jwt); + Authentication authentication = new UsernamePasswordAuthenticationToken( + publicId, + null, + Collections.emptyList() + ); + SecurityContextHolder.getContext().setAuthentication(authentication); + request.setAttribute("publicUserId", publicId); return true; } catch (TokenExpiredException e) { @@ -77,9 +96,8 @@ private void setErrorResponse(UserErrorCode errorCode, HttpServletResponse respo // JWT 필터는 로그인, 회원가입, 테스트용 API, 웹소켓 연결 요청에 대해서는 필터링을 하지 않는다. @Override - protected boolean shouldNotFilter(HttpServletRequest request) { - return request.getServletPath().startsWith("/test/auth") - || request.getServletPath().startsWith(WS_CONNECT_ENDPOINT) - || request.getServletPath().startsWith("/auth"); + protected boolean shouldNotFilter(@NonNull HttpServletRequest request) { + return WHITE_LIST_PATHS.stream() + .anyMatch(path -> request.getServletPath().startsWith(path)); } } diff --git a/src/main/java/com/run_us/server/global/security/principal/UserPrincipal.java b/src/main/java/com/run_us/server/global/security/principal/UserPrincipal.java deleted file mode 100644 index 3b3c65d5..00000000 --- a/src/main/java/com/run_us/server/global/security/principal/UserPrincipal.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.run_us.server.global.security.principal; - -import lombok.Getter; - -@Getter -public class UserPrincipal { - private final String publicId; - private final Integer internalId; - - public UserPrincipal(String publicId, Integer userId) { - this.publicId = publicId; - this.internalId = userId; - } -} diff --git a/src/main/java/com/run_us/server/global/security/resolver/CurrentUserArgumentResolver.java b/src/main/java/com/run_us/server/global/security/resolver/CurrentUserArgumentResolver.java index 7b6cc9dd..2f1642b6 100644 --- a/src/main/java/com/run_us/server/global/security/resolver/CurrentUserArgumentResolver.java +++ b/src/main/java/com/run_us/server/global/security/resolver/CurrentUserArgumentResolver.java @@ -5,6 +5,7 @@ import com.run_us.server.global.security.annotation.CurrentUser; import lombok.RequiredArgsConstructor; import org.springframework.core.MethodParameter; +import org.springframework.security.authentication.AnonymousAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Component; @@ -16,7 +17,6 @@ @Component @RequiredArgsConstructor public class CurrentUserArgumentResolver implements HandlerMethodArgumentResolver { - private final UserIdResolver userIdResolver; @Override public boolean supportsParameter(MethodParameter parameter) { @@ -26,13 +26,13 @@ public boolean supportsParameter(MethodParameter parameter) { @Override public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) { - String userPublicId = extractUserPublicId(); - return userIdResolver.resolveUser(userPublicId); + return extractUserPublicId(); } private String extractUserPublicId() throws UserException { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - if (authentication == null) { + if (authentication == null || !authentication.isAuthenticated() || + authentication instanceof AnonymousAuthenticationToken) { throw new UserException(UserErrorCode.JWT_BROKEN); } return authentication.getName();