Skip to content
Merged
10 changes: 9 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,6 @@ dependencies {

// S3
implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE'
implementation 'software.amazon.awssdk:s3:2.17.72'

// redis
implementation 'org.redisson:redisson-spring-boot-starter:3.36.0'
Expand All @@ -70,6 +69,15 @@ dependencies {

// oauth
implementation 'org.springframework.security:spring-security-oauth2-client:6.3.1'

// QueryDSL
implementation 'com.querydsl:querydsl-jpa:5.1.0:jakarta'
annotationProcessor 'com.querydsl:querydsl-apt:5.1.0:jakarta'
annotationProcessor 'jakarta.persistence:jakarta.persistence-api:3.1.0'
annotationProcessor 'jakarta.annotation:jakarta.annotation-api:3.0.0'

// DataFaker
implementation 'net.datafaker:datafaker:2.4.4'
}

tasks.named('test') {
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@

import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import onepiece.dailysnapbackend.object.constants.KeywordCategory;
import onepiece.dailysnapbackend.object.dto.CustomOAuth2User;
import onepiece.dailysnapbackend.object.dto.KeywordRequest;
import onepiece.dailysnapbackend.object.dto.KeywordResponse;
import onepiece.dailysnapbackend.service.keyword.AdminKeywordService;
import onepiece.dailysnapbackend.service.keyword.OpenAIKeywordService;
import onepiece.dailysnapbackend.util.log.LogMonitoringInvocation;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
Expand All @@ -17,39 +20,49 @@
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("/admin/keyword")
@Tag(name = "관리자 키워드 API", description = "관리자가 키워드를 관리하는 API")
public class AdminController implements AdminControllerDocs {
@Tag(
name = "관리자 키워드 API",
description = "관리자가 키워드를 관리하는 API"
)
public class AdminKeywordController implements AdminKeywordControllerDocs {

private final AdminKeywordService adminKeywordService;
private final OpenAIKeywordService openAIKeywordService;

/**
* 특정 날짜에 제공할 키워드 추가 (관리자 지정)
*/
@Override
@PostMapping
@LogMonitoringInvocation
public ResponseEntity<Void> addKeyword(
@AuthenticationPrincipal CustomOAuth2User userDetails,
public ResponseEntity<KeywordResponse> addKeyword(
@AuthenticationPrincipal CustomOAuth2User customOAuth2User,
@Valid @RequestBody KeywordRequest request) {
adminKeywordService.addKeyword(request);
log.info("[AdminController] 키워드 추가 완료");
return ResponseEntity.ok().build();
return ResponseEntity.ok(adminKeywordService.addKeyword(request));
}

/**
* 특정 키워드 삭제
*/
@Override
@DeleteMapping("/{keyword}")
@DeleteMapping("/{keyword-id}")
@LogMonitoringInvocation
public ResponseEntity<Void> deleteKeyword(
@AuthenticationPrincipal CustomOAuth2User userDetails,
@PathVariable String keyword) {
adminKeywordService.deleteKeyword(keyword);
@AuthenticationPrincipal CustomOAuth2User customOAuth2User,
@PathVariable(value = "keyword-id") UUID keywordId) {
adminKeywordService.deleteKeyword(keywordId);
return ResponseEntity.ok().build();
}

@Override
@PostMapping("/list")
public ResponseEntity<Void> createKeywordList(
@AuthenticationPrincipal CustomOAuth2User customOAuth2User,
KeywordCategory category) {
openAIKeywordService.generateKeywords(category);
return ResponseEntity.ok().build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package onepiece.dailysnapbackend.controller;

import io.swagger.v3.oas.annotations.Operation;
import java.util.UUID;
import onepiece.dailysnapbackend.object.constants.KeywordCategory;
import onepiece.dailysnapbackend.object.dto.CustomOAuth2User;
import onepiece.dailysnapbackend.object.dto.KeywordRequest;
import onepiece.dailysnapbackend.object.dto.KeywordResponse;
import org.springframework.http.ResponseEntity;

public interface AdminKeywordControllerDocs {

@Operation(
summary = "특정 날짜에 제공할 키워드 추가 (관리자 지정)",
description = """
### 요청 파라미터
- `category` (KeywordCategory, required): 키워드 카테고리 (예: ADMIN_SET)
- `koreanKeyword` (String, required): 한국어 키워드
- `englishKeyword` (String, required): 영어 키워드
- `specifiedDate` (LocalDate, required): 키워드를 제공할 날짜 (YYYY-MM-DD)

### 응답 데이터
- `keywordId` (UUID): 생성된 키워드 ID
- `koreanKeyword` (String): 등록된 한국어 키워드
- `englishKeyword` (String): 등록된 영어 키워드
- `keywordCategory` (KeywordCategory): 키워드 카테고리
- `providedDate` (LocalDate): 제공 날짜 (YYYY-MM-DD)
- `used` (boolean): 사용 여부 (기본 `false`)

### 사용 방법
1. 관리자 권한을 가진 클라이언트에서 Authorization 헤더에 `Bearer {accessToken}`을 포함합니다.
2. 아래 JSON 예시처럼 `/api/...` 엔드포인트로 POST 요청을 보냅니다:
```json
{
"category": "ADMIN_SET",
"koreanKeyword": "주제",
"englishKeyword": "topic",
"specifiedDate": "2025-08-09"
}
```
3. 서버가 키워드를 저장하고, 생성된 키워드 정보를 반환합니다.

### 유의 사항
- `specifiedDate`는 오늘 이후 날짜여야 합니다.
- 동일한 `koreanKeyword`가 이미 존재할 수 없습니다.
"""
)
ResponseEntity<KeywordResponse> addKeyword(
CustomOAuth2User customOAuth2User,
KeywordRequest request
);

@Operation(
summary = "특정 키워드 삭제",
description = """
### 요청 파라미터
- `keyword-id` (UUID, required, path): 삭제할 키워드의 고유 ID

### 응답 데이터
- 없음 (빈 본문)

### 사용 방법
1. 관리자 권한을 가진 클라이언트에서 Authorization 헤더에 `Bearer {accessToken}`을 포함합니다.
2. 아래와 같이 DELETE 요청을 보냅니다:
```
DELETE /admin/keyword/{keyword-id}
```

### 유의 사항
- 관리자 권한이 필요합니다.
- 성공 시 HTTP 200 OK 응답이 반환됩니다.
"""
)
ResponseEntity<Void> deleteKeyword(
CustomOAuth2User customOAuth2User,
UUID keywordId
);

@Operation(
summary = "키워드 자동 생성 및 저장 (OpenAI 연동)",
description = """
### 요청 파라미터
- `category` (KeywordCategory, required, query): 생성할 키워드 카테고리
가능한 값: `SPRING`, `SUMMER`, `AUTUMN`, `WINTER`, `TRAVEL`, `DAILY`, `ABSTRACT`, `RANDOM`

### 응답 데이터
- 없음 (빈 본문, HTTP 200 OK)

### 사용 방법
1. 관리자 권한을 가진 클라이언트에서 Authorization 헤더에 `Bearer {accessToken}`을 포함합니다.
2. 아래와 같이 카테고리 쿼리 파라미터를 포함하여 POST 요청을 보냅니다:
```
POST /api/keyword/list?category=SUMMER
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
```
3. 서버가 지정된 카테고리에 대해 OpenAI API를 호출하여
- 한국어 키워드 및 대응 영어 번역 키워드를 생성
- 중복 및 제공일 순서에 맞춰 DB에 저장
작업을 수행한 후 200 OK를 반환합니다.

### 유의 사항
- 관리자 권한이 필요합니다.
- `category` 값은 `KeywordCategory` enum에 정의된 값만 허용됩니다.
- OpenAI API 호출로 인해 지연이 발생할 수 있습니다.
- 기존 DB에 이미 존재하는 한글 키워드는 저장에서 제외됩니다.
"""
)
ResponseEntity<Void> createKeywordList(
CustomOAuth2User customOAuth2User,
KeywordCategory category
);
}
Original file line number Diff line number Diff line change
@@ -1,45 +1,48 @@
package onepiece.dailysnapbackend.controller;

import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import onepiece.dailysnapbackend.object.dto.SignInRequest;
import onepiece.dailysnapbackend.object.dto.LoginRequest;
import onepiece.dailysnapbackend.object.dto.LoginResponse;
import onepiece.dailysnapbackend.object.dto.ReissueRequest;
import onepiece.dailysnapbackend.service.MemberService;
import onepiece.dailysnapbackend.util.log.LogMonitoringInvocation;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequiredArgsConstructor
@Tag(
name = "인증 API",
description = "회원 인증 API 제공"
name = "회원 API",
description = "회원 API 제공"
)
@RequestMapping("/api/auth")
public class AuthController implements AuthControllerDocs {

private final MemberService memberService;

// ===========================
// 인증 관련 API
// ===========================

// 로그인
@Override
@PostMapping(value = "/login")
@LogMonitoringInvocation
public ResponseEntity<Void> signIn(SignInRequest request, HttpServletResponse response) {
memberService.socialSignIn(request, response);
return ResponseEntity.ok().build();
public ResponseEntity<LoginResponse> login(
@Valid @RequestBody LoginRequest request
) {
return ResponseEntity.ok(memberService.socialSignIn(request));
}


// 액세스 토큰 재발급
@Override
@PostMapping("/api/auth/reissue")
@PostMapping("/reissue")
@LogMonitoringInvocation
public ResponseEntity<Void> reissue(HttpServletRequest request, HttpServletResponse response) {
memberService.reissue(request, response);
return ResponseEntity.ok().build();
public ResponseEntity<LoginResponse> reissue(
@Valid @RequestBody ReissueRequest request
) {
return ResponseEntity.ok(memberService.reissue(request));
}
}
Loading