Skip to content
Open
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 @@ -27,6 +27,16 @@ public void commence(
HttpServletResponse response,
AuthenticationException authException
) throws IOException {
if (request.getRequestURI().matches("/api/v1/parties/[^/]+/participants")) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setCharacterEncoding("UTF-8");
response.getWriter().write(
objectMapper.writeValueAsString(ApiResponse.fail("로그인이 필요합니다."))
);
return;
}
Comment on lines +30 to +38

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate file =="
fd -a 'JwtAuthenticationEntryPoint\.java$' . || true

echo "== locate participant endpoints =="
rg -n 'participants|delivery-parties|parties' src/main/java src/test/java --glob '*java' || true

echo "== git status =="
git status --short || true

Repository: Leets-Official/TDD-BE

Length of output: 8657


🏁 Script executed:

#!/bin/bash
set -euo pipefail

entrypoint=$(fd 'JwtAuthenticationEntryPoint\.java$' . | head -n 1)
echo "== entrypoint: $entrypoint =="
if [ -n "$entrypoint" ]; then
  wc -l "$entrypoint"
  cat -n "$entrypoint"
fi

echo "== candidate controller files =="
rg -l '`@RequestMapping`|`@GetMapping`|participants|delivery-parties' src/main/java --glob '*java' | head -n 50 || true

echo "== participant mappings outline/text snippets =="
for f in $(rg -l 'participants|delivery-parties' src/main/java --glob '*java' | head -n 20); do
  echo "--- $f ---"
  wc -l "$f"
  rg -n -C 4 'participants|delivery-parties|parties' "$f" || true
done

echo "== regex-only parse verifier from source =="
python3 - <<'PY'
import re, pathlib, sys
entrypoints=list(pathlib.Path('.').rglob('JwtAuthenticationEntryPoint.java'))
for p in entrypoints:
    text=p.read_text()
    m=re.search(r'request\.getRequestURI\(\)\.matches\("([^"]+)"\)', text)
    print(f"{p}: {m.group(1) if m else 'no route regex'}")
    if m:
        pattern=m.group(1)
        for path in [
            "/api/v1/parties/abc123/participants",
            "/api/v1/delivery-parties/abc123/participants",
            "/api/v1/parties/abc123",
        ]:
            print(f"  {path} -> {re.fullmatch(pattern, path)}")
PY

Repository: Leets-Official/TDD-BE

Length of output: 17286


Handle both registered participant-list routes.

DeliveryPartyController maps {partyId}/participants under both /api/v1/parties and /api/v1/delivery-parties, but this exception handler only matches the former. Missing-token requests to /api/v1/delivery-parties/{partyId}/participants fall through to the generic JWT error response instead of returning “로그인이 필요합니다.”

Proposed fix
-        if (request.getRequestURI().matches("/api/v1/parties/[^/]+/participants")) {
+        if (request.getRequestURI().matches(
+                "/api/v1/(?:parties|delivery-parties)/[^/]+/participants"
+        )) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (request.getRequestURI().matches("/api/v1/parties/[^/]+/participants")) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setCharacterEncoding("UTF-8");
response.getWriter().write(
objectMapper.writeValueAsString(ApiResponse.fail("로그인이 필요합니다."))
);
return;
}
if (request.getRequestURI().matches(
"/api/v1/(?:parties|delivery-parties)/[^/]+/participants"
)) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setCharacterEncoding("UTF-8");
response.getWriter().write(
objectMapper.writeValueAsString(ApiResponse.fail("로그인이 필요합니다."))
);
return;
}
🧰 Tools
🪛 ast-grep (0.44.1)

[warning] 33-35: Avoid writing untrusted input to the HTTP response
Context: response.getWriter().write(
objectMapper.writeValueAsString(ApiResponse.fail("로그인이 필요합니다."))
)
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting').

