Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,15 @@ bin/

### IntelliJ IDEA ###
.env
.oci-keys/
.idea
*.iws
*.iml
*.ipr
out/
!**/src/main/**/out/
!**/src/test/**/out/
src/main/resources/oci

### NetBeans ###
/nbproject/private/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -281,4 +281,36 @@ List<StudentCouncilPost> findPinsInBounds(
@Param("maxLng") Double maxLng,
@Param("now") LocalDateTime now
);

@EntityGraph(attributePaths = {"place"})
@Query("""
SELECT scp
FROM StudentCouncilPost scp
JOIN scp.writer sc
LEFT JOIN Review r
ON r.place = scp.place
AND r.createdAt >= :from
WHERE scp.startDateTime <= :now
AND scp.endDateTime >= :now
AND (
(sc.councilType = com.campus.campus.domain.council.domain.entity.CouncilType.MAJOR_COUNCIL
AND sc.major.majorId = :majorId)
OR (sc.councilType = com.campus.campus.domain.council.domain.entity.CouncilType.COLLEGE_COUNCIL
AND sc.college.collegeId = :collegeId)
OR (sc.councilType = com.campus.campus.domain.council.domain.entity.CouncilType.SCHOOL_COUNCIL
AND sc.school.schoolId = :schoolId)
)
AND sc.deletedAt IS NULL
GROUP BY scp
ORDER BY COUNT(r.id) DESC
""")
Comment on lines +286 to +306
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

카테고리 필터 누락 가능성 확인 필요

이 쿼리는 PARTNERSHIP 카테고리로 필터링하지 않아 EVENT 게시물도 포함될 수 있습니다. findRandomActivePartnerships 등 유사한 메서드들은 p.category = :category 조건을 사용합니다. 의도된 동작인지 확인해 주세요.

🐛 카테고리 필터 추가 제안
     WHERE scp.startDateTime <= :now
       AND scp.endDateTime >= :now
