Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

merge: 11주차 작업 master에 반영 #114

Merged
merged 28 commits into from
Nov 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
bfd1d16
feat: 비관적 락 warning 해결
peeerr Nov 12, 2024
d1ea11d
feat: 로그 추가
peeerr Nov 12, 2024
9fdbd16
feat: 쿼리 수정
peeerr Nov 12, 2024
e6b8e6e
Merge pull request #100 from kakao-tech-campus-2nd-step3/feature/99-log
peeerr Nov 12, 2024
c3ad892
feat: 프로필 이미지 업데이트 로직에 로그 추가
peeerr Nov 12, 2024
f049834
Merge pull request #101 from kakao-tech-campus-2nd-step3/peeerr-patch-1
peeerr Nov 12, 2024
1bed045
feat: 불필요한 로그 제거
peeerr Nov 13, 2024
15d98d3
Merge pull request #103 from kakao-tech-campus-2nd-step3/feature/102-…
peeerr Nov 13, 2024
15fd18a
fix: 카테고리 없이 저장 안되는 오류 수정
Nov 13, 2024
5a9cac8
feat: diary에 diaryDate 추가
Nov 13, 2024
c5ec931
feat: 알람 조회 최신순으로 변경
Nov 13, 2024
d52c56c
Merge pull request #105 from HyeJiJUN11/feature/104-diary
peeerr Nov 13, 2024
731a17e
feat: 유니크 제약 조건 추가
peeerr Nov 14, 2024
f2ccd6e
feat: 비관적 락 적용
peeerr Nov 14, 2024
8a31893
Merge pull request #107 from kakao-tech-campus-2nd-step3/feature/106-…
peeerr Nov 14, 2024
2c935a7
test: 비관적 락 적용으로 테스트 코드 수정
peeerr Nov 14, 2024
4749749
Merge pull request #108 from kakao-tech-campus-2nd-step3/feature/106-…
peeerr Nov 14, 2024
5686e82
feat: 검색 조건에 공유 여부 추가
peeerr Nov 14, 2024
77266cf
Merge pull request #110 from kakao-tech-campus-2nd-step3/feature/109-…
peeerr Nov 14, 2024
b8b93d1
feat: 테스트 커버리지 측정 자동화
peeerr Nov 14, 2024
c10dee6
fix: 들여쓰기 수정
peeerr Nov 14, 2024
e0074dd
feat: 테스트 커버리지 최소 기준 명시
peeerr Nov 14, 2024
234bc8f
feat: 측정 제외 대상 추가
peeerr Nov 14, 2024
efcec90
Merge pull request #112 from kakao-tech-campus-2nd-step3/feature/111-…
peeerr Nov 14, 2024
c225da9
docs: 리드미 정리
peeerr Nov 14, 2024
39d12b1
Merge pull request #113 from kakao-tech-campus-2nd-step3/peeerr-patch-1
peeerr Nov 14, 2024
8f35bd2
docs: 중복된 내용 제거
peeerr Nov 14, 2024
0ca413e
docs: 기술 스택 정리
peeerr Nov 14, 2024
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
18 changes: 17 additions & 1 deletion .github/workflows/pr_weekly_ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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/[email protected]
with:
paths: ${{ github.workspace }}/build/reports/jacoco/test/jacocoTestReport.xml
token: ${{ github.token }}
min-coverage-overall: 70
min-coverage-changed-files: 70
title: '📊 테스트 커버리지 리포트'
149 changes: 147 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,147 @@
# Team21_BE
21조 백엔드
# Every Moment
> 하루를 자동으로 기록하고 공유하는 위치 기반 소셜 다이어리 📝

![image](https://github.com/user-attachments/assets/d2be179d-e5e4-4b14-914f-8d88f5e6f7b5)

<br/>

## 📌 프로젝트 소개
바쁜 일상 속에서 매 순간을 기록하기란 쉽지 않습니다.
Every Moment는 위치 데이터를 기반으로 사용자의 하루를 **자동**으로 기록하고 친구들과 공유할 수 있는 소셜 다이어리 서비스입니다.

### 핵심 기능
- 📍 **위치 기반 자동 기록**: 15분 이상 머문 장소 자동 감지 및 일기로 기록

- 🤝 **소셜 다이어리**: 친구들과 일기 공유 및 소통
- 🔍 **스마트 검색**: 다양한 필터링 옵션으로 원하는 일기 빠른 검색
- 🔔 **실시간 알림**: 친구와의 상호작용(좋아요, 댓글, 친구 요청 등)과 새로운 장소 감지를 실시간으로 알림

<br/>

## ⭐️ 주요 기능
| 자동 일기 기록 | 손쉬운 일기 편집 | 다양한 검색 필터링 | 친구와의 일기 공유 |
|:---:|:---:|:---:|:---:|
| <img src="https://github.com/user-attachments/assets/c7aa92f4-0ab9-44bb-8abf-872eaeeefcbb" width="200px"> | <img src="https://github.com/user-attachments/assets/a3557c70-d183-4929-ab36-5833bacf2e36" width="200px"> | <img src="https://github.com/user-attachments/assets/3448752f-2c20-4d72-b59f-560708a6e037" width="200px"> | <img src="https://github.com/user-attachments/assets/fb29d6e7-8325-4656-8514-e6767bc034fc" width="200px"> |
| 15분동안 머문 장소를<br>자동으로 기록 | 저장된 일기를<br>손쉽게 편집 | 다양한 검색 조건으로<br>손쉬운 일기 찾기 | 원하는 일기를 공유하여<br>친구와 소통 |

<br/>

## 🔍 프로젝트 정보
### 개발 기간
- 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)**

