Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
81 commits
Select commit Hold shift + click to select a range
a25dc26
feat: 질문 수정, 삭제 api (#51)
chcch529 Jul 15, 2025
9e16cd1
feat: 결과 리포트 조회 기능 구현 (#52)
chw0912 Jul 15, 2025
ceb2f9c
refactor: Top3QnA 답변 리스트로 조회 리팩토링 (#55)
chw0912 Jul 16, 2025
db10dba
feat: 공감 API Rate Limiting 적용 / 공감 API 분리 (#56)
min0962 Jul 16, 2025
2e24d58
feat: 발표방 참여 및 입장 인원 계산 로직 (#57)
rjswjddn Jul 16, 2025
aa6d72e
refactor: 질문 정렬에 queryDsl 적용 (#59)
chcch529 Jul 16, 2025
01394fa
feat : 답변 수정 삭제 api (#61)
gffd94 Jul 17, 2025
de2ceec
feat: 질문 생성 rate limiting (#65)
chcch529 Jul 17, 2025
6018ddf
feat: 참가자 퇴장 시간 저장 및 웹소켓, 발표방 리팩토링 (#64)
rjswjddn Jul 17, 2025
9779537
feat: CD 파이프라인 구축 (#71)
min0962 Jul 17, 2025
7e0a491
refactor: 파일 업로드 로직 수정 (#73)
chw0912 Jul 18, 2025
6bf41e9
답변 생성 수정 및 테스트 코드 작성 (#74)
gffd94 Jul 18, 2025
bb32066
chore: resources 폴더 생성
chw0912 Jul 18, 2025
aea5f02
feat: workflow에 EC2 자동 배포 추가 (#78)
chw0912 Jul 18, 2025
a7fea27
feat: workflow에 EC2 자동 배포 추가 (#78) (#79)
chw0912 Jul 18, 2025
4d341f8
feat: 발표방 캐싱 및 인덱싱 적용 feat 새로운 기능 추가 (#80)
rjswjddn Jul 19, 2025
ec73134
release: 병합 (#81)
rjswjddn Jul 19, 2025
657f978
feat: cors 설정 추가
rjswjddn Jul 21, 2025
7e498b8
fix: import 추가
rjswjddn Jul 21, 2025
b0469da
chore: 워크 플로우 수동 실행 추가
rjswjddn Jul 21, 2025
bdcf47a
feat: 카카오 로그인 API (#86)
rjswjddn Jul 23, 2025
2622931
refactor: Presigned URL 업로드용, 조회용 로직 분리 (#84)
chw0912 Jul 23, 2025
1980af7
merge: release 브랜치 충돌 해결
rjswjddn Jul 23, 2025
f777be0
release: 개발 완료 코드 release 브랜치 병합 (#87)
chw0912 Jul 23, 2025
12c801a
refactor: profiles 환경 변수 주입으로 변경 (#90)
chw0912 Jul 24, 2025
65e05fd
feat: 카카오 로그인 재시도 로직, 방 참가자 관리 동시성 수정 (#91)
rjswjddn Jul 24, 2025
dfb5aec
release: 개발 완료 코드 release 브랜치 병합 (#92)
chw0912 Jul 24, 2025
a0ef2c6
refactor,fix: 2회차 멘토링 기반 리팩토링 (#94)
chcch529 Jul 26, 2025
a34d680
fix: MemberController 수정 (#96)
rjswjddn Jul 27, 2025
0dc5c81
Merge branch 'release' into dev
rjswjddn Jul 27, 2025
0825423
release: release 브랜치 병합 (#99)
rjswjddn Jul 27, 2025
492992a
답변 조회 기능 수정 (#98)
gffd94 Jul 28, 2025
b203f2d
release: 개발 완료 코드 release 브랜치 병합 (#100)
chw0912 Jul 29, 2025
b0bb3b1
feat: cors 설정 (#102)
rjswjddn Jul 29, 2025
e888ed5
release: 코드 병합 (#103)
rjswjddn Jul 29, 2025
12ad4ba
Refactor/101 room detail (#104)
rjswjddn Jul 30, 2025
3adeb88
코드 통합 (#105)
rjswjddn Jul 30, 2025
e9fdf79
Refactor/101 room detail (#106)
rjswjddn Jul 30, 2025
03c6b84
코드 병합 (#107)
rjswjddn Jul 30, 2025
bf2a942
Refactor/101 room detail (#108)
rjswjddn Jul 30, 2025
9181de9
Merge branch 'release' into dev
rjswjddn Jul 30, 2025
fbac2dd
코드 병합 (#109)
rjswjddn Jul 30, 2025
790be9a
Refactor/101 room detail (#110)
rjswjddn Jul 30, 2025
d9d34df
코드병합 (#111)
rjswjddn Jul 30, 2025
439c39f
코드 병합1 (#112)
rjswjddn Jul 30, 2025
8145fa0
Update WebSocketConfig.java
rjswjddn Jul 30, 2025
d7263ef
코드 병합 (#113)
rjswjddn Jul 30, 2025
129d08f
Refactor/101 room detail (#114)
rjswjddn Jul 30, 2025
5623727
코드병합 (#115)
rjswjddn Jul 30, 2025
ab1ebb5
Refactor/101 room detail (#116)
rjswjddn Jul 30, 2025
bb7ea67
코드 병합 (#117)
rjswjddn Jul 30, 2025
a4c92d4
Refactor/101 room detail (#118)
rjswjddn Jul 30, 2025
1c6bcae
코드 병합 (#119)
rjswjddn Jul 30, 2025
4ab7101
Refactor/101 room detail (#120)
rjswjddn Jul 30, 2025
e517232
Merge branch 'release' into dev
rjswjddn Jul 30, 2025
8c54309
코드 병합 (#121)
rjswjddn Jul 30, 2025
f5e084b
test: 질문 생성 테스트 배포 (#122)
rjswjddn Jul 30, 2025
1fcebe4
질문 생성 테스트 병합 (#123)
rjswjddn Jul 30, 2025
e3d9687
fix: stomp controller 수정 (#125)
rjswjddn Jul 30, 2025
362c116
release: stomp 컨트롤러 수정 코드 병합 (#126)
rjswjddn Jul 30, 2025
87963ab
fix: 답변 기능 수정 (#128)
rjswjddn Jul 30, 2025
0aa089c
release: 답변 기능 수정 코드 병합 (#129)
rjswjddn Jul 30, 2025
09354a6
fix: cors 메서드 수정 (#131)
rjswjddn Jul 31, 2025
6835de7
Merge branch 'release' into dev
rjswjddn Jul 31, 2025
e673559
release: 코드 병합 (#132)
rjswjddn Jul 31, 2025
fb221eb
fix: 시큐리티 수정 (#133)
rjswjddn Jul 31, 2025
349499d
release: 시큐리티 수정 코드 병합 (#134)
rjswjddn Jul 31, 2025
c13703f
fix: 시큐리티 permitAll 추가 (#135)
rjswjddn Jul 31, 2025
2616ce8
release: 코드 병합 (#136)
rjswjddn Jul 31, 2025
f815228
fix: 답변율 퍼센트 수정 (#138)
min0962 Jul 31, 2025
ac354df
release: 코드 병합 (#139)
min0962 Jul 31, 2025
5e5d115
fix: 참여한 발표방 조회 시 생성일, 참여일이 나오게 변경 (#141)
min0962 Jul 31, 2025
4a6e59d
release: 코드 병합 (#142)
min0962 Jul 31, 2025
3555987
fix: 캐시 삭제 로직 수정 (#143)
rjswjddn Aug 3, 2025
4804ec6
Merge branch 'release' into dev
rjswjddn Aug 3, 2025
7c69474
release: 캐시 수정 코드 병합 (#144)
rjswjddn Aug 3, 2025
1fd33f7
refactor: devcontroller 수정 (#145)
rjswjddn Aug 4, 2025
21d5c38
Merge branch 'release' into dev
rjswjddn Aug 4, 2025
8b5c5e8
test: 캐시 테스트 코드 수정 (#148)
rjswjddn Aug 4, 2025
1cb0fde
feat: 로그인 jwt 적용 (#150)
min0962 Aug 26, 2025
0c5fcb3
Feat/151 refreshtoken blacklist (#152)
gffd94 Nov 17, 2025
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: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ jobs:
- name: application-ci.yml 주입
run: |
mkdir -p src/main/resources
echo "${{ secrets.APPLICATION_YML_CI }}" | base64 --decode > src/main/resources/application-ci.yml
echo "${{ secrets.APPLICATION_YML_CI }}" > src/main/resources/application-ci.yml

- name: 권한 세팅
run: chmod +x ./gradlew
Expand Down
101 changes: 101 additions & 0 deletions .github/workflows/release-workflow.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
name: OromaminC Backend Service Release

on:
push:
branches:
- release
workflow_dispatch:

env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}

jobs:

tagging:
name: 태깅 및 릴리즈
runs-on: ubuntu-latest
outputs:
tag_name: ${{ steps.tag_version.outputs.new_tag }}

steps:
- uses: actions/checkout@v4

- name: versioning and tagging
id: tag_version
uses: mathieudutour/[email protected]
with:
github_token: ${{ secrets.GITHUB_TOKEN }}

- name: releasing
uses: ncipollo/release-action@v1
with:
tag: ${{ steps.tag_version.outputs.new_tag }}
name: ${{ steps.tag_version.outputs.new_tag }}
body: ${{ steps.tag_version.outputs.changelog }}

build-image:
name: 도커 이미지 빌드
runs-on: ubuntu-latest
needs: tagging

permissions:
contents: read
packages: write
attestations: write
id-token: write

steps:
- name: Check out Repository
uses: actions/checkout@v4

- name: Setting for Developent
run: |
mkdir -p src/main/resources
echo "${{ secrets.APPLICATION_DEV_YML }}" > src/main/resources/application-dev.yml

- name: Sign in github container registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=sha
type=raw,value=${{ needs.tagging.outputs.tag_name }}
type=raw,value=latest

- name: Build and Push Image
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
build-args: |
SPRING_PROFILES_ACTIVE=dev

deploy:
name: EC2 자동 배포
runs-on: ubuntu-latest
needs: build-image

steps:
- name: EC2에 SSH로 접속 후 배포
uses: appleboy/[email protected]
with:
host: ${{ secrets.EC2_HOST }}
username: ${{ secrets.EC2_USERNAME }}
key: ${{ secrets.EC2_SSH_KEY }}
port: ${{ secrets.EC2_PORT }}
script: |
cd ${{ secrets.EC2_DEPLOY_DIR }}
docker compose pull
docker compose down
docker compose up -d
27 changes: 27 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
FROM gradle:jdk21 as builder

WORKDIR /libs

COPY gradlew .
COPY gradle gradle
COPY build.gradle .
COPY settings.gradle .

RUN ./gradlew dependencies --no-daemon || true

COPY src src

RUN ./gradlew build --no-daemon -x test


FROM openjdk:21-slim

WORKDIR /app

COPY --from=builder /libs/build/libs/*.jar app.jar

ARG SPRING_PROFILES_ACTIVE
ENV SPRING_PROFILES_ACTIVE=${SPRING_PROFILES_ACTIVE}

ENTRYPOINT ["java", "-Dspring.profiles.active=${SPRING_PROFILES_ACTIVE}", "-jar", "app.jar"]

24 changes: 23 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ plugins {
}

group = 'com.oronaminc'
version = '0.0.1-SNAPSHOT'
version = '0.1'

java {
toolchain {
Expand All @@ -30,6 +30,7 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-cache'
testImplementation 'org.springframework.security:spring-security-test'

compileOnly 'org.projectlombok:lombok'
Expand All @@ -49,6 +50,27 @@ dependencies {

// S3
implementation 'software.amazon.awssdk:s3:2.31.77'

// querydsl
implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta'
annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jakarta"
annotationProcessor("jakarta.persistence:jakarta.persistence-api")
annotationProcessor("jakarta.annotation:jakarta.annotation-api")

// bucket4j
implementation 'com.bucket4j:bucket4j_jdk17-core:8.14.0'

// commons-lang3
implementation 'org.apache.commons:commons-lang3:3.18.0'
// caffeine
implementation 'com.github.ben-manes.caffeine:caffeine'

implementation 'org.springframework.retry:spring-retry:2.0.12'

//jwt
implementation 'io.jsonwebtoken:jjwt-api:0.12.6'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.6'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.6'
}

tasks.named('test') {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
package com.oronaminc.join;

import com.oronaminc.join.member.token.JwtConfiguration;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;

@EnableJpaAuditing
@SpringBootApplication
@EnableConfigurationProperties(JwtConfiguration.class)
public class Web57OronaminCBeApplication {

public static void main(String[] args) {
Expand Down
29 changes: 25 additions & 4 deletions src/main/java/com/oronaminc/join/answer/api/AnswerController.java
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
package com.oronaminc.join.answer.api;

import com.oronaminc.join.answer.dto.AnswerGetResponse;
import com.oronaminc.join.answer.dto.AnswerListResponse;
import com.oronaminc.join.answer.mapper.AnswerMapper;
import com.oronaminc.join.answer.service.AnswerService;
import com.oronaminc.join.member.security.MemberDetails;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import java.time.LocalDateTime;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Slice;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

Expand All @@ -20,16 +27,30 @@ public class AnswerController {

private final AnswerService answerService;

@Operation(
summary = "답변 조회",
description = "답변 보기 클릭 시 질문에 대한 답변을 조회",
responses = {
@ApiResponse(responseCode = "200", description = "답변 조회 성공"),
@ApiResponse(responseCode = "400", description = "답변이 없는데 답변보기 버튼이 활성화 되어 잘못된 조회 접근")

}
)
@GetMapping("/rooms/{roomId}/questions/{questionId}/answers")
@ResponseStatus(HttpStatus.OK)
public ResponseEntity<AnswerGetResponse> getAnswer(
public ResponseEntity<AnswerListResponse> getAnswers(
@PathVariable Long roomId,
@PathVariable Long questionId,
@AuthenticationPrincipal MemberDetails memberDetails
@AuthenticationPrincipal MemberDetails memberDetails,
@RequestParam(required = false) Long lastId,
@RequestParam(required = false) LocalDateTime lastCreatedAt,
@RequestParam(defaultValue = "10") int size
) {
Long memberId = memberDetails.getId();
AnswerGetResponse response = answerService.getAnswer(roomId, questionId, memberId);
return ResponseEntity.ok(response);

Slice<AnswerGetResponse> response = answerService.getAnswers(roomId, questionId, memberId,
lastId, lastCreatedAt, size);
return ResponseEntity.ok(AnswerMapper.toAnswerListResponse(response));
}

}
56 changes: 51 additions & 5 deletions src/main/java/com/oronaminc/join/answer/dao/AnswerRepository.java
Original file line number Diff line number Diff line change
@@ -1,17 +1,63 @@
package com.oronaminc.join.answer.dao;

import java.util.List;

import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import com.oronaminc.join.answer.domain.Answer;
import com.oronaminc.join.question.domain.Question;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;

public interface AnswerRepository extends JpaRepository<Answer, Long> {

Optional<Answer> findByQuestionId(Long questionId);

boolean existsByQuestionIdAndMemberId(Long questionId, Long memberId);
@Query("""
SELECT a
FROM Answer a
JOIN FETCH a.member m
WHERE a.question.id = :questionId
ORDER BY a.createdAt ASC, a.id ASC
""")
List<Answer> findFirstPageByQuestionId(
@Param("questionId") Long questionId,
Pageable pageable
);

@Query("""
SELECT a
FROM Answer a
JOIN FETCH a.member m
WHERE a.question.id = :questionId
AND (a.createdAt > :lastCreatedAt OR (a.createdAt = :lastCreatedAt AND a.id > :lastId))
ORDER BY a.createdAt ASC, a.id ASC
""")
List<Answer> findByQuestionIdWithCursor(
@Param("questionId") Long questionId,
@Param("lastCreatedAt") LocalDateTime lastCreatedAt,
@Param("lastId") Long lastId,
Pageable pageable
);

void deleteByQuestionId(Long questionId);

void deleteByQuestionIn(List<Question> questions);

@Query("""
select count(distinct a.question.id)
from Answer a
where a.question.room.id = :roomId
""")
Long countAnsweredQuestionsByRoomId(@Param("roomId") Long roomId);

@Query("""
select a
from Answer a
where a.question.id in :questionIds
""")
List<Answer> findAllByQuestionIds(@Param("questionIds") List<Long> questionIds);

}
6 changes: 4 additions & 2 deletions src/main/java/com/oronaminc/join/answer/domain/Answer.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.oronaminc.join.answer.domain;

import com.oronaminc.join.answer.dto.AnswerCreateRequest;
import com.oronaminc.join.global.entity.BaseEntity;
import com.oronaminc.join.member.domain.Member;
import com.oronaminc.join.question.domain.Question;
Expand All @@ -27,7 +26,6 @@
@Builder
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@NoArgsConstructor(access = AccessLevel.PROTECTED)
// TODO: ddl-auto: create,update에만 유효 -> 추후 flyway sql 생성
@Table(name = "answer", indexes = {
@Index(name = "idx_answer_question_member", columnList = "question_id, member_id")
})
Expand Down Expand Up @@ -60,6 +58,10 @@ public static Answer create(Question question, Member member, String content) {
.build();
}

public void updataContent(String content) {
this.content = content;
}

public Long incrementEmojiCount() {
return ++this.emojiCount;
}
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
package com.oronaminc.join.answer.dto;

import com.oronaminc.join.global.dto.WriterDto;
import com.oronaminc.join.websocket.common.EventType;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import java.time.LocalDateTime;
import lombok.Builder;

@Builder
@Schema(description = "WebSocket STOMP 통신 답변 응답 DTO")
public record AnswerCreateResponse(
//TODO: QuestionCreateResponse와 유사-> 둘중 하나만?
@Schema(description = "답변이 생성될 질문 ID")
Long questionId,
@Schema(description = "답변 생성/삭제/수정 상태", example = "CREATE")
String event,
EventType event,
@Schema(description = "답변 ID", example = "11")
Long answerId,
@Schema(description = "답변 내용", example = "답변입니다.")
Expand Down
Loading