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
10 changes: 10 additions & 0 deletions .idea/.gitignore

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions .idea/JECT5-4th-Server.iml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 21 additions & 0 deletions .idea/compiler.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions .idea/copilot.data.migration.ask2agent.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 17 additions & 0 deletions .idea/gradle.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions .idea/modules.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions .idea/modules/sossbar.iml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions .idea/modules/sossbar.main.iml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

153 changes: 153 additions & 0 deletions .idea/modules/sossbar.test.iml

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions .idea/vcs.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions SossBar/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ bin/
*.iws
*.iml
*.ipr
*.xml
out/
!**/src/main/**/out/
!**/src/test/**/out/
Expand All @@ -36,4 +37,7 @@ out/
### VS Code ###
.vscode/

### HTTP Client ###
/httpRequests/

src/**/application-prod.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,13 @@ public enum ErrorCode {
// PROJECT
PROJECT_NOT_FOUND_EXCEPTION(HttpStatus.NOT_FOUND, "해당 프로젝트가 없습니다. projectId = ", "PROJECT-001"),
PROJECT_CREATE_ROLLBACK_EXCEPTION(HttpStatus.INTERNAL_SERVER_ERROR, "프로젝트 생성 중 DB 오류가 발생하여 업로드된 이미지를 롤백했습니다. imageUrl = ", "PROJECT-002"),
PROJECT_UPDATE_ROLLBACK_EXCEPTION(HttpStatus.INTERNAL_SERVER_ERROR, "프로젝트 수정 중 DB 오류가 발생하여 업로드된 이미지를 롤백했습니다. imageUrl = ", "PROJECT-003");
PROJECT_UPDATE_ROLLBACK_EXCEPTION(HttpStatus.INTERNAL_SERVER_ERROR, "프로젝트 수정 중 DB 오류가 발생하여 업로드된 이미지를 롤백했습니다. imageUrl = ", "PROJECT-003"),

// REVIEW
DUPLICATE_REVIEW_EXCEPTION(HttpStatus.CONFLICT,"이미 해당 사용자에게 후기를 남겼습니다. revieweeId = ","REVIEW-001"),
SELF_REVIEW_NOT_ALLOWED(HttpStatus.BAD_REQUEST, "자기 자신에게 후기를 남길 수 없습니다.", "REVIEW-002"),
TAG_NOT_FOUND(HttpStatus.NOT_FOUND, "일부 태그가 존재하지 않습니다.", "REVIEW-003"),
SPECTRUM_NOT_FOUND(HttpStatus.NOT_FOUND, "일부 스펙트럼이 존재하지 않습니다.", "REVIEW-004");

private final HttpStatus httpStatus;
private final String message;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.sossbar.review.controller;

import com.sossbar.global.common.code.SuccessCode;
import com.sossbar.global.common.template.ApiResTemplate;
import com.sossbar.review.dto.request.ReviewCreateReqDto;
import com.sossbar.review.dto.response.ReviewCreateResDto;
import com.sossbar.review.service.ReviewService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import java.security.Principal;

@Tag(name = "Review API", description = "후기 관련 API")
@RestController
@RequiredArgsConstructor
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

이 프로젝트의 다른 컨트롤러들은 공통 Swagger 응답 템플릿을 위해 클래스에 @SwaggerApiResTemplate를 붙이고 있습니다(예: UserController, ProjectController). ReviewController에도 동일한 템플릿을 적용하지 않으면 스웨거 응답 스펙이 컨트롤러별로 달라질 수 있으니, 필요하다면 클래스 레벨에 @SwaggerApiResTemplate를 추가해 일관성을 맞춰 주세요.

Suggested change
@RequiredArgsConstructor
@RequiredArgsConstructor
@com.sossbar.global.common.template.SwaggerApiResTemplate

Copilot uses AI. Check for mistakes.
public class ReviewController {

private final ReviewService reviewService;

@Operation(summary = "후기 작성", description = "사용자는 로그인 후 다른 사용자에 대한 후기를 남길 수 있습니다.")
@PostMapping("/api/v1/reviews")
public ApiResTemplate<ReviewCreateResDto> createReview(
@RequestBody @Valid ReviewCreateReqDto reviewCreateReqDto,
Principal principal
) {
Comment on lines +25 to +30
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

요청 DTO에 validation 어노테이션이 선언되어 있는데(ReviewReqDto 등), 컨트롤러에서 @Valid가 빠져 있어 유효성 검사가 동작하지 않습니다. @Valid @RequestBody ReviewCreateReqDto로 검증을 활성화하고, 중첩 DTO까지 검증하려면 내부 필드에 @Valid를 추가해 주세요. (경로 문자열은 다른 컨트롤러들과 동일하게 leading /를 포함해 통일하는 것도 함께 고려해 주세요.)

Copilot uses AI. Check for mistakes.
ReviewCreateResDto reviewCreateResDto = reviewService.createReview(principal, reviewCreateReqDto);

return ApiResTemplate.successResponse(SuccessCode.CREATE_SUCCESS, reviewCreateResDto);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.sossbar.review.dto.request;

import lombok.Getter;

import java.util.List;

// 통합 DTO
@Getter
public class ReviewCreateReqDto {
private ReviewReqDto reviewReqDto;
private List<SpectrumReqDto> spectrumReqDtos;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.sossbar.review.dto.request;

import com.sossbar.projects.entity.Project;
import com.sossbar.review.entity.Review;
import com.sossbar.user.entity.User;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;
import jakarta.validation.constraints.Size;
import lombok.Getter;

import java.util.List;

// 리뷰 + 태그 DTO
@Getter
public class ReviewReqDto {
@NotBlank
@Size(min = 10)
private String positiveFeedback;

@Size(min = 10)
private String negativeFeedback;

@NotNull
@Positive
private Long revieweeId;

@NotNull
@Positive
private Long projectId;

private List<Long> tagIds;

public Review toEntity(User reviewer, User reviewee, Project project) {
return Review.builder()
.positiveFeedback(positiveFeedback)
.negativeFeedback(negativeFeedback)
.reviewer(reviewer)
.reviewee(reviewee)
.project(project)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.sossbar.review.dto.request;


import lombok.Getter;


// 스펙트럼 항목 하나 당 DTO
@Getter
public class SpectrumReqDto {
Long spectrumAxisId;
Integer spectrumStrength;
Comment on lines +10 to +11
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

필드들이 private가 아니라 package-private로 선언되어 있습니다(Long spectrumAxisId, Integer spectrumStrength). 기존 요청 DTO들은 대부분 private 필드 + Getter 패턴을 사용하고 있어 일관성이 깨집니다. 캡슐화/직렬화 관점에서도 private로 바꾸고, 필요한 경우 @NotNull/범위 검증(예: strength 범위) 같은 validation을 추가하는 편이 안전합니다.

Suggested change
Long spectrumAxisId;
Integer spectrumStrength;
private Long spectrumAxisId;
private Integer spectrumStrength;

Copilot uses AI. Check for mistakes.
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package com.sossbar.review.dto.response;

import com.sossbar.review.entity.Review;
import com.sossbar.review.entity.ReviewSpectrum;
import com.sossbar.review.entity.ReviewTag;
import com.sossbar.tag.dto.response.TagResDto;
import lombok.Builder;

import java.util.List;
import java.util.stream.Collectors;

@Builder
public record ReviewCreateResDto (
ReviewResDto reviewResDto,
List<SpectrumResDto> spectrumResDtos
) {
public static ReviewCreateResDto from(Review savedReview, List<ReviewTag> reviewTags, List<ReviewSpectrum> reviewSpectrums) {
List<TagResDto> tagResDtos = reviewTags.stream()
.map(reviewTag -> new TagResDto(
reviewTag.getTag().getTagId(),
reviewTag.getTag().getTagName()
))
.collect(Collectors.toList());

ReviewResDto reviewResDto = new ReviewResDto(
savedReview.getPositiveFeedback(),
savedReview.getNegativeFeedback(),
savedReview.getReviewee().getId(),
savedReview.getProject().getProjectId(),
tagResDtos
);

List<SpectrumResDto> spectrumResDtos = reviewSpectrums.stream()
.map(reviewSpectrum -> SpectrumResDto.builder()
.spectrumAxisId(reviewSpectrum.getSpectrumAxis().getSpectrumAxisId())
.spectrumStrength(reviewSpectrum.getStrength())
.build())
.collect(Collectors.toList());

return ReviewCreateResDto.builder()
.reviewResDto(reviewResDto)
.spectrumResDtos(spectrumResDtos)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.sossbar.review.dto.response;

import com.sossbar.tag.dto.response.TagResDto;

import java.util.List;

public record ReviewResDto (
String positiveFeedback,
String negativeFeedback,
Long revieweeId,
Long projectId,
List<TagResDto> tagResDtos
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.sossbar.review.dto.response;

import lombok.Builder;

@Builder
public record SpectrumResDto (
Long spectrumAxisId,
Integer spectrumStrength
) {
}
52 changes: 52 additions & 0 deletions SossBar/src/main/java/com/sossbar/review/entity/Review.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package com.sossbar.review.entity;

import com.sossbar.projects.entity.Project;
import com.sossbar.user.entity.User;
import jakarta.persistence.*;
import lombok.*;

import java.util.ArrayList;
import java.util.List;

@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Review {
Comment on lines +11 to +14
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

Review 엔티티에 Lombok @Setter가 전체 적용되어 있어 외부에서 리뷰의 작성자/대상자/프로젝트/피드백을 임의로 변경할 수 있습니다. 현재 코드베이스의 다른 엔티티들(User, Project)은 Getter 중심 + 명시적 update 메서드로 변경 지점을 제한하고 있으니, Review도 필요한 변경 메서드만 제공하고 @Setter는 제거(또는 접근 범위 제한)하는 편이 안정적입니다.

Copilot uses AI. Check for mistakes.

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long reviewId;

@Column
private String positiveFeedback;

@Column
private String negativeFeedback;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "reviewer_id")
private User reviewer;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "reviewee_id")
private User reviewee;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "project_id")
private Project project;

@OneToMany(mappedBy = "review", cascade = CascadeType.ALL, orphanRemoval = true)
private List<ReviewTag> reviewTags = new ArrayList<>();

@OneToMany(mappedBy = "review", cascade = CascadeType.ALL, orphanRemoval = true)
private List<ReviewSpectrum> reviewSpectrums = new ArrayList<>();

@Builder
public Review(String positiveFeedback, String negativeFeedback, User reviewer, User reviewee, Project project) {
this.positiveFeedback = positiveFeedback;
this.negativeFeedback = negativeFeedback;
this.reviewer = reviewer;
this.reviewee = reviewee;
this.project = project;
}
}
Loading