<br/>

## 👥 팀원 소개
저희 팀은 **백엔드 2명, 안드로이드 3명**으로 구성되어 있습니다.

<table align="center">
<tr align="center">
<td><B>최준형<B></td>
<td><B>전혜지<B></td>
<td><B>이아림<B></td>
<td><B>윤채원<B></td>
<td><B>권새일<B></td>
</tr>
<tr align="center">
<td>
<a href="https://github.com/peeerr">
<img src="https://github.com/peeerr.png" style="max-width: 100px">
</a>
<br>
<a href="https://github.com/peeerr"><B>Backend</B></a>
</td>
<td>
<a href="https://github.com/HyeJiJUN11">
<img src="https://github.com/HyeJiJUN11.png" style="max-width: 100px">
</a>
<br>
<a href="https://github.com/HyeJiJUN11"><B>Backend</B></a>
</td>
<td>
<a href="https://github.com/arieum">
<img src="https://github.com/arieum.png" style="max-width: 100px">
</a>
<br>
<a href="https://github.com/arieum"><B>Android</B></a>
</td>
<td>
<a href="https://github.com/settle54">
<img src="https://github.com/settle54.png" style="max-width: 100px">
</a>
<br>
<a href="https://github.com/settle54"><B>Android</B></a>
</td>
<td>
<a href="https://github.com/todlf">
<img src="https://github.com/todlf.png" style="max-width: 100px">
</a>
<br>
<a href="https://github.com/todlf"><B>Android</B></a>
</td>
</tr>
</table>

<br/>

## 🛠 기술 스택
### 💻 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 (인증/인가)

<br/>

## 🗄️ ERD
![erd](https://github.com/user-attachments/assets/72e66248-f217-434a-9f20-d8150abafee4)

<br/>

## 🔍 개발 주안점
- **실시간성 확보**
- FCM을 활용한 즉각적인 푸시 알림 구현

- 댓글, 좋아요, 친구 요청, 자동 일기 작성 등 다양한 이벤트에 대한 실시간 알림

- **CI/CD 파이프라인 구축 및 최적화**
- Github Actions를 활용한 배포 자동화

- PR 단계에서 자동화된 테스트 실행으로 개발 생산성 향상
- 이전에는 배포 환경에서 테스트 실패 시 새로운 PR을 생성하고, 배포하는 과정을 반복해야 했음

- **테스트 작성**
- 70% 이상의 테스트 커버리지 유지
39 changes: 39 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -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'
}
Expand Down Expand Up @@ -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
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@ public ResponseEntity<SuccessResponse<MyDiariesResponse>> 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 = "페이지 크기")
Expand All @@ -115,6 +117,7 @@ public ResponseEntity<SuccessResponse<MyDiariesResponse>> getMyDiaries(
.from(from)
.until(until)
.isBookmark(bookmark)
.isPublic(isPublic)
.key(key)
.size(size)
.build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -11,6 +12,8 @@
@Getter
public class DiaryManualCreateRequest {

private LocalDate diaryDate;

private List<CategoryRequest> categories;

private LocationPoint locationPoint;
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -17,4 +18,5 @@ public class MyDiaryResponse {
private String content;
private boolean isLiked;
private LocalDateTime createAt;
private LocalDate diaryDate;
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.potatocake.everymoment.dto.response;

import java.time.LocalDate;
import java.time.LocalDateTime;
import lombok.Builder;
import lombok.Getter;
Expand All @@ -16,4 +17,5 @@ public class MyDiarySimpleResponse {
private ThumbnailResponse thumbnailResponse;
private String content;
private LocalDateTime createAt;
private LocalDate diaryDate;
}
4 changes: 4 additions & 0 deletions src/main/java/com/potatocake/everymoment/entity/Diary.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;

Expand Down
8 changes: 7 additions & 1 deletion src/main/java/com/potatocake/everymoment/entity/Like.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Like, Long> {

Optional<Like> 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<Like> findByMemberIdAndDiaryIdWithLock(@Param("memberId") Long memberId, @Param("diaryId") Long diaryId);

Long countByDiary(Diary diary);

Expand Down
Original file line number Diff line number Diff line change
@@ -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<Member, Long> {
Expand All @@ -18,7 +16,6 @@ public interface MemberRepository extends JpaRepository<Member, Long> {

Window<Member> 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();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Notification, Long> {
List<Notification> findAllByMemberId(Long memberId);

List<Notification> findAllByMemberId(Long memberId, Sort sort);

}
Loading
Loading