Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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.

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.

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.

Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
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.service.ReviewService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
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")
Comment thread
goodispotato marked this conversation as resolved.
Outdated
public ApiResTemplate createReview(
@RequestBody 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.
reviewService.createReview(principal, reviewCreateReqDto);
return ApiResTemplate.successWithNoContent(SuccessCode.SUCCESS);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

리소스 생성 요청에 대한 성공 응답으로는 200 OK보다 201 Created를 반환하는 것이 더 적절합니다.

Suggested change
return ApiResTemplate.successWithNoContent(SuccessCode.SUCCESS);
return ApiResTemplate.successWithNoContent(SuccessCode.CREATE_SUCCESS);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@Valid 어노테이션 추가하고, 응답 형식을 nocontent가 아닌 ReviewResDto로 data에 값을 채워서 보내주어서 생성시에도 값을 바로 확인할 수 있게 하면 좋을 것 같아요 !!
+) 값 보내는 형식 예시 명세서에 추가해 놓으면 프론트분들 확인하시기 편할 듯 합니다 ..!!

}
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.

createReview의 반환 타입이 raw type인 ApiResTemplate로 선언되어 있습니다. 다른 컨트롤러들은 ApiResTemplate<Void>처럼 제네릭을 명시해서 타입 안정성을 유지하고 있으니, 여기에서도 ApiResTemplate<Void>(또는 no-content 전용 타입)로 맞춰 주세요.

Copilot uses AI. Check for mistakes.
}
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 {
ReviewReqDto reviewReqDto;
List<SpectrumReqDto> spectrumReqDtos;
Comment thread
goodispotato marked this conversation as resolved.
Outdated
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의 필드(reviewReqDto, spectrumReqDtos)가 private가 아니고, nested DTO에 대한 검증도 선언되어 있지 않습니다. 컨트롤러에서 @Valid를 적용할 예정이라면 이 DTO에서도 필드를 private로 두고 @Valid @NotNull(필요 시 @Size)로 중첩 검증이 전파되도록 해 주세요.

Copilot uses AI. Check for mistakes.
}
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,16 @@
package com.sossbar.review.dto.response;

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

import java.util.List;

public record ReviewResDto (
String positiveFeedback,
String negativeFeedback,
Long revieweeId,
Long projectId,
List<TagResDto> tags,
List<SpectrumAxisResDto> spectrumAxes
) {
}
53 changes: 53 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,53 @@
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
@Setter
Comment thread
goodispotato marked this conversation as resolved.
Outdated
@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;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.sossbar.review.entity;

import com.sossbar.spectrumaxis.entity.SpectrumAxis;
import jakarta.persistence.*;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.NoArgsConstructor;

@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
@Builder
public class ReviewSpectrum {

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

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "review_id")
private Review review;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "spectrum_axis_id")
private SpectrumAxis spectrumAxis;

@Column
private Integer strength;

public void setReview(Review review) {
this.review = review;
}

public void setSpectrumAxis(SpectrumAxis spectrumAxis) {
this.spectrumAxis = spectrumAxis;
}

}
37 changes: 37 additions & 0 deletions SossBar/src/main/java/com/sossbar/review/entity/ReviewTag.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.sossbar.review.entity;

import com.sossbar.tag.entity.Tag;
import com.sossbar.user.entity.User;
import jakarta.persistence.*;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.NoArgsConstructor;

@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
@Builder
public class ReviewTag {

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

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "review_id")
private Review review;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "tag_id")
private Tag tag;

public void setReview(Review review) {
this.review = review;
}

public void setTag(Tag tag) {
this.tag = tag;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.sossbar.review.repository;

import com.sossbar.review.entity.Review;
import com.sossbar.user.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface ReviewRepository extends JpaRepository<Review, Long> {

// 중복 리뷰 검증
boolean existsByReviewerAndReviewee(User Reviewer, User Reviewee);
Comment thread
goodispotato marked this conversation as resolved.
Outdated
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.

메서드 파라미터명이 User Reviewer, User Reviewee로 대문자로 시작합니다. 프로젝트 내 다른 Repository(예: UserRepository)는 lowerCamelCase를 사용하고 있어 코드 컨벤션 측면에서 혼동이 생길 수 있으니 reviewer, reviewee로 정리해 주세요.

Suggested change
boolean existsByReviewerAndReviewee(User Reviewer, User Reviewee);
boolean existsByReviewerAndReviewee(User reviewer, User reviewee);

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

import com.sossbar.review.entity.ReviewSpectrum;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface ReviewSpectrumRepository extends JpaRepository<ReviewSpectrum, Long> {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.sossbar.review.repository;

import com.sossbar.review.entity.ReviewTag;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface ReviewTagRepository extends JpaRepository<ReviewTag, Long> {
}
Loading