카탈로그 데이터
직접 라우트로 시작하기
섹션 제목: “직접 라우트로 시작하기”일반 애플리케이션 읽기에는 현재 데이터용 짧은 URL을 쓰세요.
GET /api/v1/{resource}GET /api/v1/{resource}/{id}GET /api/v1/{resource}?server=jpGET /api/v1/{resource}/{id}?server=jp기본 서버는 intl입니다. server 쿼리로 다른 활성 서버를 고릅니다. 이 라우트는 /api/v1/servers/{server}/{resource}와 같은 카탈로그 처리기를 씁니다. 명시적 형태는 release 매개변수도 필요한 빌드·아카이브 도구용입니다.
자주 쓰는 리소스 이름으로는 songs, bands, characters, cards, stories, events, audio, videos, voices가 있습니다. 선택한 카탈로그가 기준입니다. 고정된 목록을 두지 말고 카탈로그의 리소스 이름, ID, 뷰, 관계를 쓰세요.
리소스 색인과 엔티티
섹션 제목: “리소스 색인과 엔티티”직접 해 보세요. 곡과 서버를 고릅니다. ID는 색인에 있는 아무 musicId로 바꿀 수 있습니다.
/api/v1/songs/{id} 색인 요청은 프로바이더 형태의 문서를 반환합니다. 컬렉션 리소스는 보통 엔티티 ID를 키로 하는 객체를 반환합니다.
curl --fail-with-body https://haneoka.org/api/v1/songs{ "100001": { "musicId": 100001, "musicTitle": ["迷星叫", "Mayoiuta", "迷星叫", "迷星叫", "헤매는 노래"], "bandId": 1, "difficulty": [ { "difficulty": 0, "difficultyName": "easy", "displayLevel": 9, "noteCount": 342 } ], "jacketUrl": "/assets/intl/Assets/AddressableResources/Image/Jacket/jkt_001_100001.png", "musicUrl": "/runtime/intl/cri/sound/musicscore/M_Mayoiuta/1_M_Mayoiuta.mp3" }}키로 전체 엔티티 읽기:
curl --fail-with-body https://haneoka.org/api/v1/songs/100001엔티티에는 언어별 크레딧, 공개일, 콤보 보상, 채보 경로, 동영상 ID처럼 색인 항목보다 많은 필드가 들어 있을 수 있습니다. 알 수 없는 필드는 보존하고, null은 원본에 값이 없다는 명시로 다루세요. 다른 리소스는 각자의 형태를 가집니다. 예를 들어 이벤트 문서는 곡 필드 대신 entries와 hasGameEvents를 씁니다.
선택적 페이지 나누기
섹션 제목: “선택적 페이지 나누기”컬렉션이나 선언된 뷰에서는 limit을 붙이면 전체 색인 대신 엔벌로프를 반환합니다.
GET /api/v1/songs?server=intl&limit=20GET /api/v1/{resource}/views/{view}?server=intl&limit=20{ "items": [{ "id": "100070", "value": { "musicId": 100070 } }], "total": 84, "limit": 20, "nextCursor": "opaque-cursor-from-the-response", "release": { "schema": "haneoka-resource-release-identity-v1", "server": "intl", "releaseId": "r-0123456789abcdef0123", "sourceId": "source-snapshot-id" }}이 축약 예는 엔벌로프를 보여 주며, 엔티티 필드와 개수는 선택한 카탈로그에서 나옵니다. limit은 1~100이며, limit 없이 커서로 페이지 나누기가 켜지면 기본값은 50입니다. 요청 하나가 훑는 엔티티 샤드는 최대 8개이므로 한 페이지가 limit보다 적을 수 있고, 이어지는 커서가 있는 빈 페이지도 나올 수 있습니다. nextCursor === null일 때만 끝내세요. 순서는 샤드 순서와 ID 사전순이며, 게임 순서나 ID 숫자 순서가 아닙니다.
커서는 URLSearchParams로 그대로 넘기세요. 커서는 현재 포인터가 갱신되어도 첫 페이지의 릴리스를 유지하며 서버, 리소스, 뷰에 묶입니다. 다음 페이지도 같은 범위로 요청하세요. 형식이 잘못되었거나 맞지 않는 커서는 400 invalid_cursor, 유지한 릴리스를 쓸 수 없으면 404 release_not_found를 반환합니다. 그 스냅숏을 더는 쓸 수 없으면 새 첫 페이지부터 다시 시작하세요. 페이지 나누기는 엔티티 스토리지가 있는 컬렉션·뷰만 지원합니다. limit과 cursor를 모두 생략하면 기존처럼 전체 색인 응답을 받습니다. 배치 id와 페이지 나누기는 함께 쓸 수 없습니다.
const scope = { server: "intl", limit: "20" };let cursor;do { const query = new URLSearchParams(scope); if (cursor) query.set("cursor", cursor); const response = await fetch(`https://haneoka.org/api/v1/songs?${query}`); if (!response.ok) throw new Error(`Catalog HTTP ${response.status}`); const page = await response.json(); for (const { id, value } of page.items) console.log(id, value); cursor = page.nextCursor;} while (cursor !== null);타입 클라이언트는 엔벌로프를 검증하고 엔티티 DTO용 디코더를 지원합니다.
배치 읽기
섹션 제목: “배치 읽기”엔티티 몇 개를 가져오려면 id를 반복합니다:
/api/v1/songs?id=100001&id=100002 curl --fail-with-body \ 'https://haneoka.org/api/v1/songs?id=100001&id=100002&server=jp'응답:
{ "items": { "100001": { "musicId": 100001, "musicTitle": ["迷星叫", "Mayoiuta"] } }, "missing": ["100002"]}items는 요청한 ID를 키로 합니다. missing은 엔티티가 없는 ID 목록입니다. id 매개변수는 1~100개 보낼 수 있으며, 상한은 중복 제거 전 보낸 개수로 셉니다. API는 캐시 안정성을 위해 ID를 정렬하고 중복을 없앱니다.
뷰와 관계
섹션 제목: “뷰와 관계”카탈로그 매니페스트는 리소스마다 선택적인 뷰와 관계를 선언합니다. 뷰는 이름이 있는 프로젝션으로, 자체 색인과 엔티티 형태를 가집니다.
GET /api/v1/{resource}/views/{view}GET /api/v1/{resource}/views/{view}/{id}GET /api/v1/{resource}/views/{view}?id={id}&id={id}관계는 리소스, 관계 이름, 키로 가리킵니다:
GET /api/v1/{resource}/relations/{relation}/{key}매니페스트의 valueMode가 결과를 설명합니다. ids 관계는 ID를 키로 하는 정식 엔티티로 펼쳐집니다. records 관계는 관계 문서의 프로바이더 레코드를 반환합니다. 관계는 있지만 값이 없으면 빈 관계 키는 {}를 반환합니다.
이 이름들이 필요하면 선택한 서버의 매니페스트를 쓰세요.
/api/v1/servers/{server}/catalog curl --fail-with-body https://haneoka.org/api/v1/servers/intl/catalog매니페스트는 리소스 kind, 개수, 색인 경로, 엔티티 저장소, 뷰 경로와 형태, 관계 이름, 의존성을 설명합니다. 저장소 필드는 크롤러에 유용하며, 애플리케이션 코드는 직접 라우트를 그대로 써도 됩니다.
클라이언트에서 중요한 필드 의미
섹션 제목: “클라이언트에서 중요한 필드 의미”| 필드 패턴 | 쓰는 법 |
|---|---|
musicTitle, bandName 같은 언어별 배열 | 문서화된 언어 위치를 고르고, 값이 비면 대체한다. 나중에 언어를 바꿀 수 있게 배열은 보존한다. |
*Url과 file 경로 | https://haneoka.org 기준으로 해석한다. 바이너리가 필요하면 그 경로를 해당 미디어나 게임 클라이언트 라우트로 보낸다. |
| 난이도 레코드 | difficultyName, displayLevel, playLevel, sortLevel, noteCount를 각각의 값으로 읽는다. 채보 file 경로는 보존한다. |
musicId, bandId, videoIds 같은 ID | 원래 숫자·문자열 타입을 유지한다. ID는 리소스마다 다르다. |
null 값 | 원본에 현재 값이 없다. null을 빈 문자열로 바꾸지 않는다. |
고급 명시적 형태
섹션 제목: “고급 명시적 형태”서버 단위 요청에는 다음을 씁니다:
GET /api/v1/servers/{server}/{resource}GET /api/v1/servers/{server}/{resource}/{id}release=r-<소문자 16진수 20자>는 재현 가능한 빌드나 아카이브에 불변 카탈로그 하나가 필요할 때만 붙이세요. 릴리스, 소스, 미디어, 스토리지는 서버 단위 고급 계약에서 설명합니다.