Skip to content

Commit 129ffdf

Browse files
author
lilyyang0077
committed
feat: 푸쉬 전 커밋
2 parents e2a08d4 + 6d5cebd commit 129ffdf

9 files changed

Lines changed: 455 additions & 150 deletions

File tree

‎.github/workflows/ci.yml‎

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
name: CI
2+
3+
on:
4+
pull_request:
5+
branches: [ develop ]
6+
7+
jobs:
8+
build:
9+
runs-on: ubuntu-latest
10+
11+
steps:
12+
- name: Checkout
13+
uses: actions/checkout@v3
14+
15+
- name: Set up JDK 21
16+
uses: actions/setup-java@v3
17+
with:
18+
java-version: '21'
19+
distribution: 'corretto'
20+
21+
- name: Grant execute permission for gradlew
22+
run: chmod +x backend/gradlew
23+
24+
- name: Build with Gradle
25+
working-directory: backend
26+
run: ./gradlew bootJar -x test
27+
env:
28+
DB_URL: ${{ secrets.DB_URL }}
29+
DB_USER: ${{ secrets.DB_USER }}
30+
DB_PASSWORD: ${{ secrets.DB_PASSWORD }}
31+
JWT_SECRET: ${{ secrets.JWT_SECRET }}
32+
JWT_EXPIRATION: ${{ secrets.JWT_EXPIRATION }}

‎.github/workflows/deploy.yml‎

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
name: Deploy to EC2
2+
3+
on:
4+
push:
5+
branches: [ develop ]
6+
7+
jobs:
8+
deploy:
9+
runs-on: ubuntu-latest
10+
11+
steps:
12+
- name: Checkout
13+
uses: actions/checkout@v3
14+
15+
- name: Set up JDK 21
16+
uses: actions/setup-java@v3
17+
with:
18+
java-version: '21'
19+
distribution: 'corretto'
20+
21+
- name: Grant execute permission for gradlew
22+
run: chmod +x backend/gradlew
23+
24+
- name: Build with Gradle
25+
working-directory: backend
26+
run: ./gradlew bootJar -x test
27+
env:
28+
DB_URL: ${{ secrets.DB_URL }}
29+
DB_USER: ${{ secrets.DB_USER }}
30+
DB_PASSWORD: ${{ secrets.DB_PASSWORD }}
31+
JWT_SECRET: ${{ secrets.JWT_SECRET }}
32+
JWT_EXPIRATION: ${{ secrets.JWT_EXPIRATION }}
33+
34+
- name: Log in to DockerHub
35+
uses: docker/login-action@v2
36+
with:
37+
username: ${{ secrets.DOCKERHUB_USERNAME }}
38+
password: ${{ secrets.DOCKERHUB_PASSWORD }}
39+
40+
- name: Build and push Docker image
41+
working-directory: backend
42+
run: |
43+
docker build -t ${{ secrets.DOCKERHUB_USERNAME }}/piroin-backend:latest .
44+
docker push ${{ secrets.DOCKERHUB_USERNAME }}/piroin-backend:latest
45+
46+
- name: Deploy to EC2
47+
uses: appleboy/ssh-action@v1.0.0
48+
with:
49+
host: ${{ secrets.EC2_HOST }}
50+
username: ${{ secrets.EC2_USER }}
51+
key: ${{ secrets.EC2_SSH_KEY }}
52+
script: |
53+
docker pull ${{ secrets.DOCKERHUB_USERNAME }}/piroin-backend:latest
54+
docker stop piroin-backend || true
55+
docker rm piroin-backend || true
56+
docker run -d \
57+
--name piroin-backend \
58+
-p 8080:8080 \
59+
-e DB_URL=${{ secrets.DB_URL }} \
60+
-e DB_USER=${{ secrets.DB_USER }} \
61+
-e DB_PASSWORD=${{ secrets.DB_PASSWORD }} \
62+
-e JWT_SECRET=${{ secrets.JWT_SECRET }} \
63+
-e JWT_EXPIRATION=${{ secrets.JWT_EXPIRATION }} \
64+
${{ secrets.DOCKERHUB_USERNAME }}/piroin-backend:latest

‎backend/src/main/java/com/example/Piroin/project/domain/question/controller/QuestionController.java‎

Lines changed: 49 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -12,50 +12,77 @@
1212
import org.springframework.web.bind.annotation.*;
1313

