Skip to content

Commit 59798c3

Browse files
min0962chw0912chcch529rjswjddngffd94
authored
release: 개발 완료 코드 release 브랜치 병합 (#72)
* feat: websocket 의존성 추가 및 STOMP config 설정 (#5) * feat: 예외처리 구현 (#6) * feat: 예외처리 구현 * fix: 불필요한 파일 제거 * [Feat]: 회원 API 구현 (#11) * chore: gitignore에 yml 추가 * feat: member controller, service, repository 생성 * feat: 카카오 로그인 구현 * feat: 게스트 로그인 구현 * feat: 로그인 확인 API 구현 * feat: 로그아웃 API 구현 * refactor: 리팩토링 * fix: 게스트 로그인 응답 수정 * feat: 게스트 닉네임 길이 검증 추가 * chore: .env 파일 설정 * feat: 테스트 자동화 CI 구현 및 슬랙 이모지 적용 (#14) * feat : 질문 생성 소켓 api 구현 (#16) * feat: question 생성 서비스 * fix: dto 명 수정 * feat: question ws 컨트롤러 * feat: 참가자 검증 추가 * fix: yml 파일 제외 * chore: 패키지 구조 수정 * feat: entity에 builder 추가 * test: createQuestion 테스트 * feat: principal에서 memberId 추출 * remove: env 파일 삭제 * feat: dto에 빌더 추가 * [Feat]: 발표방 생성, 팀원 이메일 검증 API (#18) * feat: 이메일로 회원 검색 API * feat: 발표방 생성 API * feat: 비밀코드 중복 확인 로직 추가 * refactor: 엔티티 생성자, 빌더 수정 * refactor: Repository 네이밍 수정 * refactor: Util 클래스 생성자 추가 * feat: 발표방, 질문, 로그인 Swagger 작성 (#21) Co-authored-by: STGRAM\gffd9 <[email protected]> * [Feat]: 마이페이지 API 구현 (#20) * feat : 멤버 프로필 조회 구현 * chore : yml파일 gitignore 추가 * chore : properties 파일 삭제 * refactor : 멤버 조회 예외처리 적용 * test : 프로필 조회 서비스 단위테스트 작성 * feat : 멤버 닉네임 수정 구현 * test : 멤버 닉네임 수정 엔티티, 서비스 단위 테스트코드 작성 * feat : 참여방 목록 조회 api 구현 * bug : jpql 오타 수정 * test : 참가자 리포 슬라이스 테스트코드 작성 * bug : 발표방 조회 map 수정 * refactor : test 코드 변수 접근제어자 수정 * refactor : valid 추가 * refactor : 안쓰는 코드 제거 * refactor : import 추가 * refactor : merge에 따른 변경사항 변경 * refactor : repo 테스트 어노테이션 제거 * refactor : repo 테스트 어노테이션 추가 * refactor : mapper 클래스 추가 * refactor : if -> switch로 변경 * chore: 빠진 import 추가 * feat: 비밀 코드 방 입장, 상세 조회 API 구현 (#23) * feat: 비밀코드로 방 입장 API 구현 * feat: 발표방 상세 조회 API 구현 * fix: 발표방 생성 수정 * feat: 질문 정렬 api (#26) * feat: 매퍼클래스 생성 제한 * feat: question 기본 조회 * feat: 조건별 정렬 * feat: 조회 조건에 roomId 추가 * build: 롬복 테스트 의존성 추가 * test: question 정렬 서비스 테스트 * test: question 정렬 리포지토리 테스트 * chore: test yml 파일 추가 * refactor: 반환 dto 구조 변경 * feat: 질문 정렬 api * feat: dto 설명 추가 * fix: test 프로파일 추가 * feat: 수정, 삭제, 상태 변경 API 구현 (#28) * feat: 발표방 수정 API 구현 * feat: 발표방 삭제 API 구현 * feat: 발표방 상태 수정 API 구현 * feat: 수정용 발표방 조회 API 구현 * fix: 발표방 상태 변경 수정 * feat: 개발용 회원 API (#30) * feat: 개발용 회원 가입 API * feat: 개발용 로그인 API * feat: S3 환경 설정 및 Presigned URL 발급 구현 (#31) * feat: 답변 생성 소켓 API (#32) * 발표자 팀원 답변검증 * Answer생성 구현,테스트코드 구현 * permissionValidTest 작성 * Socket통신 테스트 완료 * Question 인덱싱수정 * PR 리뷰내용 반영 * feat: 공감 api (#37) * feat : 공감 관련 dto,repo,컨트롤러 생성 * feat : 공감 api 서비스 구현 * feat : 공감 api 구현 * test : 공감 api 테스트코드 작성 * refactor : 병합 과정에서 수정 * refactor : 병합 과정에서 수정 * refactor : 프로필 수정 반환 void로 변경 * docs: 마이페이지, 공감 Swagger 작성 * refactor: Emoji create 추가 * refactor: else 제거 * refactor: 계층 추가 리팩토링 (#40) * refactor: RoomReader 적용 * refactor: QuestionReader 적용 * refactor: MemberReader 적용 * refactor: ParticipantReader 적용 * refactor: EmojiReader 적용 * refactor: DocumentReader 적용 * refactor: AnswerReader 적용 * test: AnswerServiceTests 수정 * test: EmojiServiceTests 수정 * test: MyPageServiceTests 수정 * test: QuestionServiceTests 수정 * refactor: service 의존 리팩토링 * refactor: Mapper 패키지 Util로 변경 * feat: 발표방 CRUD 시 발표자료 CRUD 로직 추가 (#41) * feat: 소켓 예외 처리 (#42) * feat: 소켓 초기 연결 예외 * feat: 비즈니즈 예외 처리 * fix: 에러 응답 문제 해결 * fix: oauth principal 설정 수정 * refactor: memberId 추출 구조 변경 * feat: 답변 조회 API (#43) * 답변 조회 API * 답변 조회 테스트 코드작성 * PR 테스트 충돌해결 * fix: 공감 버전 충돌 시 재시도 로직 (#49) * feat: 공감 동시성 제어를 위한 EmojiFacade 추가 * test: EmojiFacade 동시성(50명) 테스트 코드 작성 * fix: EmojiFacade 예외처리 수정 * feat: 질문 수정, 삭제 api (#51) * feat: 질문 수정 api * test: 질문 수정 서비스 테스트 * feat: 질문 삭제 api * test: 참가자 권한 조회 리포 테스트 * test: 질문 삭제 서비스 테스트 * feat: 예외 구체화 * fix: 테스트 호출 메서드 변경 * fix: errorCode 넘버링 수정 * feat: 질문 삭제 시 답변도 삭제 * style: 개행 제거 * feat: 결과 리포트 조회 기능 구현 (#52) * feat: 결과 리포트 조회 기능 구현 * chore: 불필요한 쿼리문 제거 * refactor: Top3QnA 답변 리스트로 조회 리팩토링 (#55) * feat: 공감 API Rate Limiting 적용 / 공감 API 분리 (#56) * refactor: 공감 api 분리 * feat: 공감 api rate limit 적용 * refactor: 이모지 요청 dto validation 적용 * fix: 공감 생성, 삭제 예외처리 추가 * style: 버킷 메서드명 변경 * style: 버킷 메서드명 변경 * feat: 발표방 참여 및 입장 인원 계산 로직 (#57) * feat: 방 참가 웹소켓 구현 * feat: 방 퇴장 웹소켓 구현 * feat: 방 구독 및 참여 조건 구현 * feat: 방 상세조회 인원 수정 * feat: 세션 관리, 방 퇴장 수정 * refactor: 질문 정렬에 queryDsl 적용 (#59) * build: 의존성 추가 * refactor: querydsl 적용 * refactor: 서비스 코드 수정 * test: 테스트 코드 수정 * style: 불필요한 import 제거 * test: repo 테스트들 config 추가 * feat : 답변 수정 삭제 api (#61) * 답변 수정/삭제 구현 * 답변 수정 삭제 테스트 작성 * feat: 질문 생성 rate limiting (#65) * feat: 질문 dto validation 추가 * feat: 질문 rate limit 적용 * feat: bucket 조건 변경 * feat: enum type 이름 변경 * feat: valid 추가 * feat: 참가자 퇴장 시간 저장 및 웹소켓, 발표방 리팩토링 (#64) * docs: 발표방 관련 스웨거 설명 추가, 비밀코드 방 참가 조건 수정 * refactor: 발표방 리팩토링 * refactor: 회원, 인증 리팩토링 * refactor: websocket 패키지 구조 변경 * feat: 방 삭제 이벤트로 수정 * feat: 참가자 퇴장 시간 기록 구현 * feat: CD 파이프라인 구축 (#71) * chore: .gitignore 추가 * feat: release-workflow, Dockerfile 추가 * fix: main에서 release로 변경 --------- Co-authored-by: Huiwoong Choi <[email protected]> Co-authored-by: chcch529 <[email protected]> Co-authored-by: 김건우 <[email protected]> Co-authored-by: SeungTae <[email protected]> Co-authored-by: STGRAM\gffd9 <[email protected]>
1 parent c14e475 commit 59798c3

File tree

144 files changed

+6864
-26
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

144 files changed

+6864
-26
lines changed

.github/workflows/ci.yml

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
name: Pull Request Test Automation
2+
on:
3+
pull_request:
4+
branches:
5+
- dev
6+
7+
jobs:
8+
9+
code-test:
10+
name: 코드 테스트
11+
runs-on: ubuntu-latest
12+
13+
env:
14+
SPRING_PROFILES_ACTIVE: ci
15+
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
16+
17+
steps:
18+
- name: 브랜치로 체크아웃
19+
uses: actions/checkout@v4
20+
21+
- name: JDK 세팅
22+
uses: actions/setup-java@v4
23+
with:
24+
java-version: '21'
25+
distribution: 'temurin'
26+
27+
- name: application-ci.yml 주입
28+
run: |
29+
mkdir -p src/main/resources
30+
echo "${{ secrets.APPLICATION_YML_CI }}" | base64 --decode > src/main/resources/application-ci.yml
31+
32+
- name: 권한 세팅
33+
run: chmod +x ./gradlew
34+
35+
- name: 테스트 수행
36+
run: ./gradlew test --no-daemon
37+
38+
- name: Slack 알림 보내기(테스트 실패 시)
39+
if: failure()
40+
uses: slackapi/[email protected]
41+
with:
42+
payload: |
43+
{
44+
"text": "🔴 *CI 테스트 실패 알림*\n리포지토리: `${{ github.repository }}`\n브랜치: `${{ github.ref_name }}`\nPR 번호: `${{ github.event.pull_request.number }}`\nPR 제목: `${{ github.event.pull_request.title }}`\n워크플로우: <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|${{ github.workflow }}>\n실패 커밋: `${{ github.event.pull_request.head.sha }}`"
45+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
name: OromaminC Backend Service Release
2+
3+
on:
4+
push:
5+
branches:
6+
- release
7+
8+
env:
9+
REGISTRY: ghcr.io
10+
IMAGE_NAME: ${{ github.repository }}
11+
12+
jobs:
13+
14+
tagging:
15+
name: 태깅 및 릴리즈
16+
runs-on: ubuntu-latest
17+
outputs:
18+
tag_name: ${{ steps.tag_version.outputs.new_tag }}
19+
20+
steps:
21+
- uses: actions/checkout@v4
22+
23+
- name: versioning and tagging
24+
id: tag_version
25+
uses: mathieudutour/[email protected]
26+
with:
27+
github_token: ${{ secrets.GITHUB_TOKEN }}
28+
29+
- name: releasing
30+
uses: ncipollo/release-action@v1
31+
with:
32+
tag: ${{ steps.tag_version.outputs.new_tag }}
33+
name: ${{ steps.tag_version.outputs.new_tag }}
34+
body: ${{ steps.tag_version.outputs.changelog }}
35+
36+
build-image:
37+
name: 도커 이미지 빌드
38+
runs-on: ubuntu-latest
39+
needs: tagging
40+
41+
permissions:
42+
contents: read
43+
packages: write
44+
attestations: write
45+
id-token: write
46+
47+
steps:
48+
- name: Check out Repository
49+
uses: actions/checkout@v4
50+
51+
- name: Setting for Developent
52+
run: echo "${{ secrets.APPLICATION_DEV_YML }}" > src/main/resources/application-dev.yml
53+
54+
- name: Sign in github container registry
55+
uses: docker/login-action@v3
56+
with:
57+
registry: ${{ env.REGISTRY }}
58+
username: ${{ github.actor }}
59+
password: ${{ secrets.GITHUB_TOKEN }}
60+
61+
- name: Extract metadata
62+
id: meta
63+
uses: docker/metadata-action@v5
64+
with:
65+
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
66+
tags: |
67+
type=sha
68+
type=raw,value=${{ needs.tagging.outputs.tag_name }}
69+
type=raw,value=latest
70+
71+
- name: Build and Push Image
72+
uses: docker/build-push-action@v6
73+
with:
74+
context: .
75+
push: true
76+
tags: ${{ steps.meta.outputs.tags }}
77+
labels: ${{ steps.meta.outputs.labels }}

.github/workflows/slack-pr.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ jobs:
1313
with:
1414
payload: |
1515
{
16-
"text": "${{ github.event.action == 'submitted' && ':돋보기: PR 리뷰가 등록되었습니다' || github.event.action == 'closed' && github.event.pull_request.merged && ':흰색_확인_표시: PR이 머지되었습니다' || github.event.action == 'opened' && ':로켓: 새 PR이 생성되었습니다' || ':시계_반대_방향_화살표: PR 이벤트가 발생했습니다' }}: *${{ github.event.pull_request.title }}* by `${{ github.event.pull_request.user.login }}`\n<${{ github.event.pull_request.html_url }}|PR 보러가기>"
16+
"text": "${{ github.event.action == 'submitted' && '🔎 PR 리뷰가 등록되었습니다' || github.event.action == 'closed' && github.event.pull_request.merged && ' PR이 머지되었습니다' || github.event.action == 'opened' && '🚀 새 PR이 생성되었습니다' || '🔄 PR 이벤트가 발생했습니다' }}: *${{ github.event.pull_request.title }}* by `${{ github.event.pull_request.user.login }}`\n<${{ github.event.pull_request.html_url }}|PR 보러가기>"
1717
}
1818
env:
1919
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ build/
55
!**/src/main/**/build/
66
!**/src/test/**/build/
77

8+
*.yml
9+
*.env
10+
811
### STS ###
912
.apt_generated
1013
.classpath
@@ -34,4 +37,4 @@ out/
3437
/.nb-gradle/
3538

3639
### VS Code ###
37-
.vscode/
40+
.vscode/

Dockerfile

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
FROM gradle:jdk21 as builder
2+
3+
WORKDIR /libs
4+
5+
COPY gradlew .
6+
COPY gradle gradle
7+
COPY build.gradle .
8+
COPY settings.gradle .
9+
10+
RUN ./gradlew dependencies --no-daemon || true
11+
12+
COPY src src
13+
14+
RUN ./gradlew build --no-daemon -x test
15+
16+
17+
FROM openjdk:21-slim
18+
19+
WORKDIR /app
20+
21+
COPY --from=builder /libs/build/libs/*.jar app.jar
22+
23+
ENTRYPOINT ["java", "-Dspring.profiles.active=dev", "-jar", "app.jar"]
24+

build.gradle

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ plugins {
55
}
66

77
group = 'com.oronaminc'
8-
version = '0.0.1-SNAPSHOT'
8+
version = '0.1'
99

1010
java {
1111
toolchain {
@@ -26,12 +26,38 @@ repositories {
2626
dependencies {
2727
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
2828
implementation 'org.springframework.boot:spring-boot-starter-web'
29+
implementation 'org.springframework.boot:spring-boot-starter-websocket'
30+
implementation 'org.springframework.boot:spring-boot-starter-security'
31+
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
32+
implementation 'org.springframework.boot:spring-boot-starter-validation'
33+
testImplementation 'org.springframework.security:spring-security-test'
34+
2935
compileOnly 'org.projectlombok:lombok'
36+
annotationProcessor 'org.projectlombok:lombok'
37+
testAnnotationProcessor 'org.projectlombok:lombok'
38+
testImplementation 'org.projectlombok:lombok'
39+
3040
runtimeOnly 'com.h2database:h2'
3141
runtimeOnly 'com.mysql:mysql-connector-j'
3242
annotationProcessor 'org.projectlombok:lombok'
3343
testImplementation 'org.springframework.boot:spring-boot-starter-test'
3444
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
45+
// swagger
46+
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.9'
47+
// env 파일
48+
implementation 'io.github.cdimascio:dotenv-java:3.2.0'
49+
50+
// S3
51+
implementation 'software.amazon.awssdk:s3:2.31.77'
52+
53+
// querydsl
54+
implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta'
55+
annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jakarta"
56+
annotationProcessor("jakarta.persistence:jakarta.persistence-api")
57+
annotationProcessor("jakarta.annotation:jakarta.annotation-api")
58+
59+
// bucket4j
60+
implementation 'com.bucket4j:bucket4j_jdk17-core:8.14.0'
3561
}
3662

3763
tasks.named('test') {

src/main/java/com/oronaminc/join/Web57OronaminCBeApplication.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22

33
import org.springframework.boot.SpringApplication;
44
import org.springframework.boot.autoconfigure.SpringBootApplication;
5+
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
56

7+
@EnableJpaAuditing
68
@SpringBootApplication
79
public class Web57OronaminCBeApplication {
810

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package com.oronaminc.join.answer.api;
2+
3+
import com.oronaminc.join.answer.dto.AnswerGetResponse;
4+
import com.oronaminc.join.answer.service.AnswerService;
5+
import com.oronaminc.join.member.security.MemberDetails;
6+
import io.swagger.v3.oas.annotations.Operation;
7+
import io.swagger.v3.oas.annotations.responses.ApiResponse;
8+
import lombok.RequiredArgsConstructor;
9+
import org.springframework.http.HttpStatus;
10+
import org.springframework.http.ResponseEntity;
11+
import org.springframework.security.core.annotation.AuthenticationPrincipal;
12+
import org.springframework.web.bind.annotation.GetMapping;
13+
import org.springframework.web.bind.annotation.PathVariable;
14+
import org.springframework.web.bind.annotation.RequestMapping;
15+
import org.springframework.web.bind.annotation.ResponseStatus;
16+
import org.springframework.web.bind.annotation.RestController;
17+
18+
@RestController
19+
@RequestMapping("/api")
20+
@RequiredArgsConstructor
21+
public class AnswerController {
22+
23+
private final AnswerService answerService;
24+
25+
@Operation(
26+
summary = "답변 조회",
27+
description = "답변 보기 클릭 시 질문에 대한 답변을 조회",
28+
responses = {
29+
@ApiResponse(responseCode = "200", description = "답변 조회 성공"),
30+
@ApiResponse(responseCode = "400", description = "답변이 없는데 답변보기 버튼이 활성화 되어 잘못된 조회 접근")
31+
32+
}
33+
)
34+
@GetMapping("/rooms/{roomId}/questions/{questionId}/answers")
35+
@ResponseStatus(HttpStatus.OK)
36+
public ResponseEntity<AnswerGetResponse> getAnswer(
37+
@PathVariable Long roomId,
38+
@PathVariable Long questionId,
39+
@AuthenticationPrincipal MemberDetails memberDetails
40+
) {
41+
Long memberId = memberDetails.getId();
42+
AnswerGetResponse response = answerService.getAnswer(roomId, questionId, memberId);
43+
return ResponseEntity.ok(response);
44+
}
45+
46+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package com.oronaminc.join.answer.dao;
2+
3+
import java.util.List;
4+
import java.util.Optional;
5+
import org.springframework.data.jpa.repository.JpaRepository;
6+
import com.oronaminc.join.answer.domain.Answer;
7+
import com.oronaminc.join.question.domain.Question;
8+
import org.springframework.data.jpa.repository.Query;
9+
import org.springframework.data.repository.query.Param;
10+
11+
public interface AnswerRepository extends JpaRepository<Answer, Long> {
12+
13+
Optional<Answer> findByQuestionId(Long questionId);
14+
15+
boolean existsByQuestionIdAndMemberId(Long questionId, Long memberId);
16+
17+
void deleteByQuestionId(Long questionId);
18+
19+
void deleteByQuestionIn(List<Question> questions);
20+
21+
@Query("""
22+
select count(distinct a.question.id)
23+
from Answer a
24+
where a.question.room.id = :roomId
25+
""")
26+
Long countAnsweredQuestionsByRoomId(@Param("roomId") Long roomId);
27+
28+
@Query("""
29+
select a
30+
from Answer a
31+
where a.question.id in :questionIds
32+
""")
33+
List<Answer> findAllByQuestionIds(@Param("questionIds") List<Long> questionIds);
34+
35+
}

src/main/java/com/oronaminc/join/answer/domain/Answer.java

Lines changed: 47 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,27 +3,45 @@
33
import com.oronaminc.join.global.entity.BaseEntity;
44
import com.oronaminc.join.member.domain.Member;
55
import com.oronaminc.join.question.domain.Question;
6-
import jakarta.persistence.*;
6+
7+
import jakarta.persistence.Entity;
8+
import jakarta.persistence.FetchType;
9+
import jakarta.persistence.GeneratedValue;
10+
import jakarta.persistence.GenerationType;
11+
import jakarta.persistence.Id;
12+
import jakarta.persistence.Index;
13+
import jakarta.persistence.JoinColumn;
14+
import jakarta.persistence.ManyToOne;
15+
import jakarta.persistence.Table;
16+
import jakarta.persistence.Version;
717
import lombok.AccessLevel;
18+
import lombok.AllArgsConstructor;
19+
import lombok.Builder;
820
import lombok.Getter;
921
import lombok.NoArgsConstructor;
1022

1123

1224
@Entity
1325
@Getter
26+
@Builder
27+
@AllArgsConstructor(access = AccessLevel.PRIVATE)
1428
@NoArgsConstructor(access = AccessLevel.PROTECTED)
29+
// TODO: ddl-auto: create,update에만 유효 -> 추후 flyway sql 생성
30+
@Table(name = "answer", indexes = {
31+
@Index(name = "idx_answer_question_member", columnList = "question_id, member_id")
32+
})
1533
public class Answer extends BaseEntity {
1634

1735
@Id
1836
@GeneratedValue(strategy = GenerationType.IDENTITY)
1937
private Long id;
2038

21-
@ManyToOne
22-
@JoinColumn(name = "question_id")
39+
@ManyToOne(fetch = FetchType.LAZY)
40+
@JoinColumn(name = "question_id", nullable = false)
2341
private Question question;
2442

25-
@ManyToOne
26-
@JoinColumn(name = "member_id")
43+
@ManyToOne(fetch = FetchType.LAZY)
44+
@JoinColumn(name = "member_id", nullable = false)
2745
private Member member;
2846

2947
private String content;
@@ -32,4 +50,28 @@ public class Answer extends BaseEntity {
3250
@Version
3351
private Integer version;
3452

53+
public static Answer create(Question question, Member member, String content) {
54+
return Answer.builder()
55+
.question(question)
56+
.member(member)
57+
.content(content)
58+
.emojiCount(0L)
59+
.build();
60+
}
61+
62+
public void updataContent(String content) {
63+
this.content = content;
64+
}
65+
66+
public Long incrementEmojiCount() {
67+
return ++this.emojiCount;
68+
}
69+
70+
public Long decrementEmojiCount() {
71+
if (this.emojiCount > 0) {
72+
this.emojiCount--;
73+
}
74+
return this.emojiCount;
75+
}
76+
3577
}

0 commit comments

Comments
 (0)