+      AND scp.category = com.campus.campus.domain.councilpost.domain.entity.PostCategory.PARTNERSHIP
       AND (
              (sc.councilType = com.campus.campus.domain.council.domain.entity.CouncilType.MAJOR_COUNCIL
🤖 Prompt for AI Agents
In
@src/main/java/com/campus/campus/domain/councilpost/domain/repository/StudentCouncilPostRepository.java
around lines 286 - 306, The query in StudentCouncilPostRepository currently
lacks a category filter and can return non-PARTNERSHIP posts; add a category
predicate like "AND scp.category = :category" to the WHERE clause of the
annotated query and update the corresponding repository method signature to
accept a category parameter (e.g., CouncilPostCategory category), then ensure
callers of the method pass the intended CouncilPostCategory.PARTNERSHIP value
(or make the param fixed in the method if it should always be PARTNERSHIP).

List<StudentCouncilPost> findTop3RecommendedPartnershipPlaces(
@Param("majorId") Long majorId,
@Param("collegeId") Long collegeId,
@Param("schoolId") Long schoolId,
@Param("from") LocalDateTime from,
@Param("now") LocalDateTime now,
Pageable pageable
);

}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public record PartnershipResponse(
Double longitude,
String tag, //(ex.) 총학생회, 사회과학대학, IT공학과
boolean isLiked,
double star, //리뷰 평점
Double star, //리뷰 평점
String partnerTitle, //제휴 제목
double distance, //거리(m)
LocalDate endDate, //제휴 끝나는 시점
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.campus.campus.domain.councilpost.application.dto.request.PostRequest;
import com.campus.campus.domain.place.application.dto.response.LikeResponse;
import com.campus.campus.domain.place.application.dto.response.SavedPlaceInfo;
import com.campus.campus.domain.place.application.dto.response.SearchCandidateResponse;
Expand Down Expand Up @@ -88,7 +87,7 @@ public List<SavedPlaceInfo> searchByKeyword(String keyword) {

return processSearchResults(naverSearchResponse);
}

public Place findOrCreatePlace(SavedPlaceInfo place) {
String placeKey = place.placeKey();

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package com.campus.campus.domain.review.application.dto.request;

import java.util.List;

import com.campus.campus.domain.place.application.dto.response.SavedPlaceInfo;

Comment on lines +5 to +6
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

fd -t f ReviewRequest.java

Repository: our-campUS/our-campUS-BE

Length of output: 192


🏁 Script executed:

find . -type f -name "SavedPlaceInfo.java"

Repository: our-campUS/our-campUS-BE

Length of output: 159


🏁 Script executed:

find . -type f -name "PlaceRequest.java"

Repository: our-campUS/our-campUS-BE

Length of output: 50


🏁 Script executed:

cat -n src/main/java/com/campus/campus/domain/review/application/dto/request/ReviewRequest.java

Repository: our-campUS/our-campUS-BE

Length of output: 1899


🏁 Script executed:

cat -n src/main/java/com/campus/campus/domain/place/application/dto/response/SavedPlaceInfo.java

Repository: our-campUS/our-campUS-BE

Length of output: 1418


요청 DTO가 응답 DTO(SavedPlaceInfo)에 의존하는 구조는 결합도가 높고 검증 범위가 제한적입니다.

SavedPlaceInfoplaceName, placeKey, address, category에만 @NotBlank 검증이 적용되어 있고, link, telephone, coordinate, imgUrls 필드는 검증 제약이 없습니다. 따라서 @Valid가 적용되더라도 모든 입력 필드를 검증하지 못합니다. 리뷰 작성용 별도의 PlaceRequest DTO를 분리하거나, SavedPlaceInfo의 모든 필수 필드에 명시적 검증 제약을 추가하는 것을 권장합니다.

🤖 Prompt for AI Agents
In
@src/main/java/com/campus/campus/domain/review/application/dto/request/ReviewRequest.java
around lines 5 - 6, ReviewRequest가 응답 DTO인 SavedPlaceInfo에 의존해 검증 범위가 제한되어 있으니,
리뷰용으로 별도 PlaceRequest DTO를 생성하거나 SavedPlaceInfo의 모든 리뷰에 필요한 필수 필드에 명시적
검증(@NotBlank, @NotNull 등)을 추가하여 결합도를 낮추고 검증을 완전하게 만드세요; 구체적으로는 ReviewRequest에서
SavedPlaceInfo를 참조하는 부분을 찾아(예: ReviewRequest.place 필드/생성자) PlaceRequest로 교체하거나
SavedPlaceInfo의 link, telephone, coordinate, imgUrls 등에 적절한 제약을 추가하고
ReviewController/Service에서 @Valid가 적용되도록 보장하세요.

import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.Valid;
import jakarta.validation.constraints.DecimalMax;
import jakarta.validation.constraints.DecimalMin;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;

public record ReviewRequest(

@NotNull
@Size(min = 10, message = "리뷰 내용은 최소 10자 이상이어야 합니다.")
@Schema(example = "아주 정말 맛있습니다. 저의 완전 짱 또간집. 꼭꼮꼬꼬꼭 가세요.")
String content,

@NotNull
@DecimalMin(value = "0.0", inclusive = true)
@DecimalMax(value = "5.0", inclusive = true)
@Schema(example = "3.5")
Double star,
Comment on lines +21 to +25
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

find . -type f -name "ReviewRequest.java" | head -5

Repository: our-campUS/our-campUS-BE

Length of output: 192


🏁 Script executed:

pwd && ls -la | head -20

Repository: our-campUS/our-campUS-BE

Length of output: 906


🏁 Script executed:

find . -type f -name "ReviewRequest.java"

Repository: our-campUS/our-campUS-BE

Length of output: 158


🏁 Script executed:

cat -n ./src/main/java/com/campus/campus/domain/review/application/dto/request/ReviewRequest.java

Repository: our-campUS/our-campUS-BE

Length of output: 1899


🏁 Script executed:

find . -type f -name "*Validator*.java" | head -20

Repository: our-campUS/our-campUS-BE

Length of output: 142


🏁 Script executed:

find . -type f -name "*ReviewService*.java"

Repository: our-campUS/our-campUS-BE

Length of output: 154


🏁 Script executed:

cat -n ./src/main/java/com/campus/campus/domain/review/application/service/ReviewService.java

Repository: our-campUS/our-campUS-BE

Length of output: 12667


🏁 Script executed:

find . -type f -name "Review.java" -path "*/domain/entity/*"

Repository: our-campUS/our-campUS-BE

Length of output: 141


🏁 Script executed:

cat -n ./src/main/java/com/campus/campus/domain/review/domain/entity/Review.java

Repository: our-campUS/our-campUS-BE

Length of output: 1942


🏁 Script executed:

find . -type f -name "*ReviewTest*.java" -o -name "*ReviewRequest*.java" | grep -i test

Repository: our-campUS/our-campUS-BE

Length of output: 50


🏁 Script executed:

rg "0\.5|granularity|decimal|star" --type java -i | head -30

Repository: our-campUS/our-campUS-BE

Length of output: 4196


🏁 Script executed:

cat -n ./src/main/java/com/campus/campus/domain/review/presentation/ReviewController.java | grep -A 20 -B 20 "star"

Repository: our-campUS/our-campUS-BE

Length of output: 1631


🏁 Script executed:

find . -type f \( -name "*.md" -o -name "*.yaml" -o -name "*.yml" \) | xargs grep -l "star\|rating" 2>/dev/null | head -5

Repository: our-campUS/our-campUS-BE

Length of output: 235


🏁 Script executed:

grep -A 5 -B 5 "star\|rating" ./src/main/resources/application-dev.yml ./src/main/resources/application-local.yml ./src/main/resources/application-prod.yml

Repository: our-campUS/our-campUS-BE

Length of output: 2156


🏁 Script executed:

find . -type f -name "*.java" -path "*review*" -exec grep -l "comment\|javadoc\|documentation\|granularity\|unit\|step" {} \;

Repository: our-campUS/our-campUS-BE

Length of output: 50


🏁 Script executed:

find . -path "*/test/*" -name "*Review*Test*.java" -o -path "*/test/*" -name "*Test*Review*.java"

Repository: our-campUS/our-campUS-BE

Length of output: 50


star 필드의 단위(예: 0.5점) 검증 로직이 구현되지 않았습니다.

현재 코드는 0.0~5.0 범위 검증만 수행하며, 0.5 단위 강제 같은 세부 정책은 Bean Validation만으로는 처리되지 않습니다. 컨트롤러 예제에 "3.5"가 명시되어 있지만 실제로는 3.14 같은 임의의 값도 저장될 수 있습니다. 커스텀 @DecimalPlace 제약조건이나 서비스 레벨 검증으로 보완이 필요합니다.

🤖 Prompt for AI Agents
In
@src/main/java/com/campus/campus/domain/review/application/dto/request/ReviewRequest.java
around lines 21 - 25, The star field in ReviewRequest (Double star) only checks
range 0.0–5.0 and lacks enforcement of 0.5 steps; add a validation that ensures
star is a multiple of 0.5 either by creating a custom Bean Validation constraint
(e.g., @DecimalPlace or @Step(value = 0.5) with its corresponding
ConstraintValidator applied to the ReviewRequest.star) or by adding an explicit
check in the review creation service (e.g., in the method that consumes
ReviewRequest, such as ReviewService.createReview) that validates (star * 2) is
an integer and throws a proper BadRequest/ConstraintViolationException when it’s
not.


List<String> imageUrls,

@Schema(example =
"""
"placeName": "숙명여자대학교",
"placeKey": "string",
"address": "서울특별시 용산구 청파로47길 99",
"category": "교육,학문>대학교",
"link": "https://map.naver.com/v5/search/%EC%88%99%EB%AA%85%EC%97%AC%EC%9E%90%EB%8C%80%ED%95%99%EA%B5%90+%EC%A0%9C1%EC%BA%A0%ED%8D%BC%EC%8A%A4?c=37.545947,126.964578,15,0,0,0,dh",
"telephone": "010-1234-1234",
"coordinate": {
"latitude": 0.1,
"longitude": 0.1
},
"imgUrls": [
"string"
]
""",
description = "/search API에서 반환된 결과 중 하나를 선택")
Comment on lines +29 to +45
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

find . -type f -name "ReviewRequest.java" | head -20

Repository: our-campUS/our-campUS-BE

Length of output: 158


🏁 Script executed:

cat -n ./src/main/java/com/campus/campus/domain/review/application/dto/request/ReviewRequest.java | sed -n '20,50p'

Repository: our-campUS/our-campUS-BE

Length of output: 1120


🏁 Script executed:

find . -type f -name "SavedPlaceInfo.java" | head -5

Repository: our-campUS/our-campUS-BE

Length of output: 159


🏁 Script executed:

cat -n ./src/main/java/com/campus/campus/domain/place/application/dto/response/SavedPlaceInfo.java

Repository: our-campUS/our-campUS-BE

Length of output: 1418


@Schema(example = """ ... """)의 JSON 예시가 유효하지 않아 Swagger UI에서 오류를 야기할 수 있습니다.

최상위 {}를 포함하여 유효한 JSON 형식으로 수정이 필요합니다.

제안 수정
 	@Schema(example =
 		"""
-			"placeName": "숙명여자대학교",
-			      "placeKey": "string",
-			      "address": "서울특별시 용산구 청파로47길 99",
-			      "category": "교육,학문>대학교",
-			      "link": "https://map.naver.com/v5/search/%EC%88%99%EB%AA%85%EC%97%AC%EC%9E%90%EB%8C%80%ED%95%99%EA%B5%90+%EC%A0%9C1%EC%BA%A0%ED%8D%BC%EC%8A%A4?c=37.545947,126.964578,15,0,0,0,dh",
-			      "telephone": "010-1234-1234",
-			      "coordinate": {
-			        "latitude": 0.1,
-			        "longitude": 0.1
-			      },
-			      "imgUrls": [
-			        "string"
-			      ]
+			{
+			  "placeName": "숙명여자대학교",
+			  "placeKey": "string",
+			  "address": "서울특별시 용산구 청파로47길 99",
+			  "category": "교육,학문>대학교",
+			  "link": "https://map.naver.com/v5/search/%EC%88%99%EB%AA%85%EC%97%AC%EC%9E%90%EB%8C%80%ED%95%99%EA%B5%90+%EC%A0%9C1%EC%BA%A0%ED%8D%BC%EC%8A%A4?c=37.545947,126.964578,15,0,0,0,dh",
+			  "telephone": "010-1234-1234",
+			  "coordinate": { "latitude": 0.1, "longitude": 0.1 },
+			  "imgUrls": ["string"]
+			}
 			""",
 		description = "/search API에서 반환된 결과 중 하나를 선택")
🤖 Prompt for AI Agents
In
@src/main/java/com/campus/campus/domain/review/application/dto/request/ReviewRequest.java
around lines 29 - 45, The @Schema(example = """...""") JSON in ReviewRequest is
not a valid top-level JSON object; update the example inside the triple-quoted
string in the ReviewRequest class's @Schema annotation to be a valid JSON object
(wrap the existing fields with top-level { ... }, ensure all keys/values are
properly quoted and commas are correct) so Swagger can parse and render it;
locate the @Schema on the ReviewRequest DTO and replace the current invalid
example with a syntactically valid JSON example.

@NotNull
@Valid
SavedPlaceInfo place
Comment on lines +29 to +48
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

@Schema 예시 JSON 형식이 올바르지 않습니다.

place 필드의 @Schema 예시가 JSON 객체의 외부 중괄호 {}가 누락되어 있어 Swagger UI에서 잘못된 형식으로 표시될 수 있습니다.

📝 수정 제안
 	@Schema(example =
 		"""
-			"placeName": "숙명여자대학교",
+			{
+			  "placeName": "숙명여자대학교",
 			      "placeKey": "string",
 			      "address": "서울특별시 용산구 청파로47길 99",
 			      "category": "교육,학문>대학교",
 			      "link": "https://map.naver.com/v5/search/...",
 			      "telephone": "010-1234-1234",
 			      "coordinate": {
 			        "latitude": 0.1,
 			        "longitude": 0.1
 			      },
 			      "imgUrls": [
 			        "string"
 			      ]
-			""",
+			}
+			""",
 		description = "/search API에서 반환된 결과 중 하나를 선택")
🤖 Prompt for AI Agents
In
@src/main/java/com/campus/campus/domain/review/application/dto/request/ReviewRequest.java
around lines 25 - 44, The @Schema example for the SavedPlaceInfo 'place' field
in ReviewRequest is missing the outer JSON braces; update the @Schema(example =
"""...""") value in class ReviewRequest (field: place) to wrap the shown content
with enclosing { ... } so the example becomes a valid JSON object, keeping the
existing multi-line string and description unchanged.


) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.campus.campus.domain.review.application.dto.response;

import java.util.List;

import lombok.Builder;
import lombok.Getter;

@Getter
@Builder
public class CursorPageReviewResponse<ReviewResponse> {

private List<ReviewResponse> items;
private String nextCursorCreatedAt;
private Long nextCursorId;
private boolean hasNext;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.campus.campus.domain.review.application.dto.response;

import lombok.Builder;

@Builder
public record PlaceReviewRankResponse(
Long placeId,
String placeName,
String category,
String partnership,
String thumbnailUrl
// double distance
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.campus.campus.domain.review.application.dto.response;

import lombok.Builder;

@Builder
public record RankingScope(
String scope,
long rank //n번째 리뷰
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.campus.campus.domain.review.application.dto.response;

import lombok.Builder;

@Builder
public record ReviewCreateResponse(
WriteReviewResponse review,
ReviewCreateResult result,
ReviewRankingResponse ranking

) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.campus.campus.domain.review.application.dto.response;

import lombok.Builder;

@Builder
public record ReviewCreateResult(
boolean isFirstReviewOfPlace,
int userReviewCountOfPlace
// int NumberOfUserStamp,
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.campus.campus.domain.review.application.dto.response;

public record ReviewRankingResponse(
RankingScope major,
RankingScope college,
RankingScope school
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.campus.campus.domain.review.application.dto.response;

import java.time.LocalDate;
import java.util.List;

import lombok.Builder;

@Builder
public record ReviewResponse(
Long id,
Long userId,
String userName,
LocalDate createDate,
Long placeId,
String content,
Double star,
List<String> imageUrls
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.campus.campus.domain.review.application.dto.response;

import java.time.LocalDate;

import lombok.Builder;

@Builder
public record WriteReviewResponse(
Long id,
Long userId,
String userName,
LocalDate createDate,
Long placeId,
String content,
Double star,
String imageUrl
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.campus.campus.domain.review.application.exception;

import org.springframework.http.HttpStatus;

import com.campus.campus.global.common.exception.ErrorCodeInterface;

import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
public enum ErrorCode implements ErrorCodeInterface {

REVIEW_NOT_FOUND(2800, HttpStatus.NOT_FOUND, "리뷰를 찾을 수 없습니다."),
NOT_REVIEW_WRITER(2801, HttpStatus.FORBIDDEN, "작성자만 해당 작업을 수행할 수 있습니다.");

private final int code;
private final HttpStatus status;
private final String message;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.campus.campus.domain.review.application.exception;

import com.campus.campus.global.common.exception.ApplicationException;

public class NotUserWriterException extends ApplicationException {
public NotUserWriterException() {
super(ErrorCode.NOT_REVIEW_WRITER);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.campus.campus.domain.review.application.exception;

import com.campus.campus.global.common.exception.ApplicationException;

public class ReviewNotFoundException extends ApplicationException {
public ReviewNotFoundException() {
super(ErrorCode.REVIEW_NOT_FOUND);
}
}
Loading