fromis_9/docs/api.md
caadiq aa6c05e6b5 docs: 활동 로그 시스템 문서 업데이트
api.md에 GET /admin/logs 명세 추가, architecture.md에
logs 테이블/파일 추가, development.md에 로그 시스템 가이드 추가,
logs.md를 실제 구현 결과에 맞게 갱신.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 17:08:35 +09:00

12 KiB

API 명세

Base URL: /api

인증

POST /auth/login

로그인 (JWT 토큰 발급)

Rate Limit: 1분당 5회 (IP 기준)

GET /auth/verify

토큰 검증 및 사용자 정보 (인증 필요)


멤버

GET /members

멤버 목록 조회

GET /members/:name

멤버 상세 조회

Parameters:

  • name - 멤버 이름 (한글 또는 영문, 대소문자 무관)

예시:

  • /members/박지원 - 한글명으로 조회
  • /members/jiwon - 영문명으로 조회

앨범

GET /albums

앨범 목록 조회

GET /albums/:id

앨범 상세 조회


일정

GET /schedules

일정 조회

Query Parameters:

  • year, month - 월별 조회
  • startDate - 시작 날짜 (YYYY-MM-DD), 다가오는 일정 조회
  • search - 검색어 (Meilisearch 사용)
  • offset, limit - 페이징

search, startDate, year/month 중 하나는 필수

월별 조회 응답:

{
  "schedules": [
    {
      "id": 123,
      "title": "...",
      "date": "2026-01-18",
      "time": "19:00:00",
      "category": { "id": 2, "name": "유튜브", "color": "#ff0033" },
      "source": {
        "name": "fromis_9",
        "url": "https://www.youtube.com/watch?v=VIDEO_ID"
      },
      "members": ["송하영"]
    }
  ]
}

특수 일정 ID 형식:

  • 생일: birthday-{year}-{nameEn} (예: birthday-2026-jiwon)
  • 데뷔: debut-{year} (예: debut-2018)
  • 주년: anniversary-{year} (예: anniversary-2026) ※ time: 시간이 없는 일정은 null, 00:00 시간은 "00:00:00"으로 반환

**source 객체 (카테고리별):**
- YouTube (category_id=2): `{ name: "채널명", url: "https://www.youtube.com/..." }`
- X (category_id=3): `{ name: "", url: "https://x.com/realfromis_9/status/..." }` (name 빈 문자열)
- 기타 카테고리: source 없음

**다가오는 일정 응답 (startDate):**
```json
{
  "schedules": [
    {
      "id": 123,
      "title": "...",
      "date": "2026-01-18",
      "time": "19:00:00",
      "category": { "id": 2, "name": "유튜브", "color": "#ff0033" },
      "source": { "name": "fromis_9", "url": "https://..." },
      "members": ["송하영"]
    }
  ]
}

※ 현재 활동 멤버 전원인 경우 ["프로미스나인"] 반환 (탈퇴 멤버 제외) ※ time: 시간이 없는 일정은 null, 00:00 시간은 "00:00:00"으로 반환

검색 응답:

{
  "schedules": [
    {
      "id": 123,
      "title": "...",
      "date": "2026-01-18",
      "time": "19:00:00",
      "category": { "id": 2, "name": "유튜브", "color": "#ff0033" },
      "source": { "name": "fromis_9", "url": "https://..." },
      "members": ["송하영"],
      "_rankingScore": 0.95
    }
  ],
  "total": 100,
  "offset": 0,
  "limit": 20,
  "hasMore": true
}

time: 시간이 없는 일정은 null, 00:00 시간은 "00:00:00"으로 반환


### GET /schedules/categories
카테고리 목록 조회

**응답:**
```json
[
  { "id": 1, "name": "기타", "color": "#gray", "sort_order": 0 },
  { "id": 2, "name": "유튜브", "color": "#ff0033", "sort_order": 1 }
]

GET /schedules/:id

일정 상세 조회

DELETE /schedules/:id

일정 삭제 (인증 필요)

POST /schedules/sync-search

Meilisearch 전체 동기화 (인증 필요)


추천 검색어

GET /schedules/suggestions

추천 검색어 조회

Query Parameters:

  • q - 검색어 (2자 이상)
  • limit - 결과 개수 (기본 10)

응답:

{
  "suggestions": ["송하영", "송하영 직캠", "하영"]
}

인기 검색어 조회

Query Parameters:

  • limit - 결과 개수 (기본 10)

응답:

