diff --git a/.github/workflows/pr_weekly_ci.yml b/.github/workflows/pr_weekly_ci.yml
index f54701f..372f0ca 100644
--- a/.github/workflows/pr_weekly_ci.yml
+++ b/.github/workflows/pr_weekly_ci.yml
@@ -53,7 +53,7 @@ jobs:
- name: 빌드 테스트 수행
run: |
chmod +x ./gradlew
- ./gradlew clean build --build-cache --stacktrace
+ ./gradlew clean build jacocoTestReport --build-cache --stacktrace
- name: 테스트 수행 결과 보고
uses: EnricoMi/publish-unit-test-result-action@v2
@@ -67,3 +67,19 @@ jobs:
with:
report_paths: '**/build/test-results/test/TEST-*.xml'
token: ${{ github.token }}
+
+ - name: JaCoCo 테스트 커버리지 리포트 업로드
+ uses: actions/upload-artifact@v3
+ if: always()
+ with:
+ name: jacoco-report
+ path: '**/build/reports/jacoco/'
+
+ - name: JaCoCo 테스트 커버리지 결과를 PR에 코멘트로 등록
+ uses: madrapps/jacoco-report@v1.6.1
+ with:
+ paths: ${{ github.workspace }}/build/reports/jacoco/test/jacocoTestReport.xml
+ token: ${{ github.token }}
+ min-coverage-overall: 70
+ min-coverage-changed-files: 70
+ title: '📊 테스트 커버리지 리포트'
diff --git a/README.md b/README.md
index 19ed58e..9ef5a47 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,147 @@
-# Team21_BE
-21조 백엔드
+# Every Moment
+> 하루를 자동으로 기록하고 공유하는 위치 기반 소셜 다이어리 📝
+
+![image](https://github.com/user-attachments/assets/d2be179d-e5e4-4b14-914f-8d88f5e6f7b5)
+
+
+
+## 📌 프로젝트 소개
+바쁜 일상 속에서 매 순간을 기록하기란 쉽지 않습니다.
+Every Moment는 위치 데이터를 기반으로 사용자의 하루를 **자동**으로 기록하고 친구들과 공유할 수 있는 소셜 다이어리 서비스입니다.
+
+### 핵심 기능
+- 📍 **위치 기반 자동 기록**: 15분 이상 머문 장소 자동 감지 및 일기로 기록
+
+- 🤝 **소셜 다이어리**: 친구들과 일기 공유 및 소통
+- 🔍 **스마트 검색**: 다양한 필터링 옵션으로 원하는 일기 빠른 검색
+- 🔔 **실시간 알림**: 친구와의 상호작용(좋아요, 댓글, 친구 요청 등)과 새로운 장소 감지를 실시간으로 알림
+
+
+
+## ⭐️ 주요 기능
+| 자동 일기 기록 | 손쉬운 일기 편집 | 다양한 검색 필터링 | 친구와의 일기 공유 |
+|:---:|:---:|:---:|:---:|
+| | | | |
+| 15분동안 머문 장소를
자동으로 기록 | 저장된 일기를
손쉽게 편집 | 다양한 검색 조건으로
손쉬운 일기 찾기 | 원하는 일기를 공유하여
친구와 소통 |
+
+
+
+## 🔍 프로젝트 정보
+### 개발 기간
+- 2024.09 ~ 2024.11 (3개월)
+
+### 서비스 링크
+- **API 서버**: http://13.125.156.74:8080
+
+- **API 문서**: [Swagger UI](http://13.125.156.74:8080/swagger-ui/index.html) | [노션 문서](https://peeerr.notion.site/API-2e575ca8df07493dbc25f3d0e91ca211?pvs=4)
+
+### 프로젝트 관리
+- **[Backend Repository](https://github.com/kakao-tech-campus-2nd-step3/Team21_BE)**
+- **[Android Repository](https://github.com/kakao-tech-campus-2nd-step3/Team21_Android)**
+
+- **[Git Flow 전략](https://github.com/kakao-tech-campus-2nd-step3/Team21_BE/wiki/Git-Flow-%EC%A0%84%EB%9E%B5)**
+- **[코딩 컨벤션](https://github.com/kakao-tech-campus-2nd-step3/Team21_BE/wiki/%EC%BD%94%EB%94%A9-%EC%BB%A8%EB%B2%A4%EC%85%98)**
+- **[커밋 컨벤션](https://github.com/kakao-tech-campus-2nd-step3/Team21_BE/wiki/%EC%BB%A4%EB%B0%8B-%EC%BB%A8%EB%B2%A4%EC%85%98)**
+
+
+
+## 👥 팀원 소개
+저희 팀은 **백엔드 2명, 안드로이드 3명**으로 구성되어 있습니다.
+
+
+
+
+
+## 🛠 기술 스택
+### 💻 Language & Framework
+- Java 21
+- Spring Boot 3.3
+- Spring Data JPA
+
+### 📊 Database & Storage
+- MySQL
+- AWS S3 (파일 저장소)
+
+### 📱 Communication
+- Firebase Cloud Messaging (FCM)
+
+### 🏗 Infra
+- AWS EC2
+- AWS RDS
+
+### 🔧 Development Tools
+- GitHub Actions
+
+### 📚 Documentation & Testing
+- Swagger
+- JUnit5
+
+### ⚙️ Others
+- JWT (인증/인가)
+
+
+
+## 🗄️ ERD
+![erd](https://github.com/user-attachments/assets/72e66248-f217-434a-9f20-d8150abafee4)
+
+
+
+## 🔍 개발 주안점
+- **실시간성 확보**
+ - FCM을 활용한 즉각적인 푸시 알림 구현
+
+ - 댓글, 좋아요, 친구 요청, 자동 일기 작성 등 다양한 이벤트에 대한 실시간 알림
+
+- **CI/CD 파이프라인 구축 및 최적화**
+ - Github Actions를 활용한 배포 자동화
+
+ - PR 단계에서 자동화된 테스트 실행으로 개발 생산성 향상
+ - 이전에는 배포 환경에서 테스트 실패 시 새로운 PR을 생성하고, 배포하는 과정을 반복해야 했음
+
+- **테스트 작성**
+ - 70% 이상의 테스트 커버리지 유지
diff --git a/build.gradle b/build.gradle
index 98243d9..ff625e8 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,5 +1,6 @@
plugins {
id 'java'
+ id 'jacoco'
id 'org.springframework.boot' version '3.3.3'
id 'io.spring.dependency-management' version '1.1.6'
}
@@ -51,3 +52,41 @@ dependencies {
tasks.named('test') {
useJUnitPlatform()
}
+
+jacocoTestReport {
+ dependsOn test // 테스트 실행 후 리포트 생성을 보장
+
+ reports {
+ xml.required = true
+ html.required = true
+ }
+
+ afterEvaluate {
+ classDirectories.setFrom(files(classDirectories.files.collect {
+ fileTree(dir: it, exclude: [
+ '**/constant/**',
+ '**/config/**',
+ '**/dto/**',
+ '**/security/**',
+ '**/exception/**',
+ '**/*Application.class'
+ ])
+ }))
+ }
+}
+
+test {
+ useJUnitPlatform()
+ finalizedBy jacocoTestReport
+}
+
+// 테스트 커버리지 최소 기준 설정
+jacocoTestCoverageVerification {
+ violationRules {
+ rule {
+ limit {
+ minimum = 0.70
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/potatocake/everymoment/controller/DiaryController.java b/src/main/java/com/potatocake/everymoment/controller/DiaryController.java
index 41579ac..afae616 100644
--- a/src/main/java/com/potatocake/everymoment/controller/DiaryController.java
+++ b/src/main/java/com/potatocake/everymoment/controller/DiaryController.java
@@ -100,6 +100,8 @@ public ResponseEntity> getMyDiaries(
@RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate until,
@Parameter(description = "북마크 여부")
@RequestParam(required = false) Boolean bookmark,
+ @Parameter(description = "공유 여부")
+ @RequestParam(required = false) Boolean isPublic,
@Parameter(description = "페이지 키")
@RequestParam(defaultValue = "0") int key,
@Parameter(description = "페이지 크기")
@@ -115,6 +117,7 @@ public ResponseEntity> getMyDiaries(
.from(from)
.until(until)
.isBookmark(bookmark)
+ .isPublic(isPublic)
.key(key)
.size(size)
.build();
diff --git a/src/main/java/com/potatocake/everymoment/dto/request/DiaryFilterRequest.java b/src/main/java/com/potatocake/everymoment/dto/request/DiaryFilterRequest.java
index 29fbb5d..429f0c7 100644
--- a/src/main/java/com/potatocake/everymoment/dto/request/DiaryFilterRequest.java
+++ b/src/main/java/com/potatocake/everymoment/dto/request/DiaryFilterRequest.java
@@ -18,6 +18,7 @@ public class DiaryFilterRequest {
private LocalDate from;
private LocalDate until;
private Boolean isBookmark;
+ private Boolean isPublic;
private int key;
private int size;
diff --git a/src/main/java/com/potatocake/everymoment/dto/request/DiaryManualCreateRequest.java b/src/main/java/com/potatocake/everymoment/dto/request/DiaryManualCreateRequest.java
index 7659c2b..5ed8174 100644
--- a/src/main/java/com/potatocake/everymoment/dto/request/DiaryManualCreateRequest.java
+++ b/src/main/java/com/potatocake/everymoment/dto/request/DiaryManualCreateRequest.java
@@ -3,6 +3,7 @@
import com.fasterxml.jackson.annotation.JsonProperty;
import com.potatocake.everymoment.dto.LocationPoint;
import jakarta.validation.constraints.Size;
+import java.time.LocalDate;
import java.util.List;
import lombok.Builder;
import lombok.Getter;
@@ -11,6 +12,8 @@
@Getter
public class DiaryManualCreateRequest {
+ private LocalDate diaryDate;
+
private List categories;
private LocationPoint locationPoint;
diff --git a/src/main/java/com/potatocake/everymoment/dto/response/MyDiaryResponse.java b/src/main/java/com/potatocake/everymoment/dto/response/MyDiaryResponse.java
index c8c8110..346a1f1 100644
--- a/src/main/java/com/potatocake/everymoment/dto/response/MyDiaryResponse.java
+++ b/src/main/java/com/potatocake/everymoment/dto/response/MyDiaryResponse.java
@@ -1,5 +1,6 @@
package com.potatocake.everymoment.dto.response;
+import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
import lombok.Builder;
@@ -17,4 +18,5 @@ public class MyDiaryResponse {
private String content;
private boolean isLiked;
private LocalDateTime createAt;
+ private LocalDate diaryDate;
}
diff --git a/src/main/java/com/potatocake/everymoment/dto/response/MyDiarySimpleResponse.java b/src/main/java/com/potatocake/everymoment/dto/response/MyDiarySimpleResponse.java
index 59e7c70..6e7fa5b 100644
--- a/src/main/java/com/potatocake/everymoment/dto/response/MyDiarySimpleResponse.java
+++ b/src/main/java/com/potatocake/everymoment/dto/response/MyDiarySimpleResponse.java
@@ -1,5 +1,6 @@
package com.potatocake.everymoment.dto.response;
+import java.time.LocalDate;
import java.time.LocalDateTime;
import lombok.Builder;
import lombok.Getter;
@@ -16,4 +17,5 @@ public class MyDiarySimpleResponse {
private ThumbnailResponse thumbnailResponse;
private String content;
private LocalDateTime createAt;
+ private LocalDate diaryDate;
}
diff --git a/src/main/java/com/potatocake/everymoment/entity/Diary.java b/src/main/java/com/potatocake/everymoment/entity/Diary.java
index c139ff3..bbdeedd 100644
--- a/src/main/java/com/potatocake/everymoment/entity/Diary.java
+++ b/src/main/java/com/potatocake/everymoment/entity/Diary.java
@@ -11,6 +11,7 @@
import jakarta.persistence.Lob;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany;
+import java.time.LocalDate;
import java.util.HashSet;
import java.util.Set;
import lombok.AccessLevel;
@@ -37,6 +38,9 @@ public class Diary extends BaseTimeEntity {
@Column(columnDefinition = "TEXT")
private String content;
+ @Column
+ private LocalDate diaryDate;
+
@Column(nullable = false)
private Point locationPoint;
diff --git a/src/main/java/com/potatocake/everymoment/entity/Like.java b/src/main/java/com/potatocake/everymoment/entity/Like.java
index ff8ecfb..2db2b54 100644
--- a/src/main/java/com/potatocake/everymoment/entity/Like.java
+++ b/src/main/java/com/potatocake/everymoment/entity/Like.java
@@ -7,12 +7,18 @@
import jakarta.persistence.Id;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
+import jakarta.persistence.UniqueConstraint;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
-@Table(name = "likes")
+@Table(
+ name = "likes",
+ uniqueConstraints = {
+ @UniqueConstraint(columnNames = {"member_id", "diary_id"})
+ }
+)
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
@Entity
diff --git a/src/main/java/com/potatocake/everymoment/repository/LikeRepository.java b/src/main/java/com/potatocake/everymoment/repository/LikeRepository.java
index 367cd00..fa45665 100644
--- a/src/main/java/com/potatocake/everymoment/repository/LikeRepository.java
+++ b/src/main/java/com/potatocake/everymoment/repository/LikeRepository.java
@@ -2,12 +2,18 @@
import com.potatocake.everymoment.entity.Diary;
import com.potatocake.everymoment.entity.Like;
+import jakarta.persistence.LockModeType;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Lock;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
public interface LikeRepository extends JpaRepository {
- Optional findByMemberIdAndDiaryId(Long memberId, Long diaryId);
+ @Lock(LockModeType.PESSIMISTIC_WRITE)
+ @Query("SELECT l FROM Like l WHERE l.member.id = :memberId AND l.diary.id = :diaryId")
+ Optional findByMemberIdAndDiaryIdWithLock(@Param("memberId") Long memberId, @Param("diaryId") Long diaryId);
Long countByDiary(Diary diary);
diff --git a/src/main/java/com/potatocake/everymoment/repository/MemberRepository.java b/src/main/java/com/potatocake/everymoment/repository/MemberRepository.java
index 7f318ed..6e2e1e7 100644
--- a/src/main/java/com/potatocake/everymoment/repository/MemberRepository.java
+++ b/src/main/java/com/potatocake/everymoment/repository/MemberRepository.java
@@ -1,13 +1,11 @@
package com.potatocake.everymoment.repository;
import com.potatocake.everymoment.entity.Member;
-import jakarta.persistence.LockModeType;
import java.util.Optional;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.ScrollPosition;
import org.springframework.data.domain.Window;
import org.springframework.data.jpa.repository.JpaRepository;
-import org.springframework.data.jpa.repository.Lock;
import org.springframework.data.jpa.repository.Query;
public interface MemberRepository extends JpaRepository {
@@ -18,7 +16,6 @@ public interface MemberRepository extends JpaRepository {
Window findByNicknameContaining(String nickname, ScrollPosition position, Pageable pageable);
- @Lock(LockModeType.PESSIMISTIC_WRITE)
@Query("SELECT CASE WHEN MIN(m.number) > 0 OR MIN(m.number) IS NULL THEN -1 ELSE MIN(m.number) - 1 END FROM Member m")
Long findNextAnonymousNumber();
diff --git a/src/main/java/com/potatocake/everymoment/repository/NotificationRepository.java b/src/main/java/com/potatocake/everymoment/repository/NotificationRepository.java
index 19297a5..e752355 100644
--- a/src/main/java/com/potatocake/everymoment/repository/NotificationRepository.java
+++ b/src/main/java/com/potatocake/everymoment/repository/NotificationRepository.java
@@ -2,8 +2,11 @@
import com.potatocake.everymoment.entity.Notification;
import java.util.List;
+import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.repository.JpaRepository;
public interface NotificationRepository extends JpaRepository {
- List findAllByMemberId(Long memberId);
+
+ List findAllByMemberId(Long memberId, Sort sort);
+
}
diff --git a/src/main/java/com/potatocake/everymoment/service/DiaryService.java b/src/main/java/com/potatocake/everymoment/service/DiaryService.java
index 3378c30..da0c65d 100644
--- a/src/main/java/com/potatocake/everymoment/service/DiaryService.java
+++ b/src/main/java/com/potatocake/everymoment/service/DiaryService.java
@@ -95,6 +95,7 @@ public void createDiaryManual(Long memberId, DiaryManualCreateRequest diaryManua
Diary diary = Diary.builder()
.member(currentMember)
.content(diaryManualCreateRequest.getContent())
+ .diaryDate(diaryManualCreateRequest.getDiaryDate())
.locationPoint(point)
.locationName(diaryManualCreateRequest.getLocationName())
.address(diaryManualCreateRequest.getAddress())
@@ -107,22 +108,8 @@ public void createDiaryManual(Long memberId, DiaryManualCreateRequest diaryManua
//카테고리 저장
List categoryRequestList = diaryManualCreateRequest.getCategories();
- for (CategoryRequest categoryRequest : categoryRequestList) {
- Long categoryId = categoryRequest.getCategoryId();
-
- DiaryCategory diaryCategory = DiaryCategory.builder()
- .diary(savedDiary)
- .category(categoryRepository.findById(categoryId)
- .map(category -> {
- // Category가 현재 사용자의 소유인지 확인
- category.checkOwner(currentMember.getId());
- return category;
- })
- .orElseThrow(() -> new GlobalException(ErrorCode.CATEGORY_NOT_FOUND)))
- .build();
-
- diaryCategoryRepository.save(diaryCategory);
-
+ if (categoryRequestList != null) {
+ addDiaryCategory(savedDiary, currentMember.getId(), categoryRequestList);
}
}
@@ -150,7 +137,8 @@ public MyDiariesResponse getMyDiaries(Long memberId, DiaryFilterRequest diaryFil
today,
diaryFilterRequest.getFrom(),
diaryFilterRequest.getUntil(),
- diaryFilterRequest.getIsBookmark())
+ diaryFilterRequest.getIsBookmark(),
+ diaryFilterRequest.getIsPublic())
.and((root, query, builder) -> builder.equal(root.get("member"), currentMember));
diaryPage = diaryRepository.findAll(spec,
@@ -163,7 +151,8 @@ public MyDiariesResponse getMyDiaries(Long memberId, DiaryFilterRequest diaryFil
diaryFilterRequest.getDate(),
diaryFilterRequest.getFrom(),
diaryFilterRequest.getUntil(),
- diaryFilterRequest.getIsBookmark())
+ diaryFilterRequest.getIsBookmark(),
+ diaryFilterRequest.getIsPublic())
.and((root, query, builder) -> builder.equal(root.get("member"), currentMember));
diaryPage = diaryRepository.findAll(spec,
@@ -213,22 +202,7 @@ public void updateDiary(Long memberId, Long diaryId, DiaryPatchRequest diaryPatc
if (categoryRequestList != null && !categoryRequestList.isEmpty()) {
diaryCategoryRepository.deleteByDiary(existingDiary);
-
- for (CategoryRequest categoryRequest : categoryRequestList) {
- Long categoryId = categoryRequest.getCategoryId();
-
- DiaryCategory diaryCategory = DiaryCategory.builder()
- .diary(existingDiary)
- .category(categoryRepository.findById(categoryId)
- .map(category -> {
- category.checkOwner(memberId);
- return category;
- })
- .orElseThrow(() -> new GlobalException(ErrorCode.CATEGORY_NOT_FOUND)))
- .build();
-
- diaryCategoryRepository.save(diaryCategory);
- }
+ addDiaryCategory(existingDiary, memberId, categoryRequestList);
}
}
@@ -282,6 +256,25 @@ private Diary getExistDiary(Long memberId, Long diaryId) {
return diary;
}
+ //다이어리에 카테고리 추가
+ private void addDiaryCategory(Diary savedDiary, Long memberId, List categoryRequestList) {
+ for (CategoryRequest categoryRequest : categoryRequestList) {
+ Long categoryId = categoryRequest.getCategoryId();
+
+ DiaryCategory diaryCategory = DiaryCategory.builder()
+ .diary(savedDiary)
+ .category(categoryRepository.findById(categoryId)
+ .map(category -> {
+ category.checkOwner(memberId);
+ return category;
+ })
+ .orElseThrow(() -> new GlobalException(ErrorCode.CATEGORY_NOT_FOUND)))
+ .build();
+
+ diaryCategoryRepository.save(diaryCategory);
+ }
+ }
+
//상세 조회시 일기DTO 변환
private MyDiaryResponse convertToMyDiaryResponseDto(Diary savedDiary, Long memberId) {
// 카테고리 찾음
@@ -305,6 +298,7 @@ private MyDiaryResponse convertToMyDiaryResponseDto(Diary savedDiary, Long membe
.content(savedDiary.getContent())
.isLiked(isLiked)
.createAt(savedDiary.getCreateAt())
+ .diaryDate(savedDiary.getDiaryDate())
.build();
}
@@ -329,6 +323,7 @@ private MyDiarySimpleResponse convertToMyDiarySimpleResponseDto(Diary savedDiary
.thumbnailResponse(thumbnailResponse)
.content(savedDiary.getContent())
.createAt(savedDiary.getCreateAt())
+ .diaryDate(savedDiary.getDiaryDate())
.build();
}
}
diff --git a/src/main/java/com/potatocake/everymoment/service/DiarySpecification.java b/src/main/java/com/potatocake/everymoment/service/DiarySpecification.java
index 96e8e9d..01184f3 100644
--- a/src/main/java/com/potatocake/everymoment/service/DiarySpecification.java
+++ b/src/main/java/com/potatocake/everymoment/service/DiarySpecification.java
@@ -18,7 +18,7 @@ public class DiarySpecification {
public static Specification filterDiaries(
String keyword, List emojis, List categories,
- LocalDate date, LocalDate from, LocalDate until, Boolean isBookmark) {
+ LocalDate date, LocalDate from, LocalDate until, Boolean isBookmark, Boolean isPublic) {
return (Root root, CriteriaQuery> query, CriteriaBuilder builder) -> {
Predicate predicate = builder.conjunction();
@@ -36,19 +36,42 @@ public static Specification filterDiaries(
predicate = builder.and(predicate, categoryJoin.get("categoryName").in(categories));
}
- if(date != null){
- predicate = builder.and(predicate, builder.equal(root.get("createAt").as(LocalDate.class), date));
+ if (date != null) {
+ predicate = builder.and(predicate,
+ builder.or(
+ builder.and(
+ builder.isNotNull(root.get("diaryDate")),
+ builder.equal(root.get("diaryDate"), date)
+ ),
+ builder.and(
+ builder.isNull(root.get("diaryDate")),
+ builder.equal(root.get("createAt").as(LocalDate.class), date)
+ )
+ ));
}
if (from != null && until != null) {
predicate = builder.and(predicate,
- builder.between(root.get("createAt"), from, until.plusDays(1)));
+ builder.or(
+ builder.and(
+ builder.isNotNull(root.get("diaryDate")),
+ builder.between(root.get("diaryDate"), from, until)
+ ),
+ builder.and(
+ builder.isNull(root.get("diaryDate")),
+ builder.between(root.get("createAt"), from, until.plusDays(1))
+ )
+ ));
}
if (isBookmark != null) {
predicate = builder.and(predicate, builder.equal(root.get("isBookmark"), isBookmark));
}
+ if (isPublic != null) {
+ predicate = builder.and(predicate, builder.equal(root.get("isPublic"), isPublic));
+ }
+
return predicate;
};
}
diff --git a/src/main/java/com/potatocake/everymoment/service/LikeService.java b/src/main/java/com/potatocake/everymoment/service/LikeService.java
index d2c5586..95584ad 100644
--- a/src/main/java/com/potatocake/everymoment/service/LikeService.java
+++ b/src/main/java/com/potatocake/everymoment/service/LikeService.java
@@ -48,7 +48,7 @@ public void toggleLike(Long memberId, Long diaryId) {
Member member = memberRepository.findById(memberId)
.orElseThrow(() -> new GlobalException(ErrorCode.MEMBER_NOT_FOUND));
- Optional existingLike = likeRepository.findByMemberIdAndDiaryId(memberId, diaryId);
+ Optional existingLike = likeRepository.findByMemberIdAndDiaryIdWithLock(memberId, diaryId);
if (existingLike.isPresent()) {
// 이미 좋아요가 존재하면 삭제 (좋아요 취소)
diff --git a/src/main/java/com/potatocake/everymoment/service/NotificationService.java b/src/main/java/com/potatocake/everymoment/service/NotificationService.java
index 4f5fd36..bd352dd 100644
--- a/src/main/java/com/potatocake/everymoment/service/NotificationService.java
+++ b/src/main/java/com/potatocake/everymoment/service/NotificationService.java
@@ -14,6 +14,7 @@
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
+import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -60,7 +61,8 @@ public List getNotifications(Long memberId) {
Member currentMember = memberRepository.findById(memberId)
.orElseThrow(() -> new GlobalException(ErrorCode.MEMBER_NOT_FOUND));
- List notifications = notificationRepository.findAllByMemberId(currentMember.getId());
+ List notifications = notificationRepository.findAllByMemberId(
+ currentMember.getId(), Sort.by(Sort.Direction.DESC, "createAt"));
return notifications.stream()
.map(this::convertToNotificationResponseDTO)
diff --git a/src/test/java/com/potatocake/everymoment/repository/DiaryRepositoryTest.java b/src/test/java/com/potatocake/everymoment/repository/DiaryRepositoryTest.java
index 74f276c..37ec8df 100644
--- a/src/test/java/com/potatocake/everymoment/repository/DiaryRepositoryTest.java
+++ b/src/test/java/com/potatocake/everymoment/repository/DiaryRepositoryTest.java
@@ -93,7 +93,7 @@ void should_FindDiaries_When_FilteringWithSearchCriteria() {
.locationName("Busan")
.address("Busan Address")
.emoji("😍")
- .isPublic(true)
+ .isPublic(false)
.isBookmark(false)
.build();
@@ -107,7 +107,8 @@ void should_FindDiaries_When_FilteringWithSearchCriteria() {
null, // date
null, // from
null, // until
- false // isBookmark
+ false, // isBookmark
+ true // isPublic
);
Page result = diaryRepository.findAll(spec, PageRequest.of(0, 10));
@@ -132,7 +133,7 @@ void should_FindDiaries_When_FilteringBookmarked() {
.locationName("Seoul")
.address("Seoul Address")
.isBookmark(true)
- .isPublic(false)
+ .isPublic(true)
.build();
Diary diary2 = createDiary(member, "Content 2", "Busan", "Busan Address");
@@ -156,7 +157,8 @@ void should_FindDiaries_When_FilteringBookmarked() {
null, // date
null, // from
null, // until
- true // isBookmark
+ true, // isBookmark
+ true // isPublic
);
Page result = diaryRepository.findAll(spec, PageRequest.of(0, 10));
diff --git a/src/test/java/com/potatocake/everymoment/repository/LikeRepositoryTest.java b/src/test/java/com/potatocake/everymoment/repository/LikeRepositoryTest.java
index 87027c4..d3f3b8b 100644
--- a/src/test/java/com/potatocake/everymoment/repository/LikeRepositoryTest.java
+++ b/src/test/java/com/potatocake/everymoment/repository/LikeRepositoryTest.java
@@ -96,7 +96,7 @@ void should_FindLike_When_FilteringByMemberAndDiary() {
likeRepository.save(like);
// when
- Optional foundLike = likeRepository.findByMemberIdAndDiaryId(member.getId(), diary.getId());
+ Optional foundLike = likeRepository.findByMemberIdAndDiaryIdWithLock(member.getId(), diary.getId());
// then
assertThat(foundLike).isPresent();
diff --git a/src/test/java/com/potatocake/everymoment/repository/NotificationRepositoryTest.java b/src/test/java/com/potatocake/everymoment/repository/NotificationRepositoryTest.java
index b5924c8..76177f4 100644
--- a/src/test/java/com/potatocake/everymoment/repository/NotificationRepositoryTest.java
+++ b/src/test/java/com/potatocake/everymoment/repository/NotificationRepositoryTest.java
@@ -9,6 +9,7 @@
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
+import org.springframework.data.domain.Sort;
import org.springframework.test.context.TestPropertySource;
@TestPropertySource(properties = "spring.jpa.hibernate.ddl-auto=create-drop")
@@ -79,7 +80,7 @@ void should_FindNotifications_When_FilteringByMemberId() {
// when
List foundNotifications = notificationRepository
- .findAllByMemberId(savedMember.getId());
+ .findAllByMemberId(savedMember.getId(), Sort.by(Sort.Direction.DESC, "createAt"));
// then
assertThat(foundNotifications).hasSize(2);
@@ -113,7 +114,7 @@ void should_DeleteNotification_When_ValidEntity() {
// then
List remainingNotifications = notificationRepository
- .findAllByMemberId(savedMember.getId());
+ .findAllByMemberId(savedMember.getId(), Sort.by(Sort.Direction.DESC, "createAt"));
assertThat(remainingNotifications).isEmpty();
}
diff --git a/src/test/java/com/potatocake/everymoment/service/LikeServiceTest.java b/src/test/java/com/potatocake/everymoment/service/LikeServiceTest.java
index f645c71..0bd3eb7 100644
--- a/src/test/java/com/potatocake/everymoment/service/LikeServiceTest.java
+++ b/src/test/java/com/potatocake/everymoment/service/LikeServiceTest.java
@@ -85,7 +85,7 @@ void should_AddLike_When_NotLiked() {
given(memberRepository.findById(memberId)).willReturn(Optional.of(member));
given(diaryRepository.findById(diaryId)).willReturn(Optional.of(diary));
- given(likeRepository.findByMemberIdAndDiaryId(memberId, diaryId))
+ given(likeRepository.findByMemberIdAndDiaryIdWithLock(memberId, diaryId))
.willReturn(Optional.empty());
// when
@@ -122,7 +122,7 @@ void should_RemoveLike_When_AlreadyLiked() {
given(memberRepository.findById(memberId)).willReturn(Optional.of(member));
given(diaryRepository.findById(diaryId)).willReturn(Optional.of(diary));
- given(likeRepository.findByMemberIdAndDiaryId(memberId, diaryId))
+ given(likeRepository.findByMemberIdAndDiaryIdWithLock(memberId, diaryId))
.willReturn(Optional.of(like));
// when
@@ -138,9 +138,7 @@ void should_ThrowException_When_DiaryNotPublic() {
// given
Long memberId = 1L;
Long diaryId = 1L;
- Member member = Member.builder()
- .id(memberId)
- .build();
+
Diary diary = Diary.builder()
.id(diaryId)
.isPublic(false)
diff --git a/src/test/java/com/potatocake/everymoment/service/NotificationServiceTest.java b/src/test/java/com/potatocake/everymoment/service/NotificationServiceTest.java
index 4ad4aa0..7d842fa 100644
--- a/src/test/java/com/potatocake/everymoment/service/NotificationServiceTest.java
+++ b/src/test/java/com/potatocake/everymoment/service/NotificationServiceTest.java
@@ -24,6 +24,7 @@
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.data.domain.Sort;
@ExtendWith(MockitoExtension.class)
class NotificationServiceTest {
@@ -104,7 +105,7 @@ void should_GetNotifications_When_ValidMemberId() {
);
given(memberRepository.findById(memberId)).willReturn(Optional.of(member));
- given(notificationRepository.findAllByMemberId(memberId)).willReturn(notifications);
+ given(notificationRepository.findAllByMemberId(memberId, Sort.by(Sort.Direction.DESC, "createAt"))).willReturn(notifications);
// when
List responses = notificationService.getNotifications(memberId);