문서 / 기능 붙이기
이 문서의 큐
Catalogs
카탈로그는 사이트가 소유하는 이름 붙은 목록입니다. 꽃 도감, 배지 세트, 대여 장비 목록처럼 항목 하나하나에 의미가 있고, 나중에 다른 기록이 그 항목을 가리키게 되는 자료가 카탈로그입니다.
회원이 "무엇을 가졌는지"(보유)를 기록하거나 그룹이 서로에게 표식을 남길 때, 그 대상이 되는 것이 카탈로그 항목입니다.
컬렉션과 무엇이 다른가
겉모습이 비슷해 헷갈리기 쉽습니다. 둘 다 컨테이너가 필드 스키마를 들고 항목이 자유로운 값을 담습니다. 그런데 규칙이 반대라서, 잘못 고르면 나중에 데이터를 옮겨야 합니다.
| 카탈로그 | 컬렉션(ContentType) | |
|---|---|---|
| 항목의 이름 | 유일해야 한다 (같은 이름의 활성 항목 둘은 불가) | 제약 없음 — 같은 제목 여럿 정상 |
| 삭제 | 없다. 은퇴(retire)만 있고 ID 는 보존된다 | 하드 삭제가 정상 동작 |
| 다른 기록의 참조 | 보유·표식이 항목 ID 를 가리킨다 | 참조 관계 없음 |
| 쓰기 주체 | 정책으로 고른다 (운영자·회원·그룹원·임원) | 운영자 |
고르는 기준 한 줄: 다른 기록이 그 항목을 ID 로 가리키고, 항목이 사라진 뒤에도 그 기록이 남아야 하면 카탈로그입니다. 블로그 글이나 메뉴판처럼 그 자체로 완결되는 자료는 컬렉션입니다.
만들기
카탈로그 정의는 사이트 운영자가 합니다. CLI 또는 MCP 로 하며, 둘은 같은 백엔드를 씁니다.
npx @saeroon/cli catalog apply ./catalogs.json --site <siteId>
{
"catalogs": [
{
"key": "flowers",
"name": "꽃 도감",
"itemEditPolicy": "GroupMember",
"itemRetirePolicy": "GroupLeader",
"attributes": [
{ "key": "grade", "type": "Integer", "label": "등급", "min": 1, "max": 5 },
{ "key": "season", "type": "Select", "options": ["봄", "여름", "가을", "겨울"] }
]
}
]
}
key 는 사이트 안에서 유일하며 한 번 정하면 바꿀 수 없습니다. 항목과 보유가 이 키로 카탈로그를 찾기 때문입니다.
고객의 AI 에이전트를 쓴다면 같은 일을 MCP 로 합니다.
saeroon_define_catalog { "key": "flowers", "name": "꽃 도감",
"itemEditPolicy": "GroupMember",
"attributes": [{ "key": "grade", "type": "Integer", "min": 1, "max": 5 }],
"items": [{ "name": "장미", "attributes": { "grade": 3 } }] }
속성 스키마
attributes 는 항목이 담을 수 있는 값의 형태를 정합니다. 타입은 여덟 가지입니다.
Text · Integer · Number · Boolean · Date · Select · MultiSelect · Url
Select 와 MultiSelect 는 options 가 반드시 있어야 합니다. 선택지 없는 선택형은 어떤 값이든 통과시켜서, 제약을 걸어 둔 줄 알았는데 실제로는 아무것도 막지 않는 상태가 됩니다.
attributes 를 생략하는 것과 빈 배열을 보내는 것은 다릅니다.
- 키를 아예 안 넣으면: 기존 스키마를 건드리지 않습니다.
"attributes": []를 보내면: 스키마를 비웁니다.
이름만 고치려던 요청이 스키마를 날리지 않도록 둘을 구분해 두었습니다. 이름만 바꿀 때는 attributes 키를 빼세요.
누가 항목을 쓸 수 있나
itemEditPolicy(추가·수정)와 itemRetirePolicy(은퇴)를 따로 정합니다.
| 값 | 뜻 |
|---|---|
OperatorOnly | 사이트 소유자와 운영자 키만. 기본값입니다 — 명시하지 않으면 회원은 쓸 수 없습니다 |
SiteMember | 로그인한 사이트 회원 누구나 |
GroupMember | 활성 그룹의 활성 멤버 (직위 무관) |
GroupLeader | 그중 임원 직위 보유자 또는 권한대행 |
GroupMember 와 GroupLeader 는 그룹 정책이 먼저 있어야 의미가 생깁니다.
두 정책을 따로 두는 이유는 되돌리기 비용이 다르기 때문입니다. 항목을 더하는 것은 틀려도 고치면 그만이지만, 은퇴는 그 항목을 가리키던 보유와 표식이 함께 화면에서 빠지는 동작이라 보통 편집보다 좁게 겁니다.
회원이 항목을 쓰는 경로
itemEditPolicy 를 OperatorOnly 밖으로 열었다면, 그 권한을 실제로 쓰는 것은 회원 세션입니다. 운영자 CLI 가 아니라 아래 REST 를 부릅니다.
https://api.saeroon.com/api/v1/hosting/public/sites/{siteId}
| 메서드 | 경로 | 하는 일 | 정책 |
|---|---|---|---|
POST | /catalogs/{catalogKey}/items | 항목 추가 | itemEditPolicy |
PUT | /catalogs/{catalogKey}/items/{itemId} | 항목 수정 (전체 교체) | itemEditPolicy |
DELETE | /catalogs/{catalogKey}/items/{itemId} | 은퇴 (삭제 아님) | itemRetirePolicy |
GET | /catalogs/{catalogKey}/items | 목록 (공개 읽기) | — |
GET | /catalogs/{catalogKey}/items/{itemId} | 단건 (공개 읽기) | — |
인증은 Authorization: Bearer samt_… 또는 세션 쿠키입니다. 규약은 Groups §인증 과 같습니다.
쓰기 본문과 응답은 이렇습니다.
// POST · PUT 본문
{ "name": "장미", "attributes": { "grade": 3, "season": "여름" }, "sortOrder": 1 }
// 응답 (셋 다 CatalogItemView 를 돌려줍니다)
{ "id", "catalogId", "catalogKey", "name", "attributes",
"sortOrder", "isRetired", "createdAt", "updatedAt" }
PUT 은 전체 교체입니다 — 이름만 바꾸려고 attributes 를 빼면 그 속성이 비워집니다.
🔴 DELETE 는 지우지 않습니다. 은퇴 처리를 하고 200 과 함께 isRetired: true 인 항목을 돌려줍니다. 204 를 기대하고 본문을 안 읽는 코드는 그 자리에서 어긋납니다. 하드 삭제는 이 도메인에 없습니다.
되살리기(restore)는 회원 평면에 없습니다. 운영자 경로(catalog restore)로만 됩니다 — 다만 아래 「삭제 대신 은퇴」의 이름 재사용 규칙이 사실상 그 일을 합니다.
기본값이 OperatorOnly 라 아무것도 정하지 않으면 이 경로는 회원에게 403 입니다. 열어 두지 않은 것이 기본이고, 여는 것이 명시적 선택입니다.
🔴 읽기와 쓰기의 에러 코드가 다릅니다
같은 「없는 카탈로그」인데 어느 경로로 부딪혔느냐에 따라 다른 코드가 옵니다. 읽기에서 본 코드로 쓰기 화면을 분기하면 그 가지가 영영 안 탑니다.
| 상태 | 읽기(GET) | 쓰기(POST·PUT·DELETE) |
|---|---|---|
| 401 세션 없음 | — (공개 읽기) | AUTH_REQUIRED |
| 403 정책이 안 열림 | — | FORBIDDEN |
| 404 없는 카탈로그 | CATALOG_NOT_FOUND | NOT_FOUND |
| 404 없는 항목 | CATALOG_ITEM_NOT_FOUND | NOT_FOUND |
| 409 같은 이름의 활성 항목 | — | CONFLICT |
쓰기 쪽 셋이 뭉뚱그려진 것은 도메인 예외의 기본 코드가 그대로 나가기 때문입니다. 지금은 message 로만 구별되니, 쓰기 실패를 사용자에게 설명해야 한다면 상태 코드로 가르고 문구는 서버 것을 그대로 쓰는 편이 안전합니다.
읽기는 분당 120회, 쓰기는 분당 10회(IP 기준)입니다.
목록 조회는 ETag 를 실어 주므로 If-None-Match 로 304 를 받을 수 있습니다. 도감처럼 크고 잘 안 변하는 목록을 매 화면에서 부를 때 씁니다.
이름으로 찾기
GET /catalogs/{catalogKey}/items?q=사랑의화환
🔑 검색은 이름을 접어서 맞춥니다. 띄어쓰기·대소문자·전각을 가리지 않으므로, 사랑의화환 으로 쳐도 「사랑의 화환」이 나오고 ROSE 로 쳐도 rose 가 나옵니다. 부분일치라 화환 만 쳐도 됩니다. q 를 비우면 전체입니다.
클라이언트에서 직접 거르지 마세요. 이름의 공백은 눈에 안 보여서 사람도 AI 도 정규화를 빠뜨립니다 — 한 사이트가 붙여 친 질의로 자체 검색을 짰다가 이름 298건 중 254건(85%)을 놓쳤습니다. 같은 규칙이 항목의 유일성 판정에도 쓰이므로, 서버에 맡기면 「검색되는 것」과 「같은 항목으로 취급되는 것」이 어긋나지 않습니다.
ETag 에는 검색어가 함께 들어갑니다. 다른 검색어가 같은 판본을 받아 남의 결과로 304 가 나는 일은 없습니다.
⚠️ 페이지네이션은 없습니다. 항목 수가 수백 종이면 그대로 쓰면 되고, 그보다 크게 키울 계획이면 먼저 알려 주세요 — 상한과 페이지 규약을 함께 정해야 합니다.
항목 넣기
항목은 매니페스트로 한꺼번에 넣습니다. 도감 수백 종을 처음 채울 때 쓰는 경로입니다.
npx @saeroon/cli catalog import flowers ./items.json --site <siteId>
{
"items": [
{ "name": "장미", "attributes": { "grade": 3, "season": "여름" }, "sortOrder": 1 },
{ "name": "튤립", "attributes": { "grade": 2, "season": "봄" }, "sortOrder": 2 }
]
}
name 이 자연키입니다. 같은 이름의 활성 항목이 있으면 갱신하고, 없으면 새로 만듭니다.
기존 항목을 내보내 그대로 되먹일 수 있습니다.
npx @saeroon/cli catalog items flowers --site <siteId> --manifest -o items.json
삭제 대신 은퇴
이 도메인에는 삭제가 없습니다. 카탈로그는 보관(archive), 항목은 은퇴(retire)합니다.
npx @saeroon/cli catalog retire flowers <itemId> --site <siteId> # 목록에서 숨김
npx @saeroon/cli catalog restore flowers <itemId> --site <siteId> # 되돌림
npx @saeroon/cli catalog archive flowers --site <siteId> # 카탈로그째 숨김
지우지 않는 이유는 회원의 보유 기록과 그룹 표식이 항목 ID 를 가리키고 있기 때문입니다. 항목을 지우면 그 기록들이 가리킬 곳을 잃습니다. 은퇴한 항목은 공개 목록에서 빠지지만 이미 있던 보유는 그대로 남고, 응답에 isRetired: true 로 표시됩니다.
주의할 점이 하나 있습니다. 은퇴한 항목과 같은 이름을 다시 넣으면 그 항목이 되살아납니다. 새 행이 생기는 것이 아니라 원래 ID 가 돌아오고, 그것을 가리키던 보유도 함께 돌아옵니다. catalog import 는 그런 경우를 출력에 따로 알려 줍니다.
apply 와 import 는 매니페스트에 없는 것을 건드리지 않습니다. 정리하려면 --prune 을 명시해야 합니다.
사이트에 붙이기
방문자에게 카탈로그를 보여 주는 것은 공개 읽기라 로그인이 필요 없습니다.
<div data-saeroon-catalog-list data-catalog="flowers">
<template>
<li>
<span data-saeroon-field="name"></span>
<em data-saeroon-field="attr:grade"></em>
</li>
</template>
<p data-saeroon-empty>항목이 없습니다.</p>
</div>
<template> 안이 항목 하나의 모양입니다. data-saeroon-field 는 항목의 필드를, data-saeroon-field="attr:<키>" 는 속성 값을 채웁니다. 템플릿이 없으면 아무것도 그리지 않습니다 — 임의의 DOM 을 만들어 넣어 디자인을 흐트러뜨리지 않기 위해서입니다.
배포할 때 @saeroon/web-sdk 가 자동으로 주입됩니다.
관련 문서
요약
카탈로그는 사이트가 소유하는 이름 붙은 목록입니다. 꽃 도감, 배지 세트, 대여 장비 목록처럼 항목 하나하나에 의미가 있고, 나중에 다른 기록이 그 항목을 가리키게 되는 자료가 카탈로그입니다.