1414
@RestController
15-
@RequestMapping("/api/sessions")
1615
@RequiredArgsConstructor
1716
public class QuestionController {
1817
private final QuestionService questionService;
1918

2019
// 질문 목록 + 이해도 조회
2120
// GET /api/sessions/{sessionId}/questions?understandingIndex=0
22-
@GetMapping("/{sessionId}/questions")
21+
@GetMapping("/api/sessions/{sessionId}/questions")
2322
public ResponseEntity<ApiResponse<QuestionResDTO.QuestionRoomResponse>> getQuestionRoom(
2423
@PathVariable Long sessionId,
2524
@RequestParam(defaultValue = "0") int understandingIndex
2625
) {
27-
QuestionResDTO.QuestionRoomResponse response =
28-
questionService.getQuestionRoom(sessionId, understandingIndex);
29-
30-
return ResponseUtil.success(QuestionSuccessCode.QUESTION_ROOM_OK, response);
26+
return ResponseUtil.success(QuestionSuccessCode.QUESTION_ROOM_OK,
27+
questionService.getQuestionRoom(sessionId, understandingIndex));
3128
}
3229

33-
// 이해도 체크 응답
34-
// POST /api/sessions/{sessionId}/understanding-checks/{checkId}/responses
35-
@PostMapping("/{sessionId}/understanding-checks/{checkId}/responses")
36-
public ResponseEntity<ApiResponse<QuestionResDTO.UnderstandingResponseResult>> respondUnderstandingCheck(
37-
@PathVariable Long sessionId,
38-
@PathVariable Long checkId,
39-
@RequestBody QuestionReqDTO.UnderstandingResponseReq request,
40-
@AuthenticationPrincipal Integer userId
30+
// 질문 상세 조회
31+
// GET /api/questions/{questionId}
32+
@GetMapping("/api/questions/{questionId}")
33+
public ResponseEntity<ApiResponse<QuestionResDTO.QuestionDetailResponse>> getQuestionDetail(
34+
@PathVariable Long questionId,
35+
@AuthenticationPrincipal Long userId
4136
) {
42-
QuestionResDTO.UnderstandingResponseResult response =
43-
questionService.respondUnderstandingCheck(sessionId, checkId, request, userId);
44-
45-
return ResponseUtil.success(QuestionSuccessCode.UNDERSTANDING_RESPONSE_OK, response);
37+
return ResponseUtil.success(QuestionSuccessCode.QUESTION_DETAIL_OK,
38+
questionService.getQuestionDetail(questionId, userId));
4639
}
4740

4841
// 질문 등록
4942
// POST /api/sessions/{sessionId}/questions
50-
@PostMapping("/{sessionId}/questions")
43+
@PostMapping("/api/sessions/{sessionId}/questions")
5144
public ResponseEntity<ApiResponse<QuestionResDTO.CreateRes>> createQuestion(
5245
@PathVariable Long sessionId,
5346
@RequestBody QuestionReqDTO.CreateReq request,
5447
@AuthenticationPrincipal Integer userId
5548
) {
56-
QuestionResDTO.CreateRes response =
57-
questionService.createQuestion(userId.longValue(), request, userId);
49+
return ResponseUtil.success(QuestionSuccessCode.QUESTION_CREATED,
50+
questionService.createQuestion(sessionId, request, Long.valueOf(userId)));
51+
}
5852

59-
return ResponseUtil.success(QuestionSuccessCode.QUESTION_CREATED, response);
53+
// 댓글/대댓글 등록
54+
// POST /api/questions/{questionId}/comments
55+
@PostMapping("/api/questions/{questionId}/comments")
56+
public ResponseEntity<ApiResponse<QuestionResDTO.CommentCreateRes>> createComment(
57+
@PathVariable Long questionId,
58+
@RequestBody QuestionReqDTO.CommentReq request,
59+
@AuthenticationPrincipal Long userId
60+
) {
61+
return ResponseUtil.success(QuestionSuccessCode.COMMENT_CREATED,
62+
questionService.createComment(questionId, request, userId));
63+
}
64+
65+
// 좋아요 토글
66+
// POST /api/questions/{questionId}/like
67+
@PostMapping("/api/questions/{questionId}/like")
68+
public ResponseEntity<ApiResponse<QuestionResDTO.LikeRes>> toggleLike(
69+
@PathVariable Long questionId,
70+
@AuthenticationPrincipal Long userId
71+
) {
72+
return ResponseUtil.success(QuestionSuccessCode.LIKE_TOGGLED,
73+
questionService.toggleLike(questionId, userId));
74+
}
75+
76+
// 이해도 체크 응답
77+
// POST /api/sessions/{sessionId}/understanding-checks/{checkId}/responses
78+
@PostMapping("/api/sessions/{sessionId}/understanding-checks/{checkId}/responses")
79+
public ResponseEntity<ApiResponse<QuestionResDTO.UnderstandingResponseResult>> respondUnderstandingCheck(
80+
@PathVariable Long sessionId,
81+
@PathVariable Long checkId,
82+
@RequestBody QuestionReqDTO.UnderstandingResponseReq request,
83+
@AuthenticationPrincipal Long userId
84+
) {
85+
return ResponseUtil.success(QuestionSuccessCode.UNDERSTANDING_RESPONSE_OK,
86+
questionService.respondUnderstandingCheck(sessionId, checkId, request, userId));
6087
}
6188
}

‎backend/src/main/java/com/example/Piroin/project/domain/question/dto/QuestionReqDTO.java‎

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,24 @@
55
import lombok.NoArgsConstructor;
66

77
public class QuestionReqDTO {
8-
/*
9-
질문 등록 요청 바디
10-
프론트에서 { "content": "질문 내용" } 형태로 전달
11-
*/
8+
9+
// 질문 등록 요청
1210
@Getter
1311
@NoArgsConstructor
1412
public static class CreateReq {
1513
private String content;
1614
}
1715

18-
/*
19-
이해도 체크 응답 요청 바디
20-
프론트에서 { "choice": "UNDERSTOOD" } 또는 { "choice": "NOT_UNDERSTOOD" } 형태로 전달
21-
*/
16+
// 댓글/대댓글 등록 요청
17+
// parentCommentId가 null이면 일반 댓글, 값이 있으면 대댓글
18+
@Getter
19+
@NoArgsConstructor
20+
public static class CommentReq {
21+
private String content;
22+
private Long parentCommentId; // 대댓글일 때만 값이 있음, 일반 댓글이면 null
23+
}
24+
25+
// 이해도 체크 응답 요청
2226
@Getter
2327
@NoArgsConstructor
2428
public static class UnderstandingResponseReq {

‎backend/src/main/java/com/example/Piroin/project/domain/question/dto/QuestionResDTO.java‎

Lines changed: 47 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,7 @@
1010
import java.util.List;
1111

1212
public class QuestionResDTO {
13-
/*
14-
질문 등록 응답
15-
anonymous_no는 댓글 작성 시 부여되므로 여기서는 포함 X
16-
*/
13+
// 질문 등록 응답
1714
@Getter
1815
@Builder
1916
public static class CreateRes {
@@ -23,7 +20,6 @@ public static class CreateRes {
2320
private Integer likeCount;
2421
private LocalDateTime createdAt;
2522

26-
// 엔티티 → DTO 변환 메서드
2723
public static CreateRes from(Question question) {
2824
return CreateRes.builder()
2925
.id(question.getId())
@@ -35,6 +31,52 @@ public static CreateRes from(Question question) {
3531
}
3632
}
3733

34+
// 댓글 등록 응답
35+
public record CommentCreateRes(
36+
Long commentId,
37+
Long questionId,
38+
String displayName,
39+
String content,
40+
LocalDateTime createdAt
41+
) {
42+
}
43+
44+
// 좋아요 토글 응답
45+
// isLiked: true면 좋아요 추가된 상태, false면 취소된 상태
46+
public record LikeRes(
47+
Long id,
48+
Integer likeCount,
49+
Boolean isLiked
50+
) {
51+
}
52+
53+
// 질문 상세 응답
54+
public record QuestionDetailResponse(
55+
Long questionId,
56+
String displayName,
57+
String content,
58+
String imageUrl,
59+
Boolean isResolved,
60+
Boolean isPopular,
61+
Integer likeCount,
62+
Boolean isLiked,
63+
LocalDateTime createdAt,
64+
List<CommentResponse> comments
65+
) {
66+
}
67+
68+
// 댓글 조회 응답 (상세 페이지용)
69+
public record CommentResponse(
70+
Long commentId,
71+
String displayName,
72+
String content,
73+
String imageUrl,
74+
LocalDateTime createdAt,
75+
List<CommentResponse> replies
76+
) {
77+
}
78+
79+
// 질문 방 전체 응답
3880
public record QuestionRoomResponse(
3981
SessionResponse session,
4082
UnderstandingSliceResponse understanding,

‎backend/src/main/java/com/example/Piroin/project/domain/question/entity/Question.java‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,24 @@ public class Question {
4747

4848
@Column(name = "deleted_at")
4949
private LocalDateTime deletedAt;
50+
51+
// 댓글이 새로 달리면 미해결로 되돌리도록
52+
public void markUnresolved() {
53+
this.isResolved = false;
54+
this.updatedAt = LocalDateTime.now();
55+
}
56+
57+
// 좋아요 추가 시 호출
58+
public void increaseLikeCount() {
59+
this.likeCount++;
60+
this.updatedAt = LocalDateTime.now();
61+
}
62+
63+
// 좋아요 취소 시 호출 (0 아래로 내려가지 않도록 방어)
64+
public void decreaseLikeCount() {
65+
if (this.likeCount > 0) {
66+
this.likeCount--;
67+
}
68+
this.updatedAt = LocalDateTime.now();
69+
}
5070
}

‎backend/src/main/java/com/example/Piroin/project/domain/question/exception/code/QuestionSuccessCode.java‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,11 @@
1010
public enum QuestionSuccessCode implements BaseCode {
1111

1212
QUESTION_ROOM_OK(HttpStatus.OK, "QUESTION200_1", "질문 방 조회에 성공했습니다."),
13+
QUESTION_DETAIL_OK(HttpStatus.OK, "QUESTION200_2", "질문 상세 조회에 성공했습니다."),
14+
LIKE_TOGGLED(HttpStatus.OK, "QUESTION200_3", "좋아요가 처리되었습니다."), // ← 추가
15+
UNDERSTANDING_RESPONSE_OK(HttpStatus.OK, "QUESTION200_4", "이해도 응답이 반영되었습니다."),
1316
QUESTION_CREATED(HttpStatus.CREATED, "QUESTION201_1", "질문이 등록되었습니다."),
14-
UNDERSTANDING_RESPONSE_OK(HttpStatus.OK, "QUESTION200_2", "이해도 응답이 반영되었습니다.");
17+
COMMENT_CREATED(HttpStatus.CREATED, "QUESTION201_2", "댓글이 등록되었습니다.");
1518

1619
private final HttpStatus status;
1720
private final String code;
Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,21 @@
11
package com.example.Piroin.project.domain.question.repository;
22

3-
import com.example.Piroin.project.domain.question.entity.Question; // ← StudySession 대신 Question으로 변경
3+
import com.example.Piroin.project.domain.question.entity.Question;
44
import com.example.Piroin.project.domain.question.entity.QuestionAnonymousIdentity;
55
import com.example.Piroin.project.domain.user.entity.User;
6+
import com.example.Piroin.project.domain.user.enums.Role;
67
import org.springframework.data.jpa.repository.JpaRepository;
78

89
import java.util.Optional;
910

1011
public interface QuestionAnonymousIdentityRepository extends JpaRepository<QuestionAnonymousIdentity, Long> {
11-
/*
12-
해당 질문에서 유저의 익명 번호 조회
13-
용도: 댓글 작성 시 이미 번호가 있는지 확인
14-
*/
12+
13+
// 해당 질문에서 유저의 익명 번호 조회
14+
// 용도: 댓글 작성 시 이미 번호가 있는지 확인
1515
Optional<QuestionAnonymousIdentity> findByQuestionAndUser(Question question, User user);
1616

17-
/*
18-
해당 질문에 현재까지 부여된 익명 번호 수 조회
19-
용도: 새 익명 번호 발급 시(현재 수 + 1)
20-
*/
21-
int countByQuestion(Question question);
17+
// 해당 질문에서 특정 역할(MEMBER/ADMIN)의 익명 번호 수 조회
18+
// 용도: 새 익명 번호 발급 시 역할별로 따로 카운트
19+
// MEMBER → 익명1, 익명2... / ADMIN → 운영진1, 운영진2...
20+
int countByQuestionAndUser_Role(Question question, Role role);
2221
}

0 commit comments

Comments
 (0)