Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ dependencies {
annotationProcessor 'jakarta.persistence:jakarta.persistence-api:3.1.0'
annotationProcessor 'jakarta.annotation:jakarta.annotation-api:2.1.1'

// kafak
implementation 'org.springframework.kafka:spring-kafka'

}

tasks.named('test') {
Expand Down
35 changes: 35 additions & 0 deletions docker-compose.local.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# 개발환경 도커 컴포즈

# 로컬 개발 환경을 위한 서비스 정의
services:
# 로컬 Redis
redis:
image: redis:latest
container_name: bubblog-local-redis # 운영 환경과의 충돌 방지를 위해 이름 명시
ports:
- "6379:6379"
volumes:
- redis_data_local:/data

# 로컬 Kafka
kafka:
image: 'bitnami/kafka:latest'
container_name: bubblog-local-kafka
ports:
- "9092:9092"
environment:
- KAFKA_CFG_NODE_ID=1 # Kafka 클러스터 내에서 각 노드를 식별하는 고유 ID
- KAFKA_CFG_PROCESS_ROLES=broker,controller # 이 Kafka 노드가 수행할 역할을 지정. 브로커(메세지 관리) 겸 컨트롤러(클러스터 관리)
- KAFKA_CFG_CONTROLLER_LISTENER_NAMES=CONTROLLER # 컨트롤러(관리자)용 통신에 사용되는 리스너 채널 이름 지정
- KAFKA_CFG_LISTENERS=PLAINTEXT://:9092,CONTROLLER://:9093
- KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP=CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT
- KAFKA_CFG_ADVERTISED_LISTENERS=PLAINTEXT://localhost:9092
- KAFKA_CFG_INTER_BROKER_LISTENER_NAME=PLAINTEXT # 여러 broker 노드들끼리 서로 통신할 때 사용할 통신 채널의 이름
- KAFKA_CFG_CONTROLLER_QUORUM_VOTERS=1@kafka:9093 # 1@kafka:9093 : 'ID가 1이고 주소가 kafka:9093인 노드가 투표권을 가진다.'
- KAFKA_CFG_AUTO_CREATE_TOPICS_ENABLE=true # 존재하지 않는 토픽(Topic)으로 메시지를 보냈을 때 토픽을 자동으로 생성할지 여부
volumes:
- kafka_data_local:/bitnami/kafka
# 데이터 유지를 위한 로컬 볼륨
volumes:
redis_data_local:
kafka_data_local:
25 changes: 24 additions & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,33 @@ services:
restart: always
depends_on:
- redis
- kafka
ports:
- "8080:8080"
env_file:
- .env

# 직접 Kafka 설치하지 않고 Docker를 이용해 컨테이너 환경으로 실행
kafka:
image: 'bitnami/kafka:latest'
container_name: kafka
restart: always
ports:
- "9092:9092"
environment:
- KAFKA_CFG_NODE_ID=1
- KAFKA_CFG_PROCESS_ROLES=broker,controller
- KAFKA_CFG_CONTROLLER_LISTENER_NAMES=CONTROLLER
- KAFKA_CFG_LISTENERS=PLAINTEXT://:9092,CONTROLLER://:9093
- KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP=CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT
# Docker 내부 통신용 리스너와 외부 접속용 리스너를 함께 설정
- KAFKA_CFG_ADVERTISED_LISTENERS=PLAINTEXT://kafka:9092,PLAINTEXT_EXTERNAL://${KAFKA_HOST_IP}:9092
- KAFKA_CFG_INTER_BROKER_LISTENER_NAME=PLAINTEXT
- KAFKA_CFG_CONTROLLER_QUORUM_VOTERS=1@kafka:9093
- KAFKA_CFG_AUTO_CREATE_TOPICS_ENABLE=true
volumes:
- kafka_data:/bitnami/kafka

volumes:
redis_data:
redis_data:
kafka_data:
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
import Bubble.bubblog.domain.category.service.CategoryService;
import Bubble.bubblog.global.dto.ErrorResponse;
import Bubble.bubblog.global.dto.SuccessResponse;
import Bubble.bubblog.global.dto.swaggerResponse.category.CategoryListSuccessResponse;
import Bubble.bubblog.global.dto.swaggerResponse.category.CategorySuccessResponse;
import Bubble.bubblog.global.dto.swaggerResponse.category.CategoryTreeSuccessResponse;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
Expand Down Expand Up @@ -35,7 +38,7 @@ public class CategoryController {
@Operation(summary = "카테고리 생성", description = "새 카테고리를 생성합니다. (부모 ID는 선택)")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "생성 성공",
content = @Content(schema = @Schema(implementation = SuccessResponse.class))),
content = @Content(schema = @Schema(implementation = CategorySuccessResponse.class))),
@ApiResponse(responseCode = "404", description = "대상을 찾을 수 없습니다.",
content = @Content(schema = @Schema(implementation = ErrorResponse.class)))
})
Expand Down Expand Up @@ -86,7 +89,7 @@ public SuccessResponse<Void> deleteCategory(
@Operation(summary = "모든 카테고리 조회", description = "유저의 모든 카테고리를 리스트로 조회합니다.")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "조회 성공",
content = @Content(schema = @Schema(implementation = SuccessResponse.class)))
content = @Content(schema = @Schema(implementation = CategoryListSuccessResponse.class)))
})
@GetMapping
public SuccessResponse<List<CategoryDTO>> getAllCategories(
Expand All @@ -98,7 +101,7 @@ public SuccessResponse<List<CategoryDTO>> getAllCategories(
@Operation(summary = "카테고리 트리 조회", description = "유저의 모든 카테고리를 트리 구조로 조회합니다.")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "조회 성공",
content = @Content(schema = @Schema(implementation = SuccessResponse.class)))
content = @Content(schema = @Schema(implementation = CategoryTreeSuccessResponse.class)))
})
@GetMapping("/{userId}/tree")
public SuccessResponse<List<CategoryTreeDTO>> getAllCategoriesAsTree(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import Bubble.bubblog.domain.chatbot.service.PersonaService;
import Bubble.bubblog.global.dto.ErrorResponse;
import Bubble.bubblog.global.dto.SuccessResponse;
import Bubble.bubblog.global.dto.swaggerResponse.persona.PersonaListSuccessResponse;
import Bubble.bubblog.global.dto.swaggerResponse.persona.PersonaSuccessResponse;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Content;
Expand Down Expand Up @@ -32,7 +34,7 @@ public class PersonaController {
@Operation(summary = "말투 생성", description = "사용자가 챗봇의 말투를 생성합니다.", security = @SecurityRequirement(name = "JWT"))
@ApiResponses({
@ApiResponse(responseCode = "200", description = "말투 생성 성공",
content = @Content(schema = @Schema(implementation = SuccessResponse.class))),
content = @Content(schema = @Schema(implementation = PersonaSuccessResponse.class))),
@ApiResponse(responseCode = "400", description = "입력값이 유효하지 않음",
content = @Content(schema = @Schema(implementation = ErrorResponse.class))),
})
Expand All @@ -48,7 +50,7 @@ public SuccessResponse<PersonaResponseDTO> createPersona(
@Operation(summary = "특정 말투 조회", description = "특정 말투를 조회합니다.")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "말투 조회 성공",
content = @Content(schema = @Schema(implementation = SuccessResponse.class))),
content = @Content(schema = @Schema(implementation = PersonaSuccessResponse.class))),
@ApiResponse(responseCode = "404", description = "존재하지 않는 말투",
content = @Content(schema = @Schema(implementation = ErrorResponse.class)))
})
Expand All @@ -62,7 +64,7 @@ public SuccessResponse<PersonaResponseDTO> getPersona(@PathVariable Long persona
@Operation(summary = "말투 전체 조회", description = "모든 말투들을 조회합니다.")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "말투 목록 조회 성공",
content = @Content(schema = @Schema(implementation = SuccessResponse.class)))
content = @Content(schema = @Schema(implementation = PersonaListSuccessResponse.class)))
})
@GetMapping
public SuccessResponse<List<PersonaResponseDTO>> getAllPersonas() {
Expand All @@ -74,7 +76,7 @@ public SuccessResponse<List<PersonaResponseDTO>> getAllPersonas() {
@Operation(summary = "특정 사용자의 말투 목록 조회", description = "사용자 ID를 통해 해당 사용자의 말투 목록을 조회합니다.")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "말투 목록 조회 성공",
content = @Content(schema = @Schema(implementation = SuccessResponse.class))),
content = @Content(schema = @Schema(implementation = PersonaListSuccessResponse.class))),
@ApiResponse(responseCode = "404", description = "사용자를 찾을 수 없음",
content = @Content(schema = @Schema(implementation = ErrorResponse.class)))
})
Expand All @@ -88,7 +90,7 @@ public SuccessResponse<List<PersonaResponseDTO>> getPersonasByUserId(@PathVariab
@Operation(summary = "말투 수정", description = "로그인한 사용자가 자신의 말투를 수정합니다.", security = @SecurityRequirement(name = "JWT"))
@ApiResponses({
@ApiResponse(responseCode = "200", description = "말투 수정 성공",
content = @Content(schema = @Schema(implementation = SuccessResponse.class))),
content = @Content(schema = @Schema(implementation = PersonaSuccessResponse.class))),
@ApiResponse(responseCode = "400", description = "입력값이 유효하지 않음",
content = @Content(schema = @Schema(implementation = ErrorResponse.class))),
@ApiResponse(responseCode = "403", description = "접근 권한 없음",
Expand Down Expand Up @@ -116,7 +118,7 @@ public SuccessResponse<PersonaResponseDTO> updatePersona(
content = @Content(schema = @Schema(implementation = ErrorResponse.class)))
})
@DeleteMapping("/{personaId}")
public SuccessResponse<String> deletePersona(
public SuccessResponse<Void> deletePersona(
@PathVariable Long personaId,
@Parameter(hidden = true) @AuthenticationPrincipal UUID userId
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
import Bubble.bubblog.domain.comment.service.commentservice.CommentService;
import Bubble.bubblog.global.dto.ErrorResponse;
import Bubble.bubblog.global.dto.SuccessResponse;
import Bubble.bubblog.global.dto.swaggerResponse.comment.CommentPageSuccessResponse;
import Bubble.bubblog.global.dto.swaggerResponse.comment.CommentSuccessResponse;
import Bubble.bubblog.global.dto.swaggerResponse.comment.CommentThreadSuccessResponse;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Content;
Expand Down Expand Up @@ -36,7 +39,7 @@ public class CommentController {
/** 특정 댓글 단건 조회 */
@Operation(summary = "댓글 단건 상세 조회", description = "특정 commentId의 단건 상세를 조회합니다")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "조회 성공", content = @Content(schema = @Schema(implementation = SuccessResponse.class))),
@ApiResponse(responseCode = "200", description = "조회 성공", content = @Content(schema = @Schema(implementation = CommentSuccessResponse.class))),
@ApiResponse(responseCode = "404", description = "댓글을 찾을 수 없음", content = @Content(schema = @Schema(implementation = ErrorResponse.class)))
})
@GetMapping("/{commentId}")
Expand All @@ -48,7 +51,7 @@ public SuccessResponse<CommentResponseDTO> getCommentDetail(@PathVariable Long c
/** 특정 루트 댓글의 자식 댓글 목록을 페이징으로 조회 */
@Operation(summary = "루트 댓글의 자식들 페이징 조회", description = "특정 루트 댓글의 자식들을 페이징 처리하여 반환합니다.")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "조회 성공", content = @Content(schema = @Schema(implementation = SuccessResponse.class))),
@ApiResponse(responseCode = "200", description = "조회 성공", content = @Content(schema = @Schema(implementation = CommentPageSuccessResponse.class))),
@ApiResponse(responseCode = "404", description = "해당 루트 댓글을 찾을 수 없음", content = @Content(schema = @Schema(implementation = ErrorResponse.class)))
})
@GetMapping("/{commentId}/children")
Expand All @@ -61,7 +64,7 @@ public SuccessResponse<Page<CommentResponseDTO>> getChildrenByRoot(@PathVariable
/** 특정 루트 댓글과 그 모든 자식 댓글 함께 조회 (스레드 조회) */
@Operation(summary = "루트 댓글과 모든 자식들을 함께 스레드로 조회", description = "루트 댓글 하나와 그 자식(대댓글)들을 함께 반환합니다.")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "조회 성공", content = @Content(schema = @Schema(implementation = SuccessResponse.class))),
@ApiResponse(responseCode = "200", description = "조회 성공", content = @Content(schema = @Schema(implementation = CommentThreadSuccessResponse.class))),
@ApiResponse(responseCode = "404", description = "해당 루트 댓글을 찾을 수 없음", content = @Content(schema = @Schema(implementation = ErrorResponse.class)))
})
@GetMapping("/{commentId}/thread")
Expand All @@ -74,7 +77,7 @@ public SuccessResponse<CommentThreadResponseDTO> getThreadByRoot(@PathVariable L
/** 댓글 수정 */
@Operation(summary = "댓글 수정", description = "본인이 작성한 댓글만 수정할 수 있습니다.", security = @SecurityRequirement(name = "JWT"))
@ApiResponses({
@ApiResponse(responseCode = "200", description = "수정 성공", content = @Content(schema = @Schema(implementation = SuccessResponse.class))),
@ApiResponse(responseCode = "200", description = "수정 성공", content = @Content(schema = @Schema(implementation = CommentSuccessResponse.class))),
@ApiResponse(responseCode = "400", description = "입력값이 유효하지 않음", content = @Content(schema = @Schema(implementation = ErrorResponse.class))),
@ApiResponse(responseCode = "403", description = "수정 권한 없음", content = @Content(schema = @Schema(implementation = ErrorResponse.class))),
@ApiResponse(responseCode = "404", description = "댓글을 찾을 수 없음", content = @Content(schema = @Schema(implementation = ErrorResponse.class)))
Expand Down
Loading