{
  "queries": ["프로미스나인", "송하영", "이서연"]
}

POST /schedules/suggestions/save

검색어 저장 (검색 실행 시 호출)

Request Body:

{
  "query": "검색어"
}

GET /schedules/suggestions/dict

사용자 사전 조회 (인증 필요)

응답:

{
  "content": "프로미스나인\t프로미스나인\tNNP\n..."
}

PUT /schedules/suggestions/dict

사용자 사전 저장 (인증 필요)

Request Body:

{
  "content": "프로미스나인\t프로미스나인\tNNP\n..."
}

관리자 - 봇 관리 (인증 필요)

GET /admin/bots

봇 목록 조회

응답:

[
  {
    "id": "youtube-fromis9",
    "name": "fromis_9",
    "type": "youtube",
    "status": "running",
    "last_check_at": "2026-01-18T19:30:00+09:00",
    "last_added_count": 2,
    "last_sync_duration": 1234,
    "schedules_added": 150,
    "check_interval": 2,
    "error_message": null,
    "enabled": true
  },
  {
    "id": "meilisearch-sync",
    "name": "Meilisearch 동기화",
    "type": "meilisearch",
    "status": "running",
    "last_check_at": "2026-01-18T04:00:00+09:00",
    "last_added_count": 500,
    "last_sync_duration": 2500,
    "schedules_added": 500,
    "check_interval": 0,
    "error_message": null,
    "enabled": true,
    "version": "1.6.0"
  }
]

필드 설명:

  • last_check_at: 마지막 동기화 시간 (KST, +09:00)
  • last_sync_duration: 마지막 동기화 소요 시간 (ms)
  • version: Meilisearch 버전 (meilisearch 타입만)

POST /admin/bots/:id/start

봇 시작

POST /admin/bots/:id/stop

봇 정지

POST /admin/bots/:id/sync-all

전체 동기화 (모든 영상/트윗 수집)

응답:

{
  "success": true,
  "addedCount": 25,
  "total": 100
}

GET /admin/bots/quota-warning

YouTube API 할당량 경고 조회

응답:

{
  "active": true,
  "message": "YouTube API 할당량 초과",
  "timestamp": "2026-01-18T19:00:00+09:00"
}

DELETE /admin/bots/quota-warning

할당량 경고 해제


관리자 - YouTube 봇 (인증 필요)

POST /admin/youtube-bots/lookup

채널 핸들로 채널 정보 조회

Request Body:

{
  "handle": "@studiofromis_9"
}

응답:

{
  "channelId": "UCxxx",
  "title": "채널명",
  "thumbnailUrl": "https://...",
  "bannerUrl": "https://..."
}

GET /admin/youtube-bots

YouTube 봇 목록 조회

GET /admin/youtube-bots/:id

YouTube 봇 상세 조회

POST /admin/youtube-bots

YouTube 봇 추가

Request Body:

{
  "channel_id": "UCxxx",
  "channel_handle": "@studiofromis_9",
  "channel_name": "채널명",
  "cron_interval": 2,
  "title_filters": ["fromis_9", "프로미스나인"],
  "default_member_ids": [1, 2],
  "extract_members_from_desc": true,
  "auto_schedule_config": {
    "dayOfWeek": 4,
    "time": "18:00:00",
    "titleTemplate": "{channelName} {episode}화",
    "deadlineDayOfWeek": 5
  }
}

PUT /admin/youtube-bots/:id

YouTube 봇 수정

DELETE /admin/youtube-bots/:id

YouTube 봇 삭제


관리자 - X 봇 (인증 필요)

POST /admin/x-bots/lookup

X username으로 프로필 정보 조회 (Nitter 사용)

Request Body:

{
  "username": "realfromis_9"
}

응답:

{
  "username": "realfromis_9",
  "displayName": "프로미스나인 (fromis_9)",
  "avatarUrl": "https://..."
}

GET /admin/x-bots

X 봇 목록 조회

응답: XBot[]

GET /admin/x-bots/:id

X 봇 상세 조회

응답:

{
  "id": 1,
  "username": "realfromis_9",
  "display_name": "프로미스나인 (fromis_9)",
  "avatar_url": "https://...",
  "text_filters": ["fromis", "프로미스"],
  "include_retweets": false,
  "extract_youtube": true,
  "cron_interval": 1,
  "enabled": true
}

POST /admin/x-bots

X 봇 추가

Request Body:

{
  "username": "realfromis_9",
  "display_name": "프로미스나인 (fromis_9)",
  "avatar_url": "https://...",
  "text_filters": ["fromis"],
  "include_retweets": false,
  "extract_youtube": false,
  "cron_interval": 1
}
필드 타입 기본값 설명
username string (필수) X username (@ 없이)
display_name string|null null 표시 이름
avatar_url string|null null 프로필 이미지 URL
text_filters string[]|null null 텍스트 필터 (하나라도 포함 시 추가, 비어있으면 모든 트윗)
include_retweets boolean false 리트윗 포함 여부
extract_youtube boolean false 트윗 내 YouTube 링크 자동 추출하여 유튜브 일정 추가
cron_interval integer 1 동기화 간격 (분)

PUT /admin/x-bots/:id

X 봇 수정 (부분 업데이트 가능)

DELETE /admin/x-bots/:id

X 봇 삭제


관리자 - YouTube (인증 필요)

GET /admin/youtube/video-info

YouTube 영상 정보 조회

Query Parameters:

  • url - YouTube URL (watch, shorts, youtu.be 모두 지원)

응답:

{
  "videoId": "abc123",
  "title": "영상 제목",
  "channelId": "UCxxx",
  "channelName": "채널명",
  "date": "2026-01-19",
  "time": "15:00:00",
  "videoType": "video",
  "videoUrl": "https://www.youtube.com/watch?v=abc123"
}

POST /admin/youtube/schedule

YouTube 일정 저장

Request Body:

{
  "videoId": "abc123",
  "title": "영상 제목",
  "channelId": "UCxxx",
  "channelName": "채널명",
  "date": "2026-01-19",
  "time": "15:00:00",
  "videoType": "video"
}

PUT /admin/youtube/schedule/:id

YouTube 일정 수정 (멤버, 영상 유형)

Request Body:

{
  "memberIds": [1, 2, 3],
  "videoType": "video"
}

videoType: "video" 또는 "shorts"


관리자 - X (인증 필요)

GET /admin/x/post-info

X 게시글 정보 조회 (Nitter 스크래핑)

Query Parameters:

  • postId - 게시글 ID (필수)
  • username - 사용자명 (기본: realfromis_9)

응답:

{
  "postId": "1234567890",
  "username": "realfromis_9",
  "text": "게시글 전체 내용",
  "title": "첫 문단 (자동 추출)",
  "imageUrls": ["https://pbs.twimg.com/media/..."],
  "date": "2026-01-19",
  "time": "15:00:00",
  "postUrl": "https://x.com/realfromis_9/status/1234567890",
  "profile": {
    "displayName": "프로미스나인 (fromis_9)",
    "avatarUrl": "https://..."
  }
}

POST /admin/x/schedule

X 일정 저장

Request Body:

{
  "postId": "1234567890",
  "title": "게시글 제목",
  "content": "게시글 내용",
  "imageUrls": ["https://..."],
  "date": "2026-01-19",
  "time": "15:00:00"
}

관리자 - 활동 로그 (인증 필요)

GET /admin/logs

활동 로그 목록 조회

Query Parameters:

  • page - 페이지 번호 (기본 1)
  • limit - 페이지당 개수 (기본 50, 최대 100)
  • category - 카테고리 필터 (콤마 구분: album, schedule, member, bot, category, dict, concert, sync)
  • actor - 행위자 필터 (admin 또는 bot)
  • search - summary 텍스트 검색
  • from - 시작 날짜 (YYYY-MM-DD)
  • to - 종료 날짜 (YYYY-MM-DD)

응답:

{
  "logs": [
    {
      "id": 1,
      "actor": "admin",
      "action": "create",
      "category": "album",
      "target_type": "album",
      "target_id": 12,
      "summary": "앨범 생성: Unlock My World",
      "details": null,
      "created_at": "2026-03-02 14:30:00"
    }
  ],
  "total": 150,
  "page": 1,
  "limit": 50,
  "totalPages": 3
}

actor 값:

  • "admin" - 관리자 수동 작업
  • "youtube-{id}" - YouTube 봇 (예: youtube-3)
  • "x-{id}" - X 봇 (예: x-1)

action 값:

  • create, update, delete, upload - CRUD 작업
  • start, stop - 봇 시작/정지
  • sync_complete - 봇 동기화 완료
  • error - 봇 동기화 에러

헬스 체크

GET /health

서버 상태 확인


API 문서

GET /docs

Scalar API Reference UI

GET /docs/json

OpenAPI JSON 스펙