Files
sodalive-backend-spring-boot/docs/20260724_AI캐릭터_관리자_API/prd.md

23 KiB
Raw Blame History

PRD: AI 캐릭터 관리자 API

1. Overview

운영자가 AI 캐릭터용 Member로 직접 로그인하지 않고, ADMIN 권한으로 선택한 AI 캐릭터의 크리에이터 채널 자산을 대리 관리하는 신규 v2 관리자 API를 제공한다.


2. Problem

  • AI 캐릭터용 Member(memberKind = AI_CHARACTER)는 직접 로그인할 수 없어야 하지만, 운영자는 캐릭터의 콘텐츠, 시리즈, 커뮤니티, FanTalk 답변을 관리해야 한다.
  • 기존 기능은 creatorMember.id 기반으로 흩어져 있으며, 관리자 frontend가 레거시 endpoint를 조합하면 권한, 소유권, soft delete 의미가 일관되지 않을 수 있다.
  • 기존 creator/admin service 일부에는 소유권 검증이 약한 경로가 있어, 단순 위임만으로는 다른 캐릭터나 HUMAN creator 자원을 변경할 위험이 있다.
  • 기존 legacy/public API 계약은 유지해야 하므로 신규 관리자 표면은 별도 v2 경계로 제공되어야 한다.