(xss-protection-java)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/java/com/leets/tdd/global/config/JwtAuthenticationEntryPoint.java`
around lines 30 - 38, Update the request URI check in
JwtAuthenticationEntryPoint to match participant-list routes under both
/api/v1/parties and /api/v1/delivery-parties, while preserving the existing
unauthorized JSON response and Korean login-required message.


Object errorType = request.getAttribute(JwtAuthenticationFilter.JWT_ERROR_ATTRIBUTE);

UserErrorCode errorCode;
Expand Down
5 changes: 3 additions & 2 deletions src/main/java/com/leets/tdd/global/config/SecurityConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ public class SecurityConfig {
"/api/v1/auth/password-reset",
"/swagger-ui/**",
"/v3/api-docs/**",
"/api/v1/delivery-parties/**",
"/ws/**"
};

Expand All @@ -46,6 +45,8 @@ public SecurityFilterChain securityFilterChain(
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(auth -> auth
.requestMatchers(PUBLIC_ENDPOINTS).permitAll()
.requestMatchers(HttpMethod.GET, "/api/v1/delivery-parties", "/api/v1/delivery-parties/*").permitAll()
.requestMatchers(HttpMethod.POST, "/api/v1/delivery-parties", "/api/v1/parties").permitAll()
// 계정등록(회원가입 완료)은 아직 로그인 전 상태라 토큰이 없다. GET(마이페이지)은
// 인증이 필요하니 이 경로/메서드만 예외로 공개한다.
.requestMatchers(HttpMethod.POST, "/api/v1/users/me").permitAll()
Expand All @@ -62,4 +63,4 @@ public SecurityFilterChain securityFilterChain(
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@
import com.leets.tdd.party.dto.request.UpdateDeliveryPartyRequest;
import com.leets.tdd.party.dto.response.CreateDeliveryPartyResponse;
import com.leets.tdd.party.dto.response.DeliveryPartyDetailResponse;
import com.leets.tdd.party.dto.response.PartyParticipantListResponse;
import com.leets.tdd.party.service.DeliveryPartyService;
import com.leets.tdd.global.common.ApiResponse;
import com.leets.tdd.global.jwt.UserPrincipal;

import lombok.RequiredArgsConstructor;

Expand All @@ -23,7 +26,7 @@


@RestController
@RequestMapping("/api/v1/delivery-parties")
@RequestMapping({"/api/v1/delivery-parties", "/api/v1/parties"})
@RequiredArgsConstructor
public class DeliveryPartyController {

Expand Down Expand Up @@ -68,6 +71,17 @@ public ResponseEntity<DeliveryPartyDetailResponse> getDeliveryPartyDetail(
}


// 배달팟 참여자 목록 조회 API
@GetMapping("/{partyId}/participants")
public ResponseEntity<ApiResponse<PartyParticipantListResponse>> getPartyParticipants(
@PathVariable Long partyId,
@AuthenticationPrincipal UserPrincipal currentUser
) {
PartyParticipantListResponse response = deliveryPartyService.getPartyParticipants(partyId);
return ResponseEntity.ok(ApiResponse.success("참여자 목록 조회에 성공했습니다.", response));
}
Comment on lines +75 to +82

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate file =="
fd -a 'DeliveryPartyController.java|DeliveryPartyService.java|.*Swagger.*|.*OpenApi.*|.*ApiDoc.*' . | sed 's#^\./##' | head -h 100

echo "== target controller outline =="
if [ -f src/main/java/com/leets/tdd/party/controller/DeliveryPartyController.java ]; then
  ast-grep outline src/main/java/com/leets/tdd/party/controller/DeliveryPartyController.java || true
  echo "== target controller relevant section =="
  cat -n src/main/java/com/leets/tdd/party/controller/DeliveryPartyController.java | sed -n '1,140p'
else
  echo "target file not found"
fi

echo "== swagger annotations around target =="
rg -n "@(Operation|ApiResponse|SecurityIgnore|SecurityScheme|SecurityRequirement|Tag|SecurityAspect|ApiSecurity|Authorization|ErrorResponse|SecurityResponse|SecuritySchema|SecurityResponse|SecurityResponses|SecurityResponse)" src/main/java || true

echo "== global/api docs mentions =="
rg -n "Swagger|OpenAPI|openapi|`@io`\.swagger|`@springdoc`|ApiResponse|SecurityResponse|`@SecurityAnnotation`|`@SecurityResponses`|`@Security`" src/main/java || true

Repository: Leets-Official/TDD-BE

Length of output: 245


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate file =="
fd 'DeliveryPartyController.java|DeliveryPartyService.java|.*Swagger.*|.*OpenApi.*|.*ApiDoc.*' . | sed 's#^\./##' | head -n 150

echo "== target controller outline =="
if [ -f src/main/java/com/leets/tdd/party/controller/DeliveryPartyController.java ]; then
  ast-grep outline src/main/java/com/leets/tdd/party/controller/DeliveryPartyController.java || true
  echo "== target controller relevant section =="
  cat -n src/main/java/com/leets/tdd/party/controller/DeliveryPartyController.java | sed -n '1,140p'
else
  echo "target file not found"
fi

echo "== swagger/security annotations around target =="
rg -n "@(Operation|ApiResponse|SecurityIgnore|SecurityScheme|SecurityRequirement|Tag|SecurityAspect|ApiSecurity|Authorization|ErrorResponse|SecurityResponse|SecuritySchema|SecurityResponses|SecurityResponse)" src/main/java || true

echo "== global/api docs mentions =="
rg -n "Swagger|OpenAPI|openapi|`@io`\.swagger|`@springdoc`|ApiResponse|SecurityResponse|`@SecurityAnnotation`|`@SecurityResponses`|`@Security`" src/main/java || true

Repository: Leets-Official/TDD-BE

Length of output: 31350


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== party service outline =="
ast-grep outline src/main/java/com/leets/tdd/party/service/DeliveryPartyService.java || true
echo "== party service getPartyParticipants section =="
cat -n src/main/java/com/leets/tdd/party/service/DeliveryPartyService.java | sed -n '1,220p'

echo "== party controller security annotations =="
cat -n src/main/java/com/leets/tdd/party/controller/DeliveryPartyController.java | sed -n '1,120p' | rg -n "@(Operation|ApiResponse|SecurityIgnore|SecurityScheme|SecurityRequirement|Tag|ApiResponse|SecurityResponses|SecurityResponse)" || true

echo "== comparable documented controller sections =="
for f in \
  src/main/java/com/leets/tdd/user/controller/UserController.java \
  src/main/java/com/leets/tdd/settlement/controller/SettlementController.java \
  src/main/java/com/leets/tdd/board/controller/PostController.java
do
  if [ -f "$f" ]; then
    echo "-- $f --"
    rg -n -C 8 "@(Operation|ApiResponse|SecurityRequirement|Tag)" "$f" || true
  fi
done

echo "== swagger config =="
cat -n src/main/java/com/leets/tdd/global/config/SwaggerConfig.java

Repository: Leets-Official/TDD-BE

Length of output: 24829


Add Swagger documentation for participants retrieval.

getPartyParticipants exposes participant data and accepts @AuthenticationPrincipal, but the endpoint has no @Operation, @SecurityRequirement, or failure response documentation. Add operation/response docs including the bearer-authenticated success path and unauthorized/not-found failures.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/java/com/leets/tdd/party/controller/DeliveryPartyController.java`
around lines 75 - 82, The getPartyParticipants endpoint lacks OpenAPI
documentation. Add `@Operation` and `@SecurityRequirement` annotations to document
bearer authentication, and include response documentation for successful
retrieval plus unauthorized and not-found failures, using the controller’s
existing Swagger annotation conventions.



