Skip to content
Merged

Test #98

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
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