3. Goals

  • 신규 prefix /api/v2/admin/ai-characters/**는 JWT auth claim의 ROLE_ADMIN과 JWT subject로 조회한 현재 DB Member.role == ADMIN을 모두 만족하는 요청만 허용한다.
  • 모든 신규 target endpoint는 외부 대상 식별자로 characterId를 받고, 서버가 ChatCharacter.creatorMember를 내부 행위자로 해석한다. 단, 캐릭터 목록/검색은 아직 선택된 target이 없어 characterId를 받지 않고, 캐릭터 생성은 새 ChatCharacter를 만드는 endpoint라 path characterId를 받지 않는다.
  • target 해석 시 ChatCharacter 존재, creatorMember 존재, creatorMember.role == CREATOR, creatorMember.memberKind == AI_CHARACTER를 모두 검증한다.
  • 검증 실패 시 4xx로 거부하고 DB, S3, 외부 캐릭터 API, 이벤트 발행 등 후속 부작용을 만들지 않는다.
  • 캐릭터, 오디오 콘텐츠, 시리즈, 커뮤니티 게시글, FanTalk 답변, 오디오 signed URL을 신규 관리자 API에서 관리한다.
  • 기존 legacy/public endpoint의 URI, 성공·오류 HTTP status, response body, message/i18n을 포함한 외부 계약은 변경하지 않는다. 단, 캐릭터 관리자 frontend가 기존 관리자 인증을 재사용할 수 있도록 /admin/member/login, /member/logout의 CORS 허용 Origin만 path-specific으로 확장한다.
  • 내부 구현은 신규 v2 controller/facade/application 경계를 두고, 기존 entity/repository/S3/CloudFront/event 컴포넌트는 테스트로 고정한 뒤 선택적으로 재사용한다.

4. Non-Goals

  • 이번 PRD는 관리자 API backend 요구사항과 구현 계획만 포함하며, 관리자 UI/frontend 구현은 포함하지 않는다.
  • AI 캐릭터용 Member의 access token, refresh token, 임시 세션, impersonation 로그인은 만들지 않는다.
  • creatorMemberId를 관리자 frontend의 필수 입력으로 노출하지 않는다.
  • HUMAN creator를 이 API로 대리 관리하지 않는다.
  • 위 두 공유 인증 경로의 CORS 허용 Origin 확장 외 기존 legacy/public endpoint 변경, 폐기, deprecation, schema 변경은 포함하지 않는다.
  • 기존 external character API business contract 변경은 포함하지 않으며, 변경이 필요하면 재확인한다.
  • 기존 soft delete 의미 변경은 포함하지 않으며, 변경이 필요하면 재확인한다.
  • 물리 삭제와 연관 데이터 cascade 삭제는 포함하지 않는다.
  • 신규 DB schema/DDL 또는 ChatCharacter-Member 관계 모델 변경은 포함하지 않는다.
  • 라이브, DM, 후원, 정산, 알림 설정, 랭킹 관리, 콘텐츠 구매/좋아요/댓글, 커뮤니티 구매/좋아요/댓글 관리는 포함하지 않는다.
  • FanTalk 원글 작성, 일반 사용자 대리 작성, nested reply 작성은 포함하지 않는다.
  • AudioContentCloudFront 복사/이동, signed URL 신규 dependency 추가는 포함하지 않는다.

5. Target Users

  • 운영자: AI 캐릭터를 대신해 캐릭터 프로필, 콘텐츠, 시리즈, 커뮤니티 게시글, FanTalk 답변을 관리하는 관리자
  • 관리자 frontend: 신규 v2 AI 캐릭터 관리자 API만으로 In-Scope 작업을 수행해야 하는 클라이언트
  • 서버 개발자: 기존 creator 기능을 회귀시키지 않으면서 AI 캐릭터 대리 관리 경계를 유지해야 하는 개발자

6. User Stories

  • 운영자는 AI 캐릭터 목록을 검색하고 상세 정보를 확인한 뒤 생성, 수정, 비활성화하고 싶다.
  • 운영자는 선택한 AI 캐릭터 소유 오디오 콘텐츠를 조회, 생성, 수정, soft delete하고 관리자 화면에서 재생 가능한 signed URL을 받고 싶다.
  • 운영자는 선택한 AI 캐릭터 소유 시리즈를 조회, 생성, 수정, soft delete하고 콘텐츠 연결/해제/순서를 관리하고 싶다.
  • 운영자는 선택한 AI 캐릭터 소유 커뮤니티 게시글을 작성, 수정, 고정/해제, soft delete하고 싶다.
  • 운영자는 선택한 AI 캐릭터가 자신의 활성 root FanTalk에 creator reply를 작성하게 하고 싶다.
  • 서버는 다른 AI 캐릭터나 HUMAN creator의 resource ID가 전달되면 변경 없이 4xx로 거부해야 한다.

7. Core Features

Feature A. 공통 인증, 인가, target 해석

Requirements

  • 모든 신규 prefix endpoint는 JWT ROLE_ADMIN과 현재 DB Member.role == ADMIN을 독립적으로 모두 검증한다.
  • JWT가 없거나 잘못됐거나 만료·폐기된 경우는 401, JWT role과 현재 DB role 중 하나라도 ADMIN이 아닌 경우는 403으로 처리한다.
  • JWT에는 ROLE_ADMIN이 남아 있지만 현재 DB role이 강등된 stale claim도 403으로 거부한다.
  • 모든 domain write/read는 characterIdChatCharacter를 조회한 뒤 연결된 creatorMember를 사용한다.
  • creatorMember는 도메인 소유권/작성자 판단에만 사용하고 Spring Security principal로 교체하지 않는다.
  • creatorMember 누락, role 불일치, memberKind 불일치 요청은 4xx로 거부한다.
  • 요청 중 누락 Member 생성, role/memberKind 자동 보정 같은 lazy repair는 하지 않는다.

Edge Cases

  • stale claim을 포함한 인증·인가 실패는 target resolver와 domain use-case 실행 전에 종료되어야 한다.
  • 유효하지 않은 characterId 요청은 DB write, S3 upload/delete, 외부 캐릭터 API 호출, 이벤트 발행 없이 실패해야 한다.
  • 다른 AI 캐릭터 또는 HUMAN creator 소유 resource ID는 조회/수정/삭제/연결/답변 모두 거부해야 한다.

Feature B. AI 캐릭터 관리

Requirements

  • 목록 조회, 검색, 상세 조회, 생성, 수정, 삭제 의미의 비활성화(isActive=false)를 제공한다.
  • 레거시 플랫폼 관리자와 중복 이름 검증, 외부 캐릭터 API 연동, 대표 이미지 저장, 원작 연결, 언어 감지/번역 이벤트, AI 캐릭터용 creatorMember 생성 및 표시 정보 동기화 동작 parity를 유지한다.
  • 삭제는 soft delete이며 row, 연결 Member, 콘텐츠를 물리 삭제하지 않는다.

Edge Cases

  • 중복 이름, 외부 캐릭터 API 실패, 이미지 저장 실패는 기존 관리자 동작을 특성화 테스트로 고정한 뒤 유지한다.
  • 비활성화 실패 시 일부 관계만 변경된 상태로 남기지 않는다.

Feature C. 오디오 콘텐츠 관리 및 signed URL

Requirements

  • 캐릭터 소유 콘텐츠 목록/검색/상세 조회, 생성, 수정, 기존 삭제 동작에 따른 soft delete를 제공한다.
  • 콘텐츠 생성 화면에서 사용할 활성 콘텐츠 테마(카테고리) 목록 조회를 제공한다. 기존 크리에이터 관리자 콘텐츠 등록 화면의 테마 조회와 같은 기능이며, 신규 v2 응답 필드는 themeId, themeName, imageUrl로 명확히 구분한다.
  • 기존 크리에이터 콘텐츠 관리의 검증, 파일 처리, content upload/processing pipeline, 가격, 공개/예약, 번역/알림 등 business behavior parity를 유지한다.
  • 관리자 화면 재생용 signed URL을 콘텐츠 목록/상세 응답에 제공한다.
  • signed URL은 공통 AudioContentCloudFront를 재사용하고 기존 크리에이터 관리자와 같은 만료 정책을 따른다.
  • 기존 signed URL 구현을 재사용하기 전에 creator admin 만료 계산식과 만료 계산·path 처리에서 실제로 관찰되는 edge case를 통과하는 특성화 테스트로 고정한다.
  • 응답에 private object path나 서명 키 정보를 노출하지 않는다.

Edge Cases

  • 콘텐츠 소유자가 target creatorMember와 다르면 조회/수정/삭제 모두 거부한다.
  • 커뮤니티 오디오의 기존 30분 signed URL 정책은 이 콘텐츠 재생 정책과 임의 통합하지 않는다.

Feature D. 시리즈 관리

Requirements

  • 목록/상세 조회, 생성, 수정, isActive=false soft delete를 제공한다.
  • 콘텐츠 연결/해제, 시리즈 콘텐츠 조회/검색, 순서 관리를 제공한다.
  • 기존 creator series 관리의 생성/수정/soft delete, 콘텐츠 연결/해제, 조회/검색, 순서 관리 behavior를 먼저 통과하는 특성화 테스트로 고정하고 신규 v2 경로에서 parity를 유지한다.
  • 시리즈와 연결 콘텐츠는 모두 동일한 creatorMember 소유여야 한다.
  • 기존 updateSeriesOrders(ids)처럼 소유권 없는 ID-only 갱신은 신규 v2 경로에서 허용하지 않는다.
  • 시리즈 콘텐츠 조회는 관리자 연결 작업을 위해 검색어 기반 필터를 제공한다.

Edge Cases

  • 순서 변경 요청의 모든 series/content ID는 target character 소유 검증을 통과해야 한다.
  • inactive series는 일반 활성 조회에서 제외한다.

Feature E. 커뮤니티 게시글 관리

Requirements

  • 등록, 수정, 공지 고정/해제(isFixed), 수정 요청의 isActive=false soft delete를 제공한다.
  • soft delete 시 현재 동작처럼 isFixed=false, fixedAt=null을 적용한다.
  • 기존 최대 고정 게시글 수 3개, 이미지/오디오/유료 게시글 검증, 알림/최근 소식 side effect를 유지한다.
  • 관리자 UI에 필요한 조회는 기존 v2 커뮤니티 조회 로직을 무비판적으로 복제하지 않고 신규 관리자 facade/endpoint에서 안전하게 재사용하거나 최소 query adapter를 둔다.

Edge Cases

  • 고정 게시글이 이미 3개인 상태에서 추가 고정은 기존 정책대로 실패한다.
  • soft delete된 고정 게시글은 고정 상태와 시간이 반드시 제거되어야 한다.

Feature F. FanTalk 답변

Requirements

  • 선택한 AI 캐릭터가 자신의 활성 root FanTalk에 creator reply를 작성한다.
  • 요청은 characterId와 대상 root fanTalkId를 포함한다.
  • 대상 FanTalk가 존재하고 활성 상태이며, 대상 creator가 해석된 creatorMember와 일치하는 root 글인지 검증한다.
  • 언어 감지와 기존 응답 DTO 의미 등 검증 가능한 business behavior를 유지하고, 저장된 답변의 writer/creator는 해석된 creatorMember와 일관되어야 한다.

Edge Cases

  • 다른 캐릭터의 FanTalk, reply에 대한 nested reply, 비활성 FanTalk, 미존재 FanTalk에는 답변하지 않는다.
  • 실패 시 reply 저장과 이벤트 발행이 없어야 한다.

8. API Expectations

  • 신규 endpoint prefix는 기존 공개 /api/v2/creator-channels/*와 legacy /admin/*, /creator-admin/*를 변경하지 않기 위해 /api/v2/admin/ai-characters를 기본안으로 한다.
  • 성공 응답은 ApiResponse.ok(...), API application/controller/security filter 오류는 오류 의미에 맞는 HTTP status와 ApiResponse.error(...)를 사용한다.
  • 이 API 오류 응답은 success=false와 현지화된 message를 포함하며 2xx로 normalize하지 않는다.
  • Accept-Language: ko|en|ja에 따라 KO/EN/JA 메시지를 반환하고, 없거나 지원하지 않는 언어는 KO로 fallback한다.
  • security filter 단계의 오류도 MVC interceptor에 의존하지 않고 Accept-Language를 직접 해석해 동일한 응답 계약을 따른다.
  • 신규 prefix는 캐릭터 관리자 frontend Origin http://localhost:8888, https://test-character-admin.sodalive.net, https://character-admin.sodalive.net만 허용한다.
  • 기존 범용 관리자 frontend와 creator frontend Origin을 캐릭터 관리자 Origin 대신 허용하지 않는다.
  • 공유 인증 경로 /admin/member/login, /member/logout는 기존 전역 Origin과 위 캐릭터 관리자 Origin의 합집합만 허용한다. 이 path-specific 확장은 다른 legacy/public 경로의 CORS 허용 범위를 변경하지 않는다.
  • 위 관리자 Origin의 신규 prefix 오류와 preflight는 404 fallback 및 실제 mapped endpoint의 405/406/415 경로를 포함해 기존 전역 CORS 응답 계약을 유지하며, 두 공유 인증 경로에서도 허용·거부 Origin을 검증한다.
  • 허용되지 않은 Origin, method 또는 header를 Spring CORS 계층에서 정책 거부하는 경우는 handler 진입 전 403으로 종료되는 브라우저 보안 경계다. 이 403의 body, content type, 현지화 및 ApiResponse.error envelope는 신규 API 오류 계약의 예외로 두고 외부 계약으로 고정하지 않는다.
  • 표준 HTTP method가 MVC까지 도달했지만 해당 mapping이 없으면 기존 Spring MVC의 405와 Allow header를 유지한다.
  • StrictHttpFirewall이 신규 prefix에서 비표준 HTTP method 또는 위험 URL을 RequestRejectedException으로 거부하면, 캐릭터 관리자 허용 Origin에는 CORS header를 포함한 400 common.error.invalid_request와 현지화된 ApiResponse.error를 반환한다. 허용되지 않은 Origin은 기존 Spring CORS 정책과 같이 body 계약 없는 403으로 종료한다.
  • SecurityConfig는 기존 AiCharacterAdminSecurityErrorHandler를 global RequestRejectedHandler로 등록하되 신규 prefix만 위 400/CORS 계약으로 처리하고, legacy/public은 DefaultRequestRejectedHandler에 위임해 기존 RequestRejectedException 동작을 유지한다. Spring 5.3의 비표준 method enum 한계 때문에 CORS 검사 request만 GET wrapper를 사용하며 실제 firewall method 허용 범위는 확장하지 않고 setUnsafeAllowAnyHttpMethod(true)도 사용하지 않는다.
  • Phase 1 공통 오류는 인증 정보 없음·잘못됨·만료·폐기 401 common.error.bad_credentials, JWT 또는 현재 DB role의 ADMIN 불충족 403 common.error.access_denied, request/target 미존재·불변식 위반 400, 신규 prefix 미매핑 경로 404, 지원하지 않는 HTTP method 405, 응답 media type 406, 요청 media type 415를 common.error.invalid_request로 고정한다. 405는 표준 Allow header를, 415는 표준 Accept header를 유지한다. controller mapping의 필수 path variable 선언이 누락된 MissingPathVariableException과 예상하지 못한 controller/JWT filter 오류는 500 common.error.unknown으로 고정한다.
  • malformed JSON의 HttpMessageNotReadableException, handler에 전달된 MethodArgumentNotValidException, multipart 필수 part 누락의 MissingServletRequestPartException은 각각 400 common.error.invalid_request와 KO/EN/JA ApiResponse.error를 반환한다.
  • 이후 phase의 domain/client/server 오류는 각 task에서 정확한 HTTP status와 KO/EN/JA message key를 먼저 정의하고 같은 envelope를 적용한다.
  • 신규 prefix 전용 오류 처리는 legacy/public endpoint의 기존 성공·오류 응답에 적용하지 않는다.
  • page 기반 조회는 기존 v2 탭 API 관례를 따라 page 기본값 0, size 기본값 20, 최소 20, 최대 50 보정을 기본안으로 하며, 경계값 보정은 구현 task와 테스트에 포함한다.
  • multipart 생성/수정은 기존 admin/creator-admin 관례대로 파일 part와 request JSON string part를 사용한다.
  • GET /api/v2/admin/ai-characters/audio-content-themes는 request body 없이 활성 콘텐츠 테마 목록을 반환한다. 성공 응답 data[{ "themeId": 11, "themeName": "ASMR", "imageUrl": "https://cdn.example.com/audio-content-theme/asmr.png" }] 형태이며, 기존 내부/legacy DTO의 id, theme, image 필드명을 외부 계약으로 노출하지 않는다.
  • 오디오 콘텐츠 생성 request JSON에는 콘텐츠 테마 선택값인 themeId와 기존 CreateAudioContentRequest의 생성 필드 전체를 포함한다. v2는 detail 대신 description, releaseDate 대신 UTC ISO-8601 releaseDateUtc를 외부 계약으로 사용하고 legacy pipeline 호출 시 변환한다.
  • 오디오 콘텐츠 목록 응답은 현 v2 관리자 목록 계약을 유지한다. 상세 응답은 기존 GetAudioContentDetailResponse의 필드 전체를 v2 상세 DTO에 포함하되, 구매·좋아요·핀·추천·댓글 목록처럼 viewer 상태가 필요한 필드는 관리자 상세에서 안전한 기본값을 반환한다.
  • request/response DTO는 신규 v2 AI character admin API 전용 DTO로 두고 legacy/public DTO를 외부 계약으로 재노출하지 않는다.

9. Technical Constraints

  • Kotlin, Java 17, Spring Boot 2.7.14, Gradle Wrapper를 유지한다.
  • 신규 dependency를 추가하지 않는다.
  • 신규 DB schema/DDL을 만들지 않는다.
  • 기존 v2 API 조립 계층과 domain/application 의존 방향을 따른다.
  • controller 내부 호출, 서버 내부 legacy HTTP 호출, 기존 controller 역참조는 하지 않는다.
  • 신규 v2 application/domain 계층은 기존 controller와 v2 API response DTO를 역참조하지 않는다.
  • 기존 business method를 재사용하기 전 특성화/회귀 테스트를 작성한다.
  • 특성화/회귀 테스트는 신규 v2 use-case의 미구현 RED 테스트와 분리하고, 기존 legacy/creator-admin 구현을 대상으로 먼저 통과해야 한다.
  • 단순 복사-붙여넣기 대신 필요한 최소 추출 또는 v2 use-case 재개발을 선택한다.

10. Metrics

  • 신규 endpoint별 또는 controller slice별 JWT role × 현재 DB role 인가 매트릭스와 stale ADMIN claim 403 테스트 존재 여부
  • 신규 prefix의 각 API 오류 분기에 정확한 HTTP status, ApiResponse.error, KO/EN/JA와 405 Allow/415 Accept header 테스트 존재 여부
  • 신규 prefix 실제 mapped endpoint 및 공유 인증 경로의 허용·거부 Origin/preflight 테스트 존재 여부
  • 신규 prefix의 표준 method 미매핑 405 Allow 유지와 RequestRejectedException 400/i18n/ApiResponse.error/허용 Origin CORS header, 미허용 Origin body 계약 없는 403 테스트 존재 여부
  • legacy/public firewall 동작 불변 및 setUnsafeAllowAnyHttpMethod(true) 미사용 확인 여부
  • core controller security/error 계약의 production @SpringBootTest full-context 실행과 Redis token fixture cleanup 확인 여부
  • target resolver 조회 직후 creatorMember 초기화와 fetch join 제거 시 실패하는 non-vacuous 회귀 테스트 존재 여부
  • HttpMessageNotReadableException, MethodArgumentNotValidException, MissingServletRequestPartException의 exact exception type과 KO/EN/JA 400 envelope 직접 검증 여부
  • target/ownership 실패 시 no-side-effect 테스트 존재 여부
  • character/content/series/community/FanTalk slice별 targeted test 통과 여부
  • signed URL TTL 계산식·edge case parity 및 private path 비노출 테스트 통과 여부
  • 기존 legacy/public endpoint의 성공·오류 status/body/message 회귀 테스트 통과 여부

11. Acceptance Criteria

  • JWT ROLE_ADMIN과 현재 DB Member.role == ADMIN을 모두 만족하는 요청만 유효한 AI character 대상으로 신규 endpoint를 호출할 수 있다.
  • 비로그인 또는 잘못된 JWT 요청은 401이고, JWT 비ADMIN + DB ADMIN과 JWT ADMIN + DB 비ADMIN stale claim은 모두 403이다.
  • 신규 prefix의 API application/controller/security filter 오류는 정확한 비2xx status, ApiResponse.error, Accept-Language에 따른 KO/EN/JA message를 반환한다. Spring CORS 계층의 정책 거부 403 body는 이 envelope 계약의 예외다.
  • 지원하지 않는 HTTP method는 405와 Allow header, 응답 media type은 406, 요청 media type은 415와 Accept header를 반환하고, MissingPathVariableException은 500 common.error.unknown을 반환한다.
  • 표준 HTTP method가 MVC에 도달한 뒤 mapping이 없을 때는 기존 405와 Allow header를 유지한다. 신규 prefix의 비표준 HTTP method 또는 위험 URL이 StrictHttpFirewall에서 RequestRejectedException으로 거부되면 허용된 캐릭터 관리자 Origin에는 CORS header와 현지화된 400 common.error.invalid_request ApiResponse.error를, 미허용 Origin에는 body 계약 없는 403을 반환한다. legacy/public firewall 동작은 변하지 않고 setUnsafeAllowAnyHttpMethod(true)는 사용하지 않는다.
  • 신규 prefix는 캐릭터 관리자 Origin만 허용하고, /admin/member/login, /member/logout는 기존 전역 Origin과 캐릭터 관리자 Origin의 합집합을 허용한다. 실제 mapped endpoint와 공유 인증 경로의 CORS 허용·거부가 테스트로 고정된다.
  • core controller security/error 계약은 production @SpringBootTest full context에서 검증하고 Redis token fixture를 테스트 후 정리해 다음 테스트에 남기지 않는다.
  • target resolver의 repository 조회 결과는 반환 직후 creatorMember가 초기화되어 있어야 하며, fetch join 제거 시 실패하는 회귀 테스트로 고정한다.
  • malformed JSON, handler에 전달된 MethodArgumentNotValidException, multipart 필수 part 누락은 각각 정확한 MVC exception type과 현지화된 400 ApiResponse.error 계약을 만족한다.
  • character 미존재, creatorMember 미존재, role 불일치, memberKind 불일치 요청은 4xx이며 아무 side effect도 남기지 않는다.
  • 다른 character 소유 resource ID를 사용한 조회/수정/삭제/연결/답변은 4xx로 거부된다.
  • 캐릭터 생성/수정/비활성화는 레거시 관리자 behavior parity를 유지한다.
  • 콘텐츠 생성/수정/soft delete와 signed URL 응답은 기존 creator/admin behavior parity를 유지한다.
  • 시리즈 CRUD, 콘텐츠 연결/해제/순서 변경은 target character 소유 범위를 벗어나지 않는다.
  • 커뮤니티 생성/수정/고정/해제/soft delete는 target creatorMember 소유 게시글에만 적용된다.
  • FanTalk 답변은 target AI character 자신의 활성 root FanTalk에만 저장된다.
  • 기존 legacy/public endpoint 테스트가 통과하고, 두 공유 인증 경로의 CORS 허용 Origin 확장 외 성공·오류 status/body/message를 포함한 request/response contract가 변경되지 않는다.
  • 신규 dependency, 신규 DDL, 관련 없는 리팩터링이 없다.

12. Open Questions

  • 없음.