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 @@ -28,7 +28,7 @@

@Tag(name = "Auth", description = "인증 관련 API")
@RestController
@RequestMapping("/api/auth")
@RequestMapping("/api/v1/auth")
@RequiredArgsConstructor
public class AuthController {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

@Tag(name = "Auth", description = "인증 관련 API")
@RestController
@RequestMapping("/api/jwt")
@RequestMapping("/api/v1/jwt")
@RequiredArgsConstructor
public class JwtController {

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package backend.feed.controller;

import java.util.Map;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
Expand All @@ -10,6 +12,7 @@
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.server.ResponseStatusException;

import backend.feed.dto.FeedApplicationResponse;
import backend.feed.dto.FeedApplyRequest;
Expand All @@ -24,7 +27,7 @@

@Tag(name = "Feed API", description = "피드 관련 API")
@RestController
@RequestMapping("/api/feeds")
@RequestMapping("/api/v1/feeds")
@RequiredArgsConstructor
public class FeedController {

Expand All @@ -50,7 +53,8 @@ public void deleteFeedItem(@PathVariable String feedId) {
}

@Operation(summary = "피드 신청")
@PostMapping("/{feedId}/apply")
@PostMapping("/{feedId}/applications")
@ResponseStatus(HttpStatus.CREATED)
public ApiResponse<FeedApplicationResponse> applyToFeed(
@PathVariable String feedId,
@RequestBody FeedApplyRequest request) {
Expand All @@ -61,10 +65,27 @@ public ApiResponse<FeedApplicationResponse> applyToFeed(
}

@Operation(summary = "피드 신청 취소")
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Issue] cancelApplication 응답 envelope 불일치

FRONTEND.md 명세:

responses:
  200:
    application/json:
      data:
        feedId: string
        status: CANCELLED

현재 ApiResponse.success()는 body 없이 반환되어 프론트가 data.feedId를 읽을 수 없습니다.

수정 예시:

Map<String, Object> body = Map.of("feedId", feedId, "status", "CANCELLED");
return ApiResponse.success(body);

@DeleteMapping("/{feedId}/apply")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void cancelApplication(@PathVariable String feedId) {
@DeleteMapping("/{feedId}/applications/me")
public ApiResponse<Map<String, String>> cancelApplication(@PathVariable String feedId) {
feedItemService.cancelApplication(feedId, "dummy-user-id");
return ApiResponse.success(Map.of(
"feedId", feedId,
"status", "CANCELLED"
));
}

@Operation(summary = "피드 북마크 추가 (미구현)")
@PostMapping("/{feedId}/bookmark")
public void addBookmark(@PathVariable String feedId) {
throw new ResponseStatusException(HttpStatus.NOT_IMPLEMENTED,
"Bookmark API not implemented yet");
}

@Operation(summary = "피드 북마크 제거 (미구현)")
@DeleteMapping("/{feedId}/bookmark")
public void removeBookmark(@PathVariable String feedId) {
throw new ResponseStatusException(HttpStatus.NOT_IMPLEMENTED,
"Bookmark API not implemented yet");
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

@Operation(summary = "신청 수락 (작성자 전용) — 펀딩 목표 달성 시 Spot 자동 생성")
Expand Down
4 changes: 2 additions & 2 deletions capstone-api/src/main/java/backend/feed/dto/AddOn.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,6 @@ public class AddOn {
@Schema(description = "과금 방식", example = "FIXED")
private AddOnMechanism mechanism;

@Schema(description = "비고")
private String note;
@Schema(description = "설명")
private String explanation;
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import java.time.LocalDateTime;

import backend.feed.entity.FeedApplication;
import backend.feed.entity.FeedApplicationRole;
import backend.feed.entity.FeedApplicationStatus;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AccessLevel;
Expand All @@ -23,6 +24,8 @@ public class FeedApplicationResponse {
private String userId;
private String proposal;
private FeedApplicationStatus status;
private FeedApplicationRole appliedRole;
private Integer deposit;
private LocalDateTime createdAt;

public static FeedApplicationResponse from(FeedApplication application) {
Expand All @@ -32,6 +35,8 @@ public static FeedApplicationResponse from(FeedApplication application) {
.userId(application.getUserId())
.proposal(application.getProposal())
.status(application.getStatus())
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Issue] FeedApplicationResponse.from() — appliedRole, deposit 항상 null

FEENTEND.md 명세:

type FeedApplication = {
    appliedRole: FeedApplicationRole;  // required
    deposit: number;                   // required
    ...
};

from() 팩토리에서 두 필드를 설정하지 않아 항상 null 응답이 나갑니다. 근본 원인은 FeedApplication 엔티티에 appliedRole, deposit 컬럼 자체가 없기 때문입니다.

후속 작업으로 분리해도 되지만, 현재 상태에서는 명세상 required 필드가 null로 내려가므로 프론트 파싱이 깨질 수 있습니다. 엔티티 컬럼 추가 + 마이그레이션 스크립트를 별도 이슈/PR로 트래킹 해주세요.

.appliedRole(application.getAppliedRole())
.deposit(application.getDeposit())
.createdAt(application.getCreatedAt())
.build();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package backend.feed.dto;

import backend.feed.entity.FeedApplicationRole;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
Expand All @@ -14,4 +15,10 @@ public class FeedApplyRequest {

@Schema(description = "신청 메시지", example = "저는 공예 경험이 5년 있습니다.")
private String proposal;

@Schema(description = "신청 역할 (SUPPORTER | PARTNER)", example = "SUPPORTER")
private FeedApplicationRole role;

@Schema(description = "보증금", example = "10000")
private Integer deposit;
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package backend.feed.dto;

import com.fasterxml.jackson.annotation.JsonProperty;

import backend.global.enums.FeedAuthorRole;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AccessLevel;
Expand All @@ -23,7 +21,6 @@ public class FeedAuthorProfile {
@Schema(description = "작성자 닉네임", example = "유저1")
private String nickname;

@JsonProperty("avatar_url")
@Schema(description = "작성자 아바타 URL")
private String avatarUrl;

Expand Down
14 changes: 14 additions & 0 deletions capstone-api/src/main/java/backend/feed/dto/FeedItemResponse.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package backend.feed.dto;

import backend.feed.entity.FeedApplicationRole;
import backend.feed.entity.FeedApplicationStatus;
import backend.feed.entity.FeedItem;
import backend.global.enums.FeedCategory;
Expand Down Expand Up @@ -70,6 +71,15 @@ public class FeedItemResponse {
@Schema(description = "내 신청 상태")
private FeedApplicationStatus myApplicationStatus;

@Schema(description = "내 신청 역할 (SUPPORTER | PARTNER)")
private FeedApplicationRole myApplicationRole;

@Schema(description = "내 신청 보증금")
private Integer myApplicationDeposit;

@Schema(description = "대여 가능 여부", example = "false")
private Boolean isRentable;

Comment on lines +74 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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Populate newly added response fields in from(...).

myApplicationRole, myApplicationDeposit, and isRentable are declared but never set in the builder, so clients always get null for them.

Proposed fix
 public static FeedItemResponse from(FeedItem feedItem, Long applicantCount, Boolean isBookmarked,
-		FeedApplicationStatus myApplicationStatus, FeedAuthorProfile authorProfile) {
+		FeedApplicationStatus myApplicationStatus, FeedApplicationRole myApplicationRole,
+		Integer myApplicationDeposit, Boolean isRentable, FeedAuthorProfile authorProfile) {
 	return FeedItemResponse.builder()
 			...
 			.myApplicationStatus(myApplicationStatus)
+			.myApplicationRole(myApplicationRole)
+			.myApplicationDeposit(myApplicationDeposit)
+			.isRentable(isRentable)
 			.authorProfile(authorProfile)
 			...
 			.build();
 }

Also applies to: 102-127

🤖 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 `@capstone-api/src/main/java/backend/feed/dto/FeedItemResponse.java` around
lines 74 - 82, The DTO fields myApplicationRole, myApplicationDeposit, and
isRentable are declared but not populated in the static from(...) builder;
update FeedItemResponse.from(...) to set these three fields from the source
Feed/FeedApplication/availability data (use the same source objects used for
other fields), e.g. determine the current user's FeedApplication role and
deposit and compute rental availability, then call
builder.myApplicationRole(...), builder.myApplicationDeposit(...), and
builder.isRentable(...) before build(); reference FeedItemResponse.from, the
builder on FeedItemResponse, and the field names
myApplicationRole/myApplicationDeposit/isRentable when making the change.

@Schema(description = "작성자 프로필")
private FeedAuthorProfile authorProfile;

Expand Down Expand Up @@ -109,6 +119,10 @@ public static FeedItemResponse from(FeedItem feedItem, Long applicantCount, Bool
.applicantCount(feedItem.getType() == PostType.REQUEST ? applicantCount : null)
.isBookmarked(isBookmarked)
.myApplicationStatus(myApplicationStatus)
// myApplicationRole/Deposit, isRentable: 현재 인증/대여 모델 미구현으로 null. 별도 PR에서 채움.
.myApplicationRole(null)
.myApplicationDeposit(null)
.isRentable(null)
.authorProfile(authorProfile)
.spotId(feedItem.getSpotId())
.isAi(feedItem.isAi())
Expand Down
4 changes: 2 additions & 2 deletions capstone-api/src/main/java/backend/feed/dto/IncludedItem.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,6 @@ public class IncludedItem {
@Schema(description = "항목명", example = "재료")
private String name;

@Schema(description = "비고")
private String note;
@Schema(description = "값/설명", example = "쌀 2kg")
private String value;
}
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,8 @@ public FeedApplicationResponse applyToFeed(String feedId, String userId, String
.userId(userId)
.userNickname(userNickname)
.proposal(request.getProposal())
.appliedRole(request.getRole())
.deposit(request.getDeposit())
.build();

return FeedApplicationResponse.from(feedApplicationRepository.save(application));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
@Slf4j
public class LoginFilter extends UsernamePasswordAuthenticationFilter {

private static final String LOGIN_PATH = "/api/auth/login";
private static final String LOGIN_PATH = "/api/v1/auth/login";

private final JWTUtil jwtUtil;
private final RefreshRepository refreshRepository;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.server.ResponseStatusException;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
Expand All @@ -27,7 +28,7 @@

@Tag(name = "Notification API", description = "알림 API")
@RestController
@RequestMapping("/api/notifications")
@RequestMapping("/api/v1/notifications")
@RequiredArgsConstructor
public class NotificationController {

Expand All @@ -53,13 +54,21 @@ public ResponseEntity<ApiResponse<Page<NotificationResponse>>> getNotifications(
}

@Operation(summary = "알림 읽음 처리")
@PatchMapping("/{notificationId}/read")
public ResponseEntity<ApiResponse<Void>> markAsRead(
@PostMapping("/{notificationId}/read")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void markAsRead(
@PathVariable String notificationId,
@AuthenticationPrincipal CustomUserDetails userDetails
) {
notificationService.markAsRead(currentUserId(userDetails), notificationId);
return ResponseEntity.ok(ApiResponse.success());
}

@Operation(summary = "전체 알림 읽음 처리 (미구현)")
@PostMapping("/read-all")
public void markAllAsRead(@AuthenticationPrincipal CustomUserDetails userDetails) {
currentUserId(userDetails);
throw new ResponseStatusException(HttpStatus.NOT_IMPLEMENTED,
"전체 알림 읽음 처리는 아직 구현되지 않았습니다.");
}

private String currentUserId(CustomUserDetails userDetails) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@

@Tag(name = "Post API", description = "게시글 관련 API")
@RestController
@RequestMapping("/api/posts")
@RequestMapping("/api/v1/posts")
@RequiredArgsConstructor
public class PostController {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

@Tag(name = "Simulation API", description = "맵 시뮬레이션 재생 API")
@RestController
@RequestMapping("/api/sim/runs")
@RequestMapping("/api/v1/sim/runs")
@RequiredArgsConstructor
public class SimulationController {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import java.util.List;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.DeleteMapping;
Expand All @@ -14,6 +15,7 @@
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.server.ResponseStatusException;

import backend.global.common.response.ApiResponse;
import backend.global.security.CustomUserDetails;
Expand Down Expand Up @@ -41,7 +43,7 @@

@Tag(name = "Spot API", description = "스팟 관리 API")
@RestController
@RequestMapping("/api/spots")
@RequestMapping("/api/v1/spots")
@RequiredArgsConstructor
public class SpotController {

Expand All @@ -58,6 +60,32 @@ public ResponseEntity<ApiResponse<SpotListResponse>> getSpots(
return ResponseEntity.ok(ApiResponse.success(spotService.getSpots(page, size)));
}

@Operation(summary = "지도 마커용 스팟 목록 (미구현)", description = "bounds/필터 파라미터 처리는 추후 구현 예정")
@GetMapping("/map")
public ResponseEntity<ApiResponse<List<SpotResponse>>> getSpotMap(
@RequestParam(required = false) Double swLat,
@RequestParam(required = false) Double swLng,
@RequestParam(required = false) Double neLat,
@RequestParam(required = false) Double neLng,
@RequestParam(required = false) String category,
@RequestParam(required = false) String type,
@RequestParam(required = false) String status
) {
throw new ResponseStatusException(HttpStatus.NOT_IMPLEMENTED,
"지도 bounds 기반 조회는 아직 구현되지 않았습니다.");
}

@Operation(summary = "스팟 검색 (미구현)")
@GetMapping("/search")
public ResponseEntity<ApiResponse<SpotListResponse>> searchSpots(
@RequestParam String q,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size
) {
throw new ResponseStatusException(HttpStatus.NOT_IMPLEMENTED,
"스팟 검색은 아직 구현되지 않았습니다.");
}

@Operation(summary = "스팟 생성")
@PostMapping
public ResponseEntity<ApiResponse<SpotResponse>> createSpot(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,28 @@ public class SpotChecklistResponse {
private Long id;

@Schema(description = "항목 내용", example = "돗자리 준비")
private String content;
private String text;

@Schema(description = "완료 여부", example = "false")
private Boolean isDone;
private Boolean completed;

@Schema(description = "담당자 ID (선택)", nullable = true)
private String assigneeId;

@Schema(description = "담당자 닉네임 (선택)", nullable = true)
private String assigneeNickname;
Comment on lines +29 to +33
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Map newly added assignee fields in the factory path.

Line 29-Line 33 add assigneeId/assigneeNickname, but Line 38-Line 44 never sets them. Responses built via from(SpotChecklist) will always return these as null.

Proposed fix
 public static SpotChecklistResponse from(SpotChecklist checklist) {
+    return from(checklist, null, null);
+}
+
+public static SpotChecklistResponse from(SpotChecklist checklist, String assigneeId, String assigneeNickname) {
     return SpotChecklistResponse.builder()
         .id(checklist.getId())
         .text(checklist.getContent())
         .completed(checklist.getIsDone())
+        .assigneeId(assigneeId)
+        .assigneeNickname(assigneeNickname)
         .createdAt(checklist.getCreatedAt())
         .build();
 }

Also applies to: 38-44

🤖 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 `@capstone-api/src/main/java/backend/spot/dto/SpotChecklistResponse.java`
around lines 29 - 33, The SpotChecklistResponse factory method
from(SpotChecklist) currently never sets the newly added fields assigneeId and
assigneeNickname, so populate these two fields when building the response: in
SpotChecklistResponse.from(SpotChecklist) extract the assignee ID and nickname
from the input SpotChecklist (e.g., spot.getAssigneeId() and
spot.getAssigneeNickname() or the appropriate getters) and set them on the
response builder/constructor alongside the other mapped fields; ensure both
assigneeId and assigneeNickname are assigned where other properties are mapped
so responses no longer return null for these fields.


@Schema(description = "등록 일시")
private LocalDateTime createdAt;

public static SpotChecklistResponse from(SpotChecklist checklist) {
// assigneeId, assigneeNickname: 엔티티 컬럼 추가 후 매핑 예정 (별도 PR)
return SpotChecklistResponse.builder()
.id(checklist.getId())
.content(checklist.getContent())
.isDone(checklist.getIsDone())
.text(checklist.getContent())
.completed(checklist.getIsDone())
.assigneeId(null)
.assigneeNickname(null)
.createdAt(checklist.getCreatedAt())
.build();
}
Expand Down
Loading