diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..ab1f416
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,10 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Ignored default folder with query files
+/queries/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
+# Editor-based HTTP Client requests
+/httpRequests/
diff --git a/.idea/JECT5-4th-Server.iml b/.idea/JECT5-4th-Server.iml
new file mode 100644
index 0000000..d6ebd48
--- /dev/null
+++ b/.idea/JECT5-4th-Server.iml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
new file mode 100644
index 0000000..0a1ca4a
--- /dev/null
+++ b/.idea/compiler.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/copilot.data.migration.ask2agent.xml b/.idea/copilot.data.migration.ask2agent.xml
new file mode 100644
index 0000000..1f2ea11
--- /dev/null
+++ b/.idea/copilot.data.migration.ask2agent.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
new file mode 100644
index 0000000..3990a2a
--- /dev/null
+++ b/.idea/gradle.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..a94cba3
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..de50156
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules/sossbar.iml b/.idea/modules/sossbar.iml
new file mode 100644
index 0000000..a32d54d
--- /dev/null
+++ b/.idea/modules/sossbar.iml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules/sossbar.main.iml b/.idea/modules/sossbar.main.iml
new file mode 100644
index 0000000..dd2a079
--- /dev/null
+++ b/.idea/modules/sossbar.main.iml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules/sossbar.test.iml b/.idea/modules/sossbar.test.iml
new file mode 100644
index 0000000..0f30d94
--- /dev/null
+++ b/.idea/modules/sossbar.test.iml
@@ -0,0 +1,153 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..35eb1dd
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SossBar/.gitignore b/SossBar/.gitignore
index 9289965..3d4135a 100644
--- a/SossBar/.gitignore
+++ b/SossBar/.gitignore
@@ -22,6 +22,7 @@ bin/
*.iws
*.iml
*.ipr
+*.xml
out/
!**/src/main/**/out/
!**/src/test/**/out/
@@ -36,4 +37,7 @@ out/
### VS Code ###
.vscode/
+### HTTP Client ###
+/httpRequests/
+
src/**/application-prod.yml
diff --git a/SossBar/src/main/java/com/sossbar/global/common/code/ErrorCode.java b/SossBar/src/main/java/com/sossbar/global/common/code/ErrorCode.java
index 2d34c0a..a85a3e5 100644
--- a/SossBar/src/main/java/com/sossbar/global/common/code/ErrorCode.java
+++ b/SossBar/src/main/java/com/sossbar/global/common/code/ErrorCode.java
@@ -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;
diff --git a/SossBar/src/main/java/com/sossbar/review/controller/ReviewController.java b/SossBar/src/main/java/com/sossbar/review/controller/ReviewController.java
new file mode 100644
index 0000000..1cf246d
--- /dev/null
+++ b/SossBar/src/main/java/com/sossbar/review/controller/ReviewController.java
@@ -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
+public class ReviewController {
+
+ private final ReviewService reviewService;
+
+ @Operation(summary = "후기 작성", description = "사용자는 로그인 후 다른 사용자에 대한 후기를 남길 수 있습니다.")
+ @PostMapping("/api/v1/reviews")
+ public ApiResTemplate createReview(
+ @RequestBody @Valid ReviewCreateReqDto reviewCreateReqDto,
+ Principal principal
+ ) {
+ ReviewCreateResDto reviewCreateResDto = reviewService.createReview(principal, reviewCreateReqDto);
+
+ return ApiResTemplate.successResponse(SuccessCode.CREATE_SUCCESS, reviewCreateResDto);
+ }
+}
diff --git a/SossBar/src/main/java/com/sossbar/review/dto/request/ReviewCreateReqDto.java b/SossBar/src/main/java/com/sossbar/review/dto/request/ReviewCreateReqDto.java
new file mode 100644
index 0000000..c82d0c7
--- /dev/null
+++ b/SossBar/src/main/java/com/sossbar/review/dto/request/ReviewCreateReqDto.java
@@ -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 spectrumReqDtos;
+}
diff --git a/SossBar/src/main/java/com/sossbar/review/dto/request/ReviewReqDto.java b/SossBar/src/main/java/com/sossbar/review/dto/request/ReviewReqDto.java
new file mode 100644
index 0000000..4ce59e1
--- /dev/null
+++ b/SossBar/src/main/java/com/sossbar/review/dto/request/ReviewReqDto.java
@@ -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 tagIds;
+
+ public Review toEntity(User reviewer, User reviewee, Project project) {
+ return Review.builder()
+ .positiveFeedback(positiveFeedback)
+ .negativeFeedback(negativeFeedback)
+ .reviewer(reviewer)
+ .reviewee(reviewee)
+ .project(project)
+ .build();
+ }
+}
diff --git a/SossBar/src/main/java/com/sossbar/review/dto/request/SpectrumReqDto.java b/SossBar/src/main/java/com/sossbar/review/dto/request/SpectrumReqDto.java
new file mode 100644
index 0000000..088a53f
--- /dev/null
+++ b/SossBar/src/main/java/com/sossbar/review/dto/request/SpectrumReqDto.java
@@ -0,0 +1,12 @@
+package com.sossbar.review.dto.request;
+
+
+import lombok.Getter;
+
+
+// 스펙트럼 항목 하나 당 DTO
+@Getter
+public class SpectrumReqDto {
+ Long spectrumAxisId;
+ Integer spectrumStrength;
+}
diff --git a/SossBar/src/main/java/com/sossbar/review/dto/response/ReviewCreateResDto.java b/SossBar/src/main/java/com/sossbar/review/dto/response/ReviewCreateResDto.java
new file mode 100644
index 0000000..d68221d
--- /dev/null
+++ b/SossBar/src/main/java/com/sossbar/review/dto/response/ReviewCreateResDto.java
@@ -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 spectrumResDtos
+) {
+ public static ReviewCreateResDto from(Review savedReview, List reviewTags, List reviewSpectrums) {
+ List 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 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();
+ }
+}
diff --git a/SossBar/src/main/java/com/sossbar/review/dto/response/ReviewResDto.java b/SossBar/src/main/java/com/sossbar/review/dto/response/ReviewResDto.java
new file mode 100644
index 0000000..76166ea
--- /dev/null
+++ b/SossBar/src/main/java/com/sossbar/review/dto/response/ReviewResDto.java
@@ -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 tagResDtos
+) {
+}
diff --git a/SossBar/src/main/java/com/sossbar/review/dto/response/SpectrumResDto.java b/SossBar/src/main/java/com/sossbar/review/dto/response/SpectrumResDto.java
new file mode 100644
index 0000000..2785934
--- /dev/null
+++ b/SossBar/src/main/java/com/sossbar/review/dto/response/SpectrumResDto.java
@@ -0,0 +1,10 @@
+package com.sossbar.review.dto.response;
+
+import lombok.Builder;
+
+@Builder
+public record SpectrumResDto (
+ Long spectrumAxisId,
+ Integer spectrumStrength
+) {
+}
diff --git a/SossBar/src/main/java/com/sossbar/review/entity/Review.java b/SossBar/src/main/java/com/sossbar/review/entity/Review.java
new file mode 100644
index 0000000..ca690a8
--- /dev/null
+++ b/SossBar/src/main/java/com/sossbar/review/entity/Review.java
@@ -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 {
+
+ @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 reviewTags = new ArrayList<>();
+
+ @OneToMany(mappedBy = "review", cascade = CascadeType.ALL, orphanRemoval = true)
+ private List 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;
+ }
+}
diff --git a/SossBar/src/main/java/com/sossbar/review/entity/ReviewSpectrum.java b/SossBar/src/main/java/com/sossbar/review/entity/ReviewSpectrum.java
new file mode 100644
index 0000000..b764763
--- /dev/null
+++ b/SossBar/src/main/java/com/sossbar/review/entity/ReviewSpectrum.java
@@ -0,0 +1,37 @@
+package com.sossbar.review.entity;
+
+import com.sossbar.spectrumaxis.entity.SpectrumAxis;
+import jakarta.persistence.*;
+import lombok.*;
+
+@Entity
+@NoArgsConstructor(access = AccessLevel.PROTECTED)
+@AllArgsConstructor
+@Builder
+@Getter
+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;
+ }
+
+}
diff --git a/SossBar/src/main/java/com/sossbar/review/entity/ReviewTag.java b/SossBar/src/main/java/com/sossbar/review/entity/ReviewTag.java
new file mode 100644
index 0000000..76b8682
--- /dev/null
+++ b/SossBar/src/main/java/com/sossbar/review/entity/ReviewTag.java
@@ -0,0 +1,35 @@
+package com.sossbar.review.entity;
+
+import com.sossbar.tag.entity.Tag;
+import com.sossbar.user.entity.User;
+import jakarta.persistence.*;
+import lombok.*;
+
+@Entity
+@NoArgsConstructor(access = AccessLevel.PROTECTED)
+@AllArgsConstructor
+@Builder
+@Getter
+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;
+ }
+
+}
diff --git a/SossBar/src/main/java/com/sossbar/review/repository/ReviewRepository.java b/SossBar/src/main/java/com/sossbar/review/repository/ReviewRepository.java
new file mode 100644
index 0000000..09c4b9b
--- /dev/null
+++ b/SossBar/src/main/java/com/sossbar/review/repository/ReviewRepository.java
@@ -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 {
+
+ // 중복 리뷰 검증
+ boolean existsByReviewerAndReviewee(User reviewer, User reviewee);
+}
diff --git a/SossBar/src/main/java/com/sossbar/review/repository/ReviewSpectrumRepository.java b/SossBar/src/main/java/com/sossbar/review/repository/ReviewSpectrumRepository.java
new file mode 100644
index 0000000..d6961f8
--- /dev/null
+++ b/SossBar/src/main/java/com/sossbar/review/repository/ReviewSpectrumRepository.java
@@ -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 {
+}
diff --git a/SossBar/src/main/java/com/sossbar/review/repository/ReviewTagRepository.java b/SossBar/src/main/java/com/sossbar/review/repository/ReviewTagRepository.java
new file mode 100644
index 0000000..3b183c3
--- /dev/null
+++ b/SossBar/src/main/java/com/sossbar/review/repository/ReviewTagRepository.java
@@ -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 {
+}
diff --git a/SossBar/src/main/java/com/sossbar/review/service/ReviewService.java b/SossBar/src/main/java/com/sossbar/review/service/ReviewService.java
new file mode 100644
index 0000000..82711d3
--- /dev/null
+++ b/SossBar/src/main/java/com/sossbar/review/service/ReviewService.java
@@ -0,0 +1,122 @@
+package com.sossbar.review.service;
+
+import com.sossbar.global.common.code.ErrorCode;
+import com.sossbar.global.common.exception.BusinessException;
+import com.sossbar.projects.entity.Project;
+import com.sossbar.projects.repository.ProjectRepository;
+import com.sossbar.review.dto.request.ReviewCreateReqDto;
+import com.sossbar.review.dto.request.ReviewReqDto;
+import com.sossbar.review.dto.request.SpectrumReqDto;
+import com.sossbar.review.dto.response.ReviewCreateResDto;
+import com.sossbar.review.entity.Review;
+import com.sossbar.review.entity.ReviewSpectrum;
+import com.sossbar.review.entity.ReviewTag;
+import com.sossbar.review.repository.ReviewRepository;
+import com.sossbar.review.repository.ReviewSpectrumRepository;
+import com.sossbar.review.repository.ReviewTagRepository;
+import com.sossbar.spectrumaxis.entity.SpectrumAxis;
+import com.sossbar.spectrumaxis.repository.SpectrumAxisRepository;
+import com.sossbar.tag.entity.Tag;
+import com.sossbar.tag.repository.TagRepository;
+import com.sossbar.user.entity.User;
+import com.sossbar.user.repository.UserRepository;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.security.Principal;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+@Service
+@RequiredArgsConstructor
+public class ReviewService {
+
+ private final ReviewRepository reviewRepository;
+ private final TagRepository tagRepository;
+ private final SpectrumAxisRepository spectrumAxisRepository;
+ private final ReviewTagRepository reviewTagRepository;
+ private final ReviewSpectrumRepository reviewSpectrumRepository;
+ private final UserRepository userRepository;
+ private final ProjectRepository projectRepository;
+
+ @Transactional
+ public ReviewCreateResDto createReview(Principal principal, ReviewCreateReqDto reviewCreateReqDto) {
+ ReviewReqDto reviewReqDto = reviewCreateReqDto.getReviewReqDto();
+ List reviewTags = new ArrayList<>();
+
+ long reviewerIdentifier;
+
+ try {
+ reviewerIdentifier = Long.parseLong(principal.getName());
+ } catch (NumberFormatException e) {
+ throw new IllegalArgumentException(e);
+ }
+
+ User reviewer = userRepository.findById(reviewerIdentifier)
+ .orElseThrow(() -> new BusinessException(ErrorCode.USER_NOT_FOUND_EXCEPTION, reviewerIdentifier+""));
+ User reviewee = userRepository.findById(reviewReqDto.getRevieweeId())
+ .orElseThrow(() -> new BusinessException(ErrorCode.USER_NOT_FOUND_EXCEPTION, reviewerIdentifier+""));
+
+ if (reviewRepository.existsByReviewerAndReviewee(reviewer, reviewee)) {
+ throw new BusinessException(ErrorCode.DUPLICATE_REVIEW_EXCEPTION, reviewee.getId()+"");
+ }
+
+ if (reviewer.getId().equals(reviewee.getId())) {
+ throw new BusinessException(ErrorCode.SELF_REVIEW_NOT_ALLOWED, "");
+ }
+
+ Project project = projectRepository.findById(reviewReqDto.getProjectId())
+ .orElseThrow(() -> new BusinessException(ErrorCode.PROJECT_NOT_FOUND_EXCEPTION, reviewReqDto.getProjectId()+""));
+
+ Review savedReview = reviewRepository.save(reviewReqDto.toEntity(reviewer, reviewee, project));
+
+ // 태그 목록 저장
+ List tagIds = reviewReqDto.getTagIds();
+ if (tagIds != null && !tagIds.isEmpty()) {
+ List tags = tagRepository.findAllById(tagIds);
+
+ if(tags.size() != tagIds.size()) {
+ throw new BusinessException(ErrorCode.TAG_NOT_FOUND, "");
+ }
+
+ reviewTags = tags.stream()
+ .map(tag -> ReviewTag.builder()
+ .review(savedReview)
+ .tag(tag)
+ .build())
+ .collect(Collectors.toList());
+
+ reviewTagRepository.saveAll(reviewTags);
+ }
+
+ // 각 스펙트럼 축 당 저장하기
+ List spectrumAxisIds = reviewCreateReqDto.getSpectrumReqDtos().stream()
+ .map(SpectrumReqDto::getSpectrumAxisId)
+ .distinct()
+ .collect(Collectors.toList());
+
+ List spectrumAxes = spectrumAxisRepository.findAllById(spectrumAxisIds);
+
+ Map spectrumAxisMap = spectrumAxes.stream()
+ .collect(Collectors.toMap(SpectrumAxis::getSpectrumAxisId, axis -> axis));
+
+ if(spectrumAxisMap.size() != spectrumAxes.size()) {
+ throw new BusinessException(ErrorCode.SPECTRUM_NOT_FOUND, "");
+ }
+
+ List reviewSpectrums = reviewCreateReqDto.getSpectrumReqDtos().stream()
+ .map(dto -> ReviewSpectrum.builder()
+ .review(savedReview)
+ .spectrumAxis(spectrumAxisMap.get(dto.getSpectrumAxisId()))
+ .strength(dto.getSpectrumStrength())
+ .build())
+ .collect(Collectors.toList());
+
+ reviewSpectrumRepository.saveAll(reviewSpectrums);
+
+ return ReviewCreateResDto.from(savedReview, reviewTags, reviewSpectrums);
+ }
+}
diff --git a/SossBar/src/main/java/com/sossbar/spectrumaxis/dto/response/SpectrumAxisResDto.java b/SossBar/src/main/java/com/sossbar/spectrumaxis/dto/response/SpectrumAxisResDto.java
new file mode 100644
index 0000000..7181647
--- /dev/null
+++ b/SossBar/src/main/java/com/sossbar/spectrumaxis/dto/response/SpectrumAxisResDto.java
@@ -0,0 +1,9 @@
+package com.sossbar.spectrumaxis.dto.response;
+
+public record SpectrumAxisResDto(
+ Long spectrumAxisId,
+ String spectrumAxisName,
+ String leftLabel,
+ String rightLabel
+) {
+}
diff --git a/SossBar/src/main/java/com/sossbar/spectrumaxis/entity/SpectrumAxis.java b/SossBar/src/main/java/com/sossbar/spectrumaxis/entity/SpectrumAxis.java
new file mode 100644
index 0000000..b64d247
--- /dev/null
+++ b/SossBar/src/main/java/com/sossbar/spectrumaxis/entity/SpectrumAxis.java
@@ -0,0 +1,35 @@
+package com.sossbar.spectrumaxis.entity;
+
+import com.sossbar.review.entity.ReviewSpectrum;
+import jakarta.persistence.*;
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Entity
+@Setter(AccessLevel.PROTECTED)
+@NoArgsConstructor(access = AccessLevel.PROTECTED)
+@Getter
+public class SpectrumAxis {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long spectrumAxisId;
+
+ @Column
+ private String axisName;
+
+ @Column
+ private String leftLabel;
+
+ @Column
+ private String rightLabel;
+
+ @OneToMany(mappedBy = "spectrumAxis", cascade = CascadeType.ALL, orphanRemoval = true)
+ private List reviewSpectrums = new ArrayList<>();
+
+}
diff --git a/SossBar/src/main/java/com/sossbar/spectrumaxis/repository/SpectrumAxisRepository.java b/SossBar/src/main/java/com/sossbar/spectrumaxis/repository/SpectrumAxisRepository.java
new file mode 100644
index 0000000..544dca1
--- /dev/null
+++ b/SossBar/src/main/java/com/sossbar/spectrumaxis/repository/SpectrumAxisRepository.java
@@ -0,0 +1,7 @@
+package com.sossbar.spectrumaxis.repository;
+
+import com.sossbar.spectrumaxis.entity.SpectrumAxis;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+public interface SpectrumAxisRepository extends JpaRepository {
+}
diff --git a/SossBar/src/main/java/com/sossbar/tag/dto/response/TagResDto.java b/SossBar/src/main/java/com/sossbar/tag/dto/response/TagResDto.java
new file mode 100644
index 0000000..416e10e
--- /dev/null
+++ b/SossBar/src/main/java/com/sossbar/tag/dto/response/TagResDto.java
@@ -0,0 +1,7 @@
+package com.sossbar.tag.dto.response;
+
+public record TagResDto(
+ Long tagId,
+ String tagName
+) {
+}
diff --git a/SossBar/src/main/java/com/sossbar/tag/entity/Tag.java b/SossBar/src/main/java/com/sossbar/tag/entity/Tag.java
new file mode 100644
index 0000000..499ee61
--- /dev/null
+++ b/SossBar/src/main/java/com/sossbar/tag/entity/Tag.java
@@ -0,0 +1,29 @@
+package com.sossbar.tag.entity;
+
+import com.sossbar.review.entity.ReviewTag;
+import jakarta.persistence.*;
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Entity
+@Setter(AccessLevel.PROTECTED)
+@NoArgsConstructor(access = AccessLevel.PROTECTED)
+@Getter
+public class Tag {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long tagId;
+
+ @Column
+ private String tagName;
+
+ @OneToMany(mappedBy = "tag", cascade = CascadeType.ALL, orphanRemoval = true)
+ private List reviewTags = new ArrayList<>();
+
+}
diff --git a/SossBar/src/main/java/com/sossbar/tag/repository/TagRepository.java b/SossBar/src/main/java/com/sossbar/tag/repository/TagRepository.java
new file mode 100644
index 0000000..ef5b083
--- /dev/null
+++ b/SossBar/src/main/java/com/sossbar/tag/repository/TagRepository.java
@@ -0,0 +1,7 @@
+package com.sossbar.tag.repository;
+
+import com.sossbar.tag.entity.Tag;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+public interface TagRepository extends JpaRepository {
+}
diff --git a/SossBar/src/main/resources/application.yml b/SossBar/src/main/resources/application.yml
index faddc8e..827d737 100644
--- a/SossBar/src/main/resources/application.yml
+++ b/SossBar/src/main/resources/application.yml
@@ -12,7 +12,12 @@ spring:
password: ${spring.datasource.password}
driver-class-name: ${spring.datasource.driver-class-name}
+ sql:
+ init:
+ mode: always
+
jpa:
+ defer-datasource-initialization: true
hibernate:
ddl-auto: update
naming:
@@ -42,8 +47,8 @@ cloud:
access-key: ${CLOUD_AWS_CREDENTIALS_ACCESS_KEY}
secret-key: ${CLOUD_AWS_CREDENTIALS_SECRET_KEY}
s3:
- bucket: ${CLOUD_AWS_S3_BUCKET}
url: ${CLOUD_AWS_S3_URL}
+ bucket: ${CLOUD_AWS_S3_BUCKET}
region:
static: ap-northeast-2
stack:
diff --git a/SossBar/src/main/resources/data.sql b/SossBar/src/main/resources/data.sql
new file mode 100644
index 0000000..d4624a0
--- /dev/null
+++ b/SossBar/src/main/resources/data.sql
@@ -0,0 +1,25 @@
+INSERT INTO tag(tag_name) VALUES ('약속을 잘 지켜요');
+INSERT INTO tag(tag_name) VALUES ('적극적이에요');
+INSERT INTO tag(tag_name) VALUES ('꼼꼼해요');
+INSERT INTO tag(tag_name) VALUES ('응답이 빨라요');
+INSERT INTO tag(tag_name) VALUES ('피드백을 잘 수용해요');
+INSERT INTO tag(tag_name) VALUES ('문서 정리를 잘해요');
+INSERT INTO tag(tag_name) VALUES ('아이디어가 많아요');
+INSERT INTO tag(tag_name) VALUES ('마감을 잘 지켜요');
+INSERT INTO tag(tag_name) VALUES ('팀 분위기를 좋게 만들어요');
+INSERT INTO tag(tag_name) VALUES ('시간 조율을 잘해요');
+INSERT INTO tag(tag_name) VALUES ('실행력이 좋아요');
+INSERT INTO tag(tag_name) VALUES ('작업 속도가 빨라요');
+INSERT INTO tag(tag_name) VALUES ('팀원들을 잘 도와줘요');
+INSERT INTO tag(tag_name) VALUES ('센스가 좋아요');
+INSERT INTO tag(tag_name) VALUES ('문제 해결을 잘 해요');
+INSERT INTO tag(tag_name) VALUES ('일정 관리를 잘 해요');
+INSERT INTO tag(tag_name) VALUES ('끈기있어요');
+INSERT INTO tag(tag_name) VALUES ('고마움을 잘 표현해요');
+INSERT INTO tag(tag_name) VALUES ('먼저 친근하게 다가와요');
+INSERT INTO tag(tag_name) VALUES ('모르는 것을 숨기지 않고 물어봐요');
+
+INSERT INTO spectrum_axis(axis_name, left_label, right_label) VALUES ('1번 항목', '서포트형', '리드형');
+INSERT INTO spectrum_axis(axis_name, left_label, right_label) VALUES ('2번 항목', '빠른 작업 속도 중시', '천천히 신중한 고민 중시');
+INSERT INTO spectrum_axis(axis_name, left_label, right_label) VALUES ('3번 항목', '상황별 유연한 대처', '철저한 계획 기반 실행');
+INSERT INTO spectrum_axis(axis_name, left_label, right_label) VALUES ('4번 항목', '냉철한 결과 지향', '따뜻한 관계 지향');
\ No newline at end of file