// 배달팟 수정 API
@PutMapping("/{partyId}")
public ResponseEntity<Void> updateDeliveryParty(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.leets.tdd.party.dto.response;

import java.util.List;

public record PartyParticipantListResponse(
Long partyId,
List<PartyParticipantResponse> participants
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.leets.tdd.party.dto.response;

public record PartyParticipantResponse(
Long userId,
String nickname,
String profileImage,
String role
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
public enum PartyErrorCode implements ErrorCode {

PARTY_NOT_FOUND(HttpStatus.NOT_FOUND, "존재하지 않는 배달팟입니다."),
PARTICIPANT_LIST_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, "참여자 목록 조회에 실패했습니다."),
NOT_OWNER(HttpStatus.FORBIDDEN, "배달팟을 수정할 권한이 없습니다."),
INVALID_PARTY_STATUS(
HttpStatus.BAD_REQUEST,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
package com.leets.tdd.party.service;

import com.leets.tdd.party.domain.DeliveryParty;
import com.leets.tdd.party.domain.PartyParticipant;
import com.leets.tdd.party.domain.PartyParticipantStatus;
import com.leets.tdd.party.domain.PartyStatus;
import com.leets.tdd.party.dto.request.CreateDeliveryPartyRequest;
import com.leets.tdd.party.dto.request.UpdateDeliveryPartyRequest;
import com.leets.tdd.party.dto.response.CreateDeliveryPartyResponse;
import com.leets.tdd.party.dto.response.DeliveryPartyDetailResponse;
import com.leets.tdd.party.dto.response.PartyParticipantListResponse;
import com.leets.tdd.party.dto.response.PartyParticipantResponse;
import com.leets.tdd.party.exception.PartyErrorCode;
import com.leets.tdd.party.exception.PartyException;
import com.leets.tdd.party.repository.DeliveryPartyRepository;
import com.leets.tdd.party.repository.PartyParticipantRepository;
import com.leets.tdd.settlement.domain.SettlementStatus;
import com.leets.tdd.user.domain.User;
import com.leets.tdd.user.repository.UserRepository;
Expand All @@ -17,13 +22,16 @@

import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

@Service
@RequiredArgsConstructor
public class DeliveryPartyService {

private final DeliveryPartyRepository deliveryPartyRepository;
private final PartyParticipantRepository partyParticipantRepository;
private final UserRepository userRepository;


Expand Down Expand Up @@ -103,6 +111,53 @@ public DeliveryPartyDetailResponse getDeliveryPartyDetail(Long partyId) {
}


// 배달팟 참여자 목록 조회 API
public PartyParticipantListResponse getPartyParticipants(Long partyId) {
DeliveryParty deliveryParty = deliveryPartyRepository.findById(partyId)
.orElseThrow(() -> new PartyException(PartyErrorCode.PARTY_NOT_FOUND));

List<PartyParticipant> joinedParticipants = partyParticipantRepository
.findAllByPartyIdAndStatus(partyId, PartyParticipantStatus.JOINED);

List<Long> userIds = joinedParticipants.stream()
.map(PartyParticipant::getUserId)
.filter(userId -> !userId.equals(deliveryParty.getCreatorId()))
.collect(Collectors.toList());
userIds.add(deliveryParty.getCreatorId());

Map<Long, User> usersById = userRepository.findAllByIdIn(userIds).stream()
.collect(Collectors.toMap(User::getId, Function.identity()));
User owner = usersById.get(deliveryParty.getCreatorId());
if (owner == null) {
throw new PartyException(PartyErrorCode.PARTICIPANT_LIST_FAILED);
}

List<PartyParticipantResponse> participants = new java.util.ArrayList<>();
participants.add(toParticipantResponse(owner, "OWNER"));
for (PartyParticipant participant : joinedParticipants) {
if (participant.getUserId().equals(deliveryParty.getCreatorId())) {
continue;
}
User user = usersById.get(participant.getUserId());
if (user == null) {
throw new PartyException(PartyErrorCode.PARTICIPANT_LIST_FAILED);
}
participants.add(toParticipantResponse(user, "MEMBER"));
}

return new PartyParticipantListResponse(partyId, participants);
}

private PartyParticipantResponse toParticipantResponse(User user, String role) {
return new PartyParticipantResponse(
user.getId(),
user.getNickname(),
user.getProfileImageUrl(),
role
);
}


// 배달팟 수정 API
public void updateDeliveryParty(
Long partyId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,24 @@
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.mock;

import com.leets.tdd.party.domain.DeliveryParty;
import com.leets.tdd.party.domain.PartyParticipant;
import com.leets.tdd.party.domain.PartyParticipantRole;
import com.leets.tdd.party.domain.PartyParticipantStatus;
import com.leets.tdd.party.domain.PartyStatus;
import com.leets.tdd.party.dto.request.UpdateDeliveryPartyRequest;
import com.leets.tdd.party.dto.response.DeliveryPartyDetailResponse;
import com.leets.tdd.party.dto.response.PartyParticipantListResponse;
import com.leets.tdd.party.exception.PartyException;
import com.leets.tdd.party.repository.DeliveryPartyRepository;
import com.leets.tdd.party.repository.PartyParticipantRepository;
import com.leets.tdd.settlement.domain.SettlementStatus;
import com.leets.tdd.user.domain.User;
import com.leets.tdd.user.repository.UserRepository;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
Expand All @@ -26,6 +35,12 @@ class DeliveryPartyServiceTest {
@Mock
private DeliveryPartyRepository deliveryPartyRepository;

@Mock
private PartyParticipantRepository partyParticipantRepository;

@Mock
private UserRepository userRepository;

@InjectMocks
private DeliveryPartyService deliveryPartyService;

Expand Down Expand Up @@ -146,4 +161,45 @@ class DeliveryPartyServiceTest {
assertThat(deliveryParty.getOrderExpectedAt())
.isEqualTo(LocalDateTime.of(2026, 7, 25, 20, 0));
}


@Test
void 배달팟_참여자_목록_조회_성공() {
// given
DeliveryParty party = new DeliveryParty(
1L, 1L, "치킨", "", 2, 4, LocalDateTime.now().plusHours(1),
PartyStatus.RECRUITING, null, SettlementStatus.NONE, null, null, null,
LocalDateTime.now(), LocalDateTime.now());
PartyParticipant member = new PartyParticipant(
1L, 2L, PartyParticipantRole.MEMBER,
PartyParticipantStatus.JOINED, LocalDateTime.now());
User owner = user(1L, "대교", "https://owner-image");
User participant = user(2L, "예서", "https://member-image");

when(deliveryPartyRepository.findById(1L)).thenReturn(Optional.of(party));
when(partyParticipantRepository.findAllByPartyIdAndStatus(1L, PartyParticipantStatus.JOINED))
.thenReturn(List.of(member));
when(userRepository.findAllByIdIn(List.of(2L, 1L))).thenReturn(List.of(owner, participant));

// when
PartyParticipantListResponse response = deliveryPartyService.getPartyParticipants(1L);

// then
assertThat(response.partyId()).isEqualTo(1L);
assertThat(response.participants()).hasSize(2);
assertThat(response.participants().get(0))
.extracting("userId", "nickname", "profileImage", "role")
.containsExactly(1L, "대교", "https://owner-image", "OWNER");
assertThat(response.participants().get(1))
.extracting("userId", "nickname", "profileImage", "role")
.containsExactly(2L, "예서", "https://member-image", "MEMBER");
}

private User user(Long id, String nickname, String profileImageUrl) {
User user = mock(User.class);
when(user.getId()).thenReturn(id);
when(user.getNickname()).thenReturn(nickname);
when(user.getProfileImageUrl()).thenReturn(profileImageUrl);
return user;
}
}
Loading