Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
89 commits
Select commit Hold shift + click to select a range
a3c9a37
#250 [Fix] 투표 비율 계산 기준을 preVoteOption에서 postVoteOption으로 수정 (#252)
HYH0804 May 27, 2026
a38c141
#254 [Refactor] 관점 등록/수정 시 GPT 검수 코드 제거 (#256)
HYH0804 Jun 3, 2026
cee2c5d
#255 [Refactor] VoteStatsResponse OptionStat에 title 필드 추가 (#257)
HYH0804 Jun 3, 2026
53533b8
#258 [Feat] 출석체크 API 추가
si-zero Jun 3, 2026
77db7a1
#260 [Feat] 스웨거 어드민/유저 분리
si-zero Jun 4, 2026
a9d4293
[Feat] 알림기능 (#263)
HYH0804 Jun 11, 2026
b5a681b
#262 [Fix] APNs 자격증명 S3 다운로드 시 임시파일 충돌 오류 수정
HYH0804 Jun 11, 2026
d281fa3
#264 [Fix] AdMob SSV 포인트 미적립 버그 수정
si-zero Jun 11, 2026
968d6a3
#262 merge (#266)
HYH0804 Jun 14, 2026
d041631
[Chore] adMob 크롤링 파일 설정: adMob 크롤링 파일 설정 #269
si-zero Jun 16, 2026
ec25882
#270 [Feat] 설정 파일 추가
si-zero Jun 16, 2026
4c45999
[Chore] JWT필터 설정 #271
si-zero Jun 16, 2026
e0c9c1c
#275 [Fix] AdMob SSV 콜백 파라미터 optional 처리 및 서명 검증 활성화
si-zero Jun 18, 2026
ab2cb78
#277 [Fix] AdMob SSV 콜백 URL 검증 요청 처리 실패
si-zero Jun 18, 2026
04d947c
#279 [Fix] AdMob SSV 엔드포인트 검증 실패 시 항상 200 반환
si-zero Jun 18, 2026
d9ea3b5
#283 [Fix] /me/content-activities voteSide null 반환 버그
si-zero Jun 19, 2026
ec27b01
#285 [Feat] content-activities 응답에 option_title 필드 추가
si-zero Jun 20, 2026
6663e7a
#287 [Fix] AdMob SSV 서명 검증 오류로 인한 포인트 미지급 버그
si-zero Jun 24, 2026
39aca84
#289 [Fix] 오늘의 배틀 개수 변경
si-zero Jul 5, 2026
11e2da1
#293 [Fix] 사전투표 철학자 아이콘 미노출
si-zero Jul 5, 2026
aa449f0
#297 [Fix] 댓글 작성 시각이 9시간 전으로 표시되는 버그
si-zero Jul 6, 2026
3259a1a
#301 [Fix] robots.txt 파일 부재로 AdMob app-ads.txt 크롤링 차단
si-zero Jul 6, 2026
e91819b
#305 [Fix] S3 오디오 조각 재다운로드 시 FileAlreadyExistsException으로 TTS 파이프라인 실패
si-zero Jul 6, 2026
d64d5ca
#309 [Fix] JwtFilter 자체 화이트리스트에 /robots.txt 누락으로 401 발생
si-zero Jul 6, 2026
ff16d85
#316 [Fix] 포인트 정책표 대비 코드 불일치 4건 수정
si-zero Jul 7, 2026
f50c749
Merge branch 'main' into dev
si-zero Jul 7, 2026
d9e28a1
#321 [Feat] 알림 미읽음 여부 확인 API 추가
si-zero Jul 7, 2026
4279341
#313 [Hotfix] Railway 배포 환경 FFmpeg 자동 설치 적용 (#320)
jucheonsu Jul 7, 2026
f77048c
#327 [Fix] 알림 전체 읽음 처리 후 모두 읽음 신호가 전달되지 않던 문제 수정
si-zero Jul 13, 2026
99886f9
#331 [Docs] 출석체크 API 명세서를 실제 코드에 맞춰 갱신
si-zero Jul 13, 2026
087b8c0
#333 [Fix] app-ads.txt가 text/html로 응답되어 AdMob 크롤러가 무시하는 문제 수정
si-zero Jul 13, 2026
d33ff1f
#337 [Feat] 인앱 알림 테스트용 API 추가
si-zero Jul 14, 2026
17f35a4
#341 [Fix] 미문서화/중복 포인트 지급 로직 정리
si-zero Jul 15, 2026
acbe078
관리자 페이지를 새 admin-web(Next.js) 앱으로 리다이렉트 (#346)
Jul 15, 2026
de75bdf
#349 [Feat] admin.web.url env 주입 + 기본값 picke.up.railway.app (#350)
Jul 15, 2026
9493351
#352 [Fix] JwtFilter 화이트리스트 /api/v1/auth 전체 매칭으로 logout 인증 우회, 500 에러 발생
si-zero Jul 16, 2026
00bfcb3
#356 [Feat] 이용약관/개인정보처리방침 페이지 백엔드 서빙
si-zero Jul 20, 2026
99c936d
#361 [Feat] 관리자용 테스트 계정 로컬 로그인 기능 추가
si-zero Jul 24, 2026
f0a837d
#360 [Refactor] 1인 1관점 제한을 해제하고 1인 다수 관점 등록 허용
si-zero Jul 24, 2026
9fb601f
#366 [Feat] 내 콘텐츠 활동 내역(댓글 탭)에 내가 작성한 관점 포함
si-zero Jul 24, 2026
6a1abde
#370 [Fix] 사전투표만 한 기록도 내 배틀 기록에 노출되는 버그
si-zero Jul 24, 2026
80f5b9c
[Feat] 관점 200자 제한 해제 (#375)
si-zero Aug 2, 2026
f9bc278
#378 [Feat] 관리자 예약 푸시알림(제목/소제목/발송시간) 설정 및 자동발송
si-zero Aug 8, 2026
99f4765
#382 [Fix] 내 콘텐츠 활동 댓글 정보 매핑 오류 수정 (#383)
jucheonsu Aug 9, 2026
79cb355
#387 [Refactor] 철학자 태그 제한 및 가치관 육각형 계산 보정 (#388)
jucheonsu Aug 13, 2026
bdbe3b3
#391 [Remove] 미사용 파일 정리 (비활성 CI 워크플로우 + 레거시 admin 프론트)
si-zero Aug 13, 2026
a82c575
#392 [CI/CD] Java CI 파이프라인 브랜치명 오류 및 지속적 빌드 실패 수정
si-zero Aug 13, 2026
70ccc90
#395 [Feat] 관리자용 푸시 알림 발송 테스트 API 추가
si-zero Aug 16, 2026
2e72bf7
#396 [Fix] Android FCM 푸시에 title/body가 실리지 않아 알림이 안 뜨던 문제 수정
si-zero Aug 16, 2026
1fc8488
#401 [Fix] FCM 만료된 디바이스 토큰이 정리되지 않아 반복 발송 실패 로그가 쌓이는 문제 수정
si-zero Aug 17, 2026
a4d3b31
#405 [Feat] 어드민 예약 알림 On/Off 전환 API 추가 및 관리자 알림 API 문서화
si-zero Aug 26, 2026
0b9a374
#407 [Feat] 어드민 유저 검색 API 추가
si-zero Aug 26, 2026
869de77
#409 [Feat] 어드민 공지사항 수정/삭제 API 추가
si-zero Aug 26, 2026
087fea5
#411 [Feat] 어드민 공지 발송 결과 조회 API 추가
si-zero Aug 26, 2026
4bdbdf3
#413 [Feat] 어드민 공지 발송 대상자 수 미리보기 API 추가
si-zero Aug 26, 2026
76e8097
#414 [Feat] 어드민 공지 작성 옵션(카테고리) 조회 API 추가
si-zero Aug 26, 2026
26116e8
#415 [Feat] 어드민 예약 알림 테스트 발송 API 추가
si-zero Aug 26, 2026
4234c29
#423 [Docs] 누락된 마이그레이션 SQL 파일 보완 (#409, #411)
si-zero Aug 30, 2026
7aaa1f4
#421 [Feat] 유저 일별 활동 트래킹(DAU/MAU 기반) 인프라 구축
si-zero Aug 30, 2026
d3b9343
#425 [Feat] 어드민 대시보드 오늘 요약 API 추가
si-zero Sep 1, 2026
2129d35
#427 [Feat] 어드민 대시보드 DAU/MAU 추이 API 추가
si-zero Sep 1, 2026
d8fe672
#429 [Feat] 어드민 대시보드 신규 가입자 추이 API 추가
si-zero Sep 1, 2026
6eea0d4
#431 [Feat] 어드민 대시보드 배틀 참여율 API 추가
si-zero Sep 1, 2026
1e134bc
#433 [Feat] 어드민 대시보드 출석 체크율 API 추가
si-zero Sep 1, 2026
cba91cb
#436 [Feat] 어드민 대시보드 크레딧 지급 현황 API 추가
si-zero Sep 2, 2026
23496d1
#444 [Refactor] AWS S3를 Railway Bucket으로 마이그레이션 (개발 서버)
si-zero Sep 7, 2026
7b0b68f
🐛 Fix: BattleVoteServiceImplTest 시간대 불일치로 15시(UTC) 이후 CI 실패 (#442)
Roy-wonji Sep 7, 2026
c99065e
✨ Feat: ad.picke.store 제휴 광고(쿠팡 파트너스·애드픽) 연동 (#443)
Roy-wonji Sep 8, 2026
d22f21a
#441 [Fix] 쿠팡 파트너스 ID 환경변수 누락 시 앱 부팅 실패 해소 (#448)
Roy-wonji Sep 8, 2026
9745f2e
#441 [Fix] 광고 스웨거 기본 서버를 문서를 연 주소로 바꿈 (#449)
Roy-wonji Sep 8, 2026
e5a0422
#441 [Fix] 필수 쿼리 파라미터 누락 시 500 대신 400 응답 (#450)
Roy-wonji Sep 8, 2026
c3bd52b
#441 [Feat] 광고 스웨거 탭에 운영 광고 도메인 추가 (#451)
Roy-wonji Sep 8, 2026
476b0dd
#441 [Feat] 애드픽 쇼핑·핫딜 상품을 광고 재고로 편입 (#454)
Roy-wonji Sep 8, 2026
495f772
#441 [Feat] 애드픽 쇼핑 상품에 더 높은 로테이션 가중치 부여 (#457)
Roy-wonji Sep 9, 2026
093278c
AdFit 수익과 비용을 기록해 관리자 ROI를 확인하도록 지원
Roy-wonji Sep 10, 2026
73a4214
test: 사용자 광고 API 조회 계약 검증 (#460)
Roy-wonji Sep 10, 2026
27e7b79
fix: 사용자 광고에서 설치·가입 캠페인 제외 (#463)
Roy-wonji Sep 10, 2026
a612828
feat: AdFit 일별 수익 자동 조회 연결 (#465)
Roy-wonji Sep 10, 2026
6891d33
#455 [Feat] 배틀 시나리오 감정 톤 + 철학자 보이스 매핑 인프라
si-zero Sep 10, 2026
291918c
#467 [Feat] 배틀 대본 붙여넣기 파서 (배틀·시나리오 자동 등록)
si-zero Sep 11, 2026
3ed96b8
#469 [Feat] 배틀 대본 파서 대사를 문장 종결부호 단위로 분리
si-zero Sep 11, 2026
8efa2ad
#471 [Feat] 배틀 대본 파서 summary·description·선택지 title 매핑 수정
si-zero Sep 11, 2026
073dfd5
#473 [Feat] 시나리오 선택지에 선택의 시간 설명 텍스트(title) 추가
si-zero Sep 11, 2026
58ce7ad
#475 [Feat] 철학자 보이스 매핑에 이미지 키(image_key) 추가
si-zero Sep 12, 2026
759f746
#477 [Docs] 철학자 보이스 매핑에 실제 이미지 키 반영
si-zero Sep 12, 2026
7a41a14
#479 [Fix] 철학자 보이스 imageKey 응답을 호출 가능한 경로로 변환
si-zero Sep 12, 2026
4cf5227
#481 [Fix] 배틀 옵션 태그 수정 시 유니크 제약 위반(23505) 발생
si-zero Sep 12, 2026
a7b0c3b
#483 [Fix] 로컬 draft→S3 promote가 DB 트랜잭션과 분리되어 orphan 파일/유실 위험
si-zero Sep 12, 2026
b76fd93
Merge branch 'main' into merge/#485
si-zero Sep 12, 2026
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
43 changes: 38 additions & 5 deletions docs/api-specs/battle-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,40 @@

기준 컨트롤러: `AdminBattleController`

### 2.1 배틀 생성
### 2.1 배틀 대본 붙여넣기 파싱 (미리보기용, 저장 안 함)
- `POST /api/v1/admin/battles/parse`
- 요청 본문(`AdminBattleParseRequest`):
- `rawText` (기획자가 구글독스에서 복사한 대본 원문 전체)
- 설명: 대본을 파싱해 배틀·시나리오 등록 폼을 자동으로 채워준다. **DB 저장은 하지 않는다.** 문서 포맷 규격/파싱 규칙은 `배틀_발행_시나리오_현행_vs_개선.md` 2.6, 실제 파싱 예시는 `배틀_대본_파싱_예시.md` 참고.
- 응답(`AdminBattleParseResponse`):
- `battlePayload` — 아래 2.2 배틀 생성 요청과 동일한 형태(`AdminBattleCreateRequest`). `status` 는 항상 `PENDING`. 문서에 없는 `thumbnailUrl`/`targetDate`/`publishAt`/`audioDuration` 은 `null`
- `summary` = 오프닝 전문을 LLM 이 40자 이내로 한 줄 요약한 것. 요약 생성 실패 시 오프닝 원문 그대로 들어가고 `SUMMARY_GENERATION_FAILED` warning 이 붙는다
- `description` = 오프닝 전문 원문 그대로
- `options[].title` = **사전 투표 영역의 짧은 선택지명이 있으면 그걸 우선 사용**(예: `"유죄다" vs "무죄다"` → `유죄다`/`무죄다`), 사전 투표가 없으면 메타데이터 표의 "선택지 명칭"으로 대체
- `scenarioPayload` — [시나리오 API](./scenario-api.md) 2.2 생성 요청과 동일한 형태(`AdminScenarioCreateRequest`). **`battleId` 는 항상 `null`** — 배틀이 아직 생성 전이라서다. 어드민이 `battlePayload` 로 배틀을 먼저 만들고, 응답으로 받은 `battleId` 를 채워 시나리오를 등록한다
- `speakerNames` — `{"A": "플라톤", "B": "마르크스"}` 형태로 화자 A/B 에 바인딩된 철학자 이름
- `warnings[]` — 아래 표. `blocking: true` 인 항목이 하나라도 있으면 미리보기에서 해결하기 전까지 발행하지 않는다
- **문장 단위 분리**: `scenarioPayload.nodes[].scripts[]` 는 원문의 한 발언(문단)을 그대로 담지 않고, 마침표/느낌표/물음표(`.` `!` `?`) 뒤 공백 기준으로 **문장마다 별도 script** 로 쪼갠다. 같은 발언에서 쪼개진 문장들은 `speakerName`/`speakerType`/`tone` 이 전부 동일하다. 오디오 파이프라인이 script 마다 무음(600ms)을 넣으므로 문장 단위 분리가 곧 자연스러운 끊어읽기가 된다. 종결부호 뒤에 닫는 따옴표(`"`/`'`)가 먼저 오면 그 지점은 안 끊긴다(알려진 제한사항).
- **선택지 설명(`interactiveOptions[].title`)**: `👉 [선택의 시간]` 아래 `A: {설명} (철학자)` / `B: {설명} (철학자)` 줄의 `{설명}` 부분이 그대로 들어간다. 인터랙티브(분기 O) 배틀에만 존재.

**warning 코드**

| code | 의미 | blocking |
|---|---|---|
| `MISSING_HEADER` | 첫 줄에서 `— 제목` 형식을 못 찾음 | no |
| `MISSING_METADATA` / `MISSING_CATEGORY` | 메타데이터 섹션/카테고리를 못 찾음 | no |
| `UNKNOWN_CATEGORY_TAG` | 그 카테고리 문자열의 CATEGORY 태그가 DB 에 없음 | no |
| `UNKNOWN_PHILOSOPHER_TAG` | 철학자 키워드가 철학자 유형 10인이 아님 | no |
| `MISSING_PHILOSOPHER_TAG` / `MISSING_VALUE_TAG` | 태그 문자열은 맞지만 DB row 가 없음 | no |
| `UNKNOWN_VALUE_TAG` | 성향 지표가 가치관 12축 문자열(원칙/결과/이성/감성/개인/관계/변화/전통/내면/구조/이상/현실)이 아님 | no |
| `MULTIPLE_TONE_TAGS` | 대사 한 줄에 톤 태그가 여러 개 → 첫 번째만 사용 | no |
| `SPEAKER_BINDING_FALLBACK` | 발화자↔A/B 매칭을 등장 순서로 임시 배정 | no |
| `LLM_CLASSIFY_FAILED` | 감정 자동분류 실패 → 톤이 전부 `NEUTRAL` | no |
| `SUMMARY_GENERATION_FAILED` | 오프닝 한 줄 요약 생성 실패 → `summary` 에 오프닝 원문이 그대로 들어감 | no |
| `MISSING_VOICE` | 발화자 보이스가 [철학자 보이스 매핑](./philosopher-voice-api.md) 에 없음 | **yes** |
| `INCOMPLETE_SPEAKER_BINDING` | A/B 발화자를 확정하지 못함 | **yes** |

### 2.2 배틀 생성
- `POST /api/v1/admin/battles`
- 요청 본문(`AdminBattleCreateRequest`) 주요 필드:
- `title`
Expand All @@ -47,21 +80,21 @@
- `imageUrl`
- `tagIds` (철학자/가치관 태그 ID 목록)

### 2.2 배틀 목록
### 2.3 배틀 목록
- `GET /api/v1/admin/battles`
- 쿼리 파라미터:
- `page` (기본값: `1`)
- `size` (기본값: `10`)
- `status` (선택)

### 2.3 배틀 상세
### 2.4 배틀 상세
- `GET /api/v1/admin/battles/{battleId}`

### 2.4 배틀 수정
### 2.5 배틀 수정
- `PATCH /api/v1/admin/battles/{battleId}`
- 요청 본문(`AdminBattleUpdateRequest`) 필드 구조는 생성과 동일

### 2.5 배틀 삭제
### 2.6 배틀 삭제
- `DELETE /api/v1/admin/battles/{battleId}`

---
Expand Down
44 changes: 44 additions & 0 deletions docs/api-specs/philosopher-voice-api.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# 철학자 보이스 매핑(Philosopher Voice) API 명세

기준 코드:
`src/main/java/com/swyp/picke/domain/admin/controller/AdminPhilosopherVoiceController.java`
`src/main/java/com/swyp/picke/domain/scenario/service/PhilosopherVoiceService.java`

철학자 이름 → Fish Audio 보이스(`reference_id`) 매핑을 관리한다. [배틀 대본 붙여넣기 파서](./battle-api.md) 가
시나리오 발화자(옵션 A/B 철학자)에 맞는 보이스를 여기서 조회해 `scenario.voiceSettings` 를 채운다.
전부 관리자 전용 API. 사용자 API 없음.

## 1. 관리자 API

### 1.1 목록 조회
- `GET /api/v1/admin/philosopher-voices`
- 응답(`PhilosopherVoiceResponse[]`): `id`, `name`, `referenceId`, `voiceLabel`, `imageKey`, `note`
- **`imageKey`는 DB의 raw 저장 키가 아니라 `ResourceUrlProvider`로 변환된 호출 가능한 경로다** (다른 이미지 필드인 배틀 썸네일·옵션 이미지와 동일 규칙). 값이 있으면 그대로 `<img src>`에 써도 된다. `null`이면 이미지가 등록 안 된 것 — 프론트 기본 이미지로 대체

### 1.2 생성
- `POST /api/v1/admin/philosopher-voices`
- 요청 본문(`PhilosopherVoiceRequest`):
- `name` (필수, 유니크)
- `referenceId` (필수, Fish Audio 보이스 모델 ID)
- `voiceLabel` (선택, 표시용 라벨. 예: `"미호크 장정진"`)
- `imageKey` (선택, 철학자 이미지 저장 키. 예: `"images/philosophers/rousseau.png"`)
- `note` (선택)
- 이름 중복 시 `PHILOSOPHER_VOICE_409_DUP`

### 1.3 수정
- `PATCH /api/v1/admin/philosopher-voices/{id}`
- 요청 본문은 생성과 동일. `name` 은 응답에서 그대로 유지되고 `referenceId`/`voiceLabel`/`note` 만 갱신된다
- 대상이 없으면 `PHIL_VOICE_404`

### 1.4 삭제
- `DELETE /api/v1/admin/philosopher-voices/{id}`
- 대상이 없으면 `PHIL_VOICE_404`

## 2. 상태/동작 메모

- 테이블은 `spring.jpa.hibernate.ddl-auto=update` 가 자동 생성한다. 소프트 삭제 없음 (참조하는 FK가 없어 hard delete)
- 초기 시드: `docs/db/20260910_seed_philosopher_voice.sql` (`ON CONFLICT (name) DO NOTHING`) — 환경별 1회 수동 실행 필요. 재실행해도 안전하고 어드민에서 수정한 값을 덮지 않는다
- **NARRATOR / USER 고정 보이스는 이 테이블이 아니라 config** (`fishaudio.voice-id.narrator`, `fishaudio.voice-id.user`). 이 API 는 A/B(철학자) 보이스 전용
- 붙여넣기 파서가 발화자 이름으로 조회했는데 매핑이 없으면 `MISSING_VOICE` warning(blocking) 을 내고, 관리자가 미리보기에서 보이스를 고르거나 이 API 로 매핑을 추가한 뒤 다시 파싱해야 한다
- 배틀별로 다른 보이스를 쓰고 싶으면 이 테이블을 바꾸지 않고 시나리오의 `voiceSettings` 를 직접 오버라이드하면 된다(이 테이블은 파서가 채우는 기본값 소스일 뿐)
- **`imageKey`**: `PhilosopherType` enum(철학자 유형 10인)만 실제 이미지가 있고, 그 밖의 철학자(예: 루소, 홉스, 쇼펜하우어)는 원래 이름을 해시해서 10개 이미지 중 하나를 무작위로 배정하는 폴백이 있었다(`PhilosopherType.resolveImageKey`) — 엉뚱한 철학자 이미지가 뜨는 문제가 있었음. 프론트는 배틀 생성 화면에서 이 API의 `imageKey` 로 정확한 이미지를 조회해 쓰고, 값이 없으면(아직 등록 안 된 철학자) 기본 이미지로 대체해야 한다. `imageKey` 는 `null` 이어도 되며, 랜덤 배정 로직으로 대신하지 않는다
8 changes: 6 additions & 2 deletions docs/api-specs/scenario-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,13 @@
- `scripts[]`
- `speakerName`
- `speakerType`
- `text`
- `text` (오디오 효과 태그 `[sighing]` `[break]` 등은 인라인으로 그대로 둔다)
- `tone` (`Tone` enum, 생략 시 `NEUTRAL`: `NEUTRAL`/`ANGRY`/`SAD`/`EMBARRASSED`/`EMPHASIS`/`WHISPERING`/`SOFT_TONE`/`BREATHY`/`EXCITED`. 대사 한 줄의 감성적인 톤 — TTS 합성 시 `[tone]` 형태로 text 앞에 주입된다)
- `interactiveOptions[]`
- `label`
- `title` (선택 시 보여줄 선택지 설명, 생략 가능)
- `nextNodeName`
- `voiceSettings` (`Map<SpeakerType, String>`)
- `voiceSettings` (`Map<SpeakerType, String>`, 값은 Fish Audio `reference_id`. 철학자 발화자(A/B)는 [철학자 보이스 매핑](./philosopher-voice-api.md)에서 조회해 채운다)

### 2.3 시나리오 본문 수정
- `PUT /api/v1/admin/scenarios/{scenarioId}`
Expand All @@ -58,3 +60,5 @@

- 임시저장(`DRAFT`) 상태에서는 대본/설정은 DB 저장, 발행(`PUBLISHED`) 시점에 TTS 파이프라인 수행
- 발행 후 수정 시에는 변경된 스크립트 조각만 재생성하고 병합 오디오를 갱신
- `text` 또는 `tone` 이 바뀐 스크립트만 오디오를 재생성한다 (그 외 조각은 S3 캐시 재사용)
- TTS 는 Fish Audio S2.1 Pro 사용 (`fishaudio.tts.model`, 기본값 `s2.1-pro`). 톤 태그는 `[...]` 대괄호로 `text` 앞에 붙어 전송된다
72 changes: 72 additions & 0 deletions docs/db/20260910_seed_philosopher_voice.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
-- 철학자 → Fish Audio 보이스(reference_id) 초기 매핑 시드
-- 관련 설계: 배틀_발행_시나리오_현행_vs_개선.md 2.5
--
-- 테이블 자체는 spring.jpa.hibernate.ddl-auto=update 가 PhilosopherVoice 엔티티를 보고 생성한다.
-- 이 스크립트는 초기 데이터만 넣는다. 환경별(dev / prod / local)로 1회 실행.
-- ON CONFLICT DO NOTHING 이라 재실행해도 안전하고, 어드민에서 수정한 값을 덮지 않는다.
-- 이후 매핑 변경/추가는 어드민 CRUD(/api/v1/admin/philosopher-voices)로 한다.

INSERT INTO philosopher_voice (name, reference_id, voice_label, created_at, updated_at) VALUES
('소크라테스', '02f1c443c12b4f1fa0777983d07e64af', '정감 있는 이야기꾼(남자 노인)', now(), now()),
('스마일즈', '02f1c443c12b4f1fa0777983d07e64af', '정감 있는 이야기꾼(남자 노인)', now(), now()),
('플라톤', '02f1c443c12b4f1fa0777983d07e64af', '정감 있는 이야기꾼(남자 노인)', now(), now()),
('톨스토이', '02f1c443c12b4f1fa0777983d07e64af', '정감 있는 이야기꾼(남자 노인)', now(), now()),
('맹자', '02f1c443c12b4f1fa0777983d07e64af', '정감 있는 이야기꾼(남자 노인)', now(), now()),
('공자', '02f1c443c12b4f1fa0777983d07e64af', '정감 있는 이야기꾼(남자 노인)', now(), now()),
('칙센트미하이', '02f1c443c12b4f1fa0777983d07e64af', '정감 있는 이야기꾼(남자 노인)', now(), now()),
('히포크라테스', '02f1c443c12b4f1fa0777983d07e64af', '정감 있는 이야기꾼(남자 노인)', now(), now()),

('아리스토텔레스', '87d3970929bf47e19cf38472eda5764d', '한국 성우', now(), now()),
('벤담', '87d3970929bf47e19cf38472eda5764d', '한국 성우', now(), now()),
('한비자', '87d3970929bf47e19cf38472eda5764d', '한국 성우', now(), now()),
('피터싱어', '87d3970929bf47e19cf38472eda5764d', '한국 성우', now(), now()),
('볼테르', '87d3970929bf47e19cf38472eda5764d', '한국 성우', now(), now()),
('푸코', '87d3970929bf47e19cf38472eda5764d', '한국 성우', now(), now()),
('토크빌', '87d3970929bf47e19cf38472eda5764d', '한국 성우', now(), now()),
('베카리아', '87d3970929bf47e19cf38472eda5764d', '한국 성우', now(), now()),
('스피노자', '87d3970929bf47e19cf38472eda5764d', '한국 성우', now(), now()),
('애덤 그랜트', '87d3970929bf47e19cf38472eda5764d', '한국 성우', now(), now()),
('버크', '87d3970929bf47e19cf38472eda5764d', '한국 성우', now(), now()),
('쇼펜하우어', '87d3970929bf47e19cf38472eda5764d', '한국 성우', now(), now()),
('노자', '87d3970929bf47e19cf38472eda5764d', '한국 성우', now(), now()),
('보르헤스', '87d3970929bf47e19cf38472eda5764d', '한국 성우', now(), now()),
('월드런', '87d3970929bf47e19cf38472eda5764d', '한국 성우', now(), now()),

('홉스', '14da644e89dc4ab18a3c066788b3f2c5', '미호크 장정진', now(), now()),
('비트겐슈타인', '14da644e89dc4ab18a3c066788b3f2c5', '미호크 장정진', now(), now()),
('도스토옙스키', '14da644e89dc4ab18a3c066788b3f2c5', '미호크 장정진', now(), now()),
('니체', '14da644e89dc4ab18a3c066788b3f2c5', '미호크 장정진', now(), now()),
('밀', '14da644e89dc4ab18a3c066788b3f2c5', '미호크 장정진', now(), now()),
('뒤르켐', '14da644e89dc4ab18a3c066788b3f2c5', '미호크 장정진', now(), now()),
('베이컨', '14da644e89dc4ab18a3c066788b3f2c5', '미호크 장정진', now(), now()),
('존 로크', '14da644e89dc4ab18a3c066788b3f2c5', '미호크 장정진', now(), now()),
('루소', '14da644e89dc4ab18a3c066788b3f2c5', '미호크 장정진', now(), now()),
('에피쿠로스', '14da644e89dc4ab18a3c066788b3f2c5', '미호크 장정진', now(), now()),
('사르트르', '14da644e89dc4ab18a3c066788b3f2c5', '미호크 장정진', now(), now()),
('간디', '14da644e89dc4ab18a3c066788b3f2c5', '미호크 장정진', now(), now()),
('사마천', '14da644e89dc4ab18a3c066788b3f2c5', '미호크 장정진', now(), now()),
('프로이트', '14da644e89dc4ab18a3c066788b3f2c5', '미호크 장정진', now(), now()),
('카뮈', '14da644e89dc4ab18a3c066788b3f2c5', '미호크 장정진', now(), now()),
('데카르트', '14da644e89dc4ab18a3c066788b3f2c5', '미호크 장정진', now(), now()),

('칸트', '21c5fb9d1000425c9a74536691c46ce4', 'Meursault(남자 중저음)', now(), now()),
('애들러', '21c5fb9d1000425c9a74536691c46ce4', 'Meursault(남자 중저음)', now(), now()),
('마키아벨리', '21c5fb9d1000425c9a74536691c46ce4', 'Meursault(남자 중저음)', now(), now()),
('엘리아데', '21c5fb9d1000425c9a74536691c46ce4', 'Meursault(남자 중저음)', now(), now()),
('프롬', '21c5fb9d1000425c9a74536691c46ce4', 'Meursault(남자 중저음)', now(), now()),
('융', '21c5fb9d1000425c9a74536691c46ce4', 'Meursault(남자 중저음)', now(), now()),
('롤스', '21c5fb9d1000425c9a74536691c46ce4', 'Meursault(남자 중저음)', now(), now()),
('왈처', '21c5fb9d1000425c9a74536691c46ce4', 'Meursault(남자 중저음)', now(), now()),
('암베드카르', '21c5fb9d1000425c9a74536691c46ce4', 'Meursault(남자 중저음)', now(), now()),
('라이프니츠', '21c5fb9d1000425c9a74536691c46ce4', 'Meursault(남자 중저음)', now(), now()),
('카너먼', '21c5fb9d1000425c9a74536691c46ce4', 'Meursault(남자 중저음)', now(), now()),
('도킨스', '21c5fb9d1000425c9a74536691c46ce4', 'Meursault(남자 중저음)', now(), now()),
('흄', '21c5fb9d1000425c9a74536691c46ce4', 'Meursault(남자 중저음)', now(), now()),
('보드리야르', '21c5fb9d1000425c9a74536691c46ce4', 'Meursault(남자 중저음)', now(), now()),
('몽테스키외', '21c5fb9d1000425c9a74536691c46ce4', 'Meursault(남자 중저음)', now(), now()),
('마이어스', '21c5fb9d1000425c9a74536691c46ce4', 'Meursault(남자 중저음)', now(), now()),
('마르크스', '21c5fb9d1000425c9a74536691c46ce4', 'Meursault(남자 중저음)', now(), now()),
('바흐', '21c5fb9d1000425c9a74536691c46ce4', 'Meursault(남자 중저음)', now(), now()),

('아렌트', '90597c459f7a4438801589a5268c1628', '차분하고 지적인(여성)', now(), now())
ON CONFLICT (name) DO NOTHING;
35 changes: 35 additions & 0 deletions docs/db/20260912_update_philosopher_voice_image_key.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
-- philosopher_voice.image_key 채우기
-- 관련 이슈: #475 (PR #476)
--
-- Railway Bucket images/philosophers/ 에 실제 업로드된 파일과 이름이 일치하는
-- 22명만 채운다. 나머지 36명(voice는 있지만 이미지 없음)은 image_key = NULL 유지 —
-- 프론트가 null 이면 기본 플레이스홀더 이미지로 대체해야 한다(philosopher-voice-api.md 참고).
--
-- images/philosophers/ 에는 이 22명 외에 6개 파일이 더 있다
-- (aquinas, choe_hangi, jeong_yakyong, xunzi, yi_hwang, yi_i) —
-- 이 6명은 philosopher_voice 에 row 자체가 없다(Fish 보이스 미정이라 별도 처리 필요).
--
-- 환경별(dev/prod) 1회 실행. 재실행해도 안전(멱등 UPDATE).

UPDATE philosopher_voice SET image_key = 'images/philosophers/aristotle.png', updated_at = now() WHERE name = '아리스토텔레스';
UPDATE philosopher_voice SET image_key = 'images/philosophers/buddha.png', updated_at = now() WHERE name = '석가모니';
UPDATE philosopher_voice SET image_key = 'images/philosophers/camus.png', updated_at = now() WHERE name = '카뮈';
UPDATE philosopher_voice SET image_key = 'images/philosophers/confucius.png', updated_at = now() WHERE name = '공자';
UPDATE philosopher_voice SET image_key = 'images/philosophers/descartes.png', updated_at = now() WHERE name = '데카르트';
UPDATE philosopher_voice SET image_key = 'images/philosophers/epicurus.png', updated_at = now() WHERE name = '에피쿠로스';
UPDATE philosopher_voice SET image_key = 'images/philosophers/fromm.png', updated_at = now() WHERE name = '프롬';
UPDATE philosopher_voice SET image_key = 'images/philosophers/hobbes.png', updated_at = now() WHERE name = '홉스';
UPDATE philosopher_voice SET image_key = 'images/philosophers/hume.png', updated_at = now() WHERE name = '흄';
UPDATE philosopher_voice SET image_key = 'images/philosophers/jung.png', updated_at = now() WHERE name = '융';
UPDATE philosopher_voice SET image_key = 'images/philosophers/kant.png', updated_at = now() WHERE name = '칸트';
UPDATE philosopher_voice SET image_key = 'images/philosophers/laozi.png', updated_at = now() WHERE name = '노자';
UPDATE philosopher_voice SET image_key = 'images/philosophers/leibniz.png', updated_at = now() WHERE name = '라이프니츠';
UPDATE philosopher_voice SET image_key = 'images/philosophers/marx.png', updated_at = now() WHERE name = '마르크스';
UPDATE philosopher_voice SET image_key = 'images/philosophers/mencius.png', updated_at = now() WHERE name = '맹자';
UPDATE philosopher_voice SET image_key = 'images/philosophers/mill.png', updated_at = now() WHERE name = '밀';
UPDATE philosopher_voice SET image_key = 'images/philosophers/nietzsche.png', updated_at = now() WHERE name = '니체';
UPDATE philosopher_voice SET image_key = 'images/philosophers/plato.png', updated_at = now() WHERE name = '플라톤';
UPDATE philosopher_voice SET image_key = 'images/philosophers/rawls.png', updated_at = now() WHERE name = '롤스';
UPDATE philosopher_voice SET image_key = 'images/philosophers/sartre.png', updated_at = now() WHERE name = '사르트르';
UPDATE philosopher_voice SET image_key = 'images/philosophers/schopenhauer.png', updated_at = now() WHERE name = '쇼펜하우어';
UPDATE philosopher_voice SET image_key = 'images/philosophers/socrates.png', updated_at = now() WHERE name = '소크라테스';
Loading
Loading