store
다수 사람·LLM 에이전트가 공유하는 노트·개념·관계 그래프 백엔드. PostgreSQL 16 + Apache AGE + pgvector 한 인스턴스에 모두 들어가고, 모든 데이터는 REST API 로만 다룹니다. account 단위로 그래프가 분리되어 다른 사람의 데이터에 절대 접근할 수 없습니다.
01무엇을 하는가
store 는 LLM 이나 사람이 만든 노트·개념·아이디어를 영구 저장하고, 노드 사이의 관계를 그래프로 연결하는 두뇌 보조 서비스입니다. 단일 도메인 https://store.parklab.work 에서 REST API 로 동작하며, 다음을 약속합니다.
- account 격리: 발급된 service account 토큰은 자기 계정의 노드와 그래프에만 접근할 수 있습니다.
- 그래프 + 의미 임베딩: 노드는 관계형 row 와 Apache AGE 그래프 vertex 가 동시에 만들어지며, 이후 단계에서 BGE-M3 임베딩(1024차원)이 자동으로 채워집니다.
- 운영 단순성: 서비스 사용자는 토큰 하나만 들고 다니면 되고, 관리자는 admin 토큰으로 service account 를 발급/회수 합니다.
02인증과 토큰
모든 사용자 엔드포인트(/v1/nodes 등)는 HTTP 헤더 Authorization: Bearer <token> 가 필요합니다. 토큰은 32바이트 임의 비밀의 base64url 표현(약 43자)이며, 발급 시 한 번만 응답에 노출됩니다. 잃어버리면 새로 발급받아야 합니다.
토큰 발급 응답에는 평문 token 외에 prefix(앞 12자)가 함께 들어 있습니다. prefix 는 평문 비밀이 아니므로 운영 로그·감사·티켓에 안전하게 적을 수 있고, 같은 account 가 여러 토큰을 가진 경우 이들을 구분하는 유일한 안전 식별자입니다 (DB 에 저장된 prefix 와 같음).
일반 사용자 — 토큰 받기
관리자(ADMIN_TOKEN 보유자)에게 다음을 요청합니다.
- 본인용 service account 생성
- 해당 account 에 발급한 Bearer 토큰 전달
관리자는 관리자 엔드포인트 절차로 발급해 줍니다. 받은 토큰은 한 번만 화면에 보이는 plaintext 이므로 즉시 안전한 곳에 보관하세요.
토큰 사용
export STORE_TOKEN='<발급받은_토큰>'
curl -fsS https://store.parklab.work/v1/nodes \
-H "Authorization: Bearer $STORE_TOKEN"
03노드 CRUD
노드 한 개는 노트·개념·엔터티 등 어떤 단위든 됩니다. kind 와 title 은 필수, 나머지는 선택입니다. 모든 노드는 호출자 account 의 AGE 그래프 안에 동시에 vertex 로 만들어집니다.
노드 생성 — POST/v1/nodes
curl -fsS -X POST https://store.parklab.work/v1/nodes \
-H "Authorization: Bearer $STORE_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"kind": "Note",
"title": "첫 노드",
"body": "store 에 처음 저장한 노드.",
"meta": { "tags": ["intro"] }
}'
응답 (예시)
{
"id": "a12f8457-e553-44a2-916e-5f89a5fd7569",
"account_id": "49629210-9611-4d96-a067-dbb030fd4568",
"kind": "Note",
"title": "첫 노드",
"body": "store 에 처음 저장한 노드.",
"meta": { "tags": ["intro"] },
"created_at": "2026-05-07T15:16:41.370414Z",
"updated_at": "2026-05-07T15:16:41.370414Z"
}
단건 조회 / 목록 / 패치 / 삭제
| endpoint | 설명 | 응답 |
|---|---|---|
| POST /v1/nodes | 새 노드 생성. Content-Type: application/json 필수. | 201 + node JSON · 400 잘못된 body · 415 잘못된 Content-Type |
| GET /v1/nodes/{id} | 한 노드 조회. account 의 노드가 아니면 404. | 200 + node JSON · 400 잘못된 id · 404 없음 |
| GET /v1/nodes?limit=N&offset=M | 최신순. limit 1..200 (기본 50), offset >= 0. 잘못된 값은 400. | 200 + {"items":[…]} · 400 잘못된 페이징 |
| PATCH /v1/nodes/{id} | 부분 수정. body 에 title, body, meta 중 변경할 키만. Content-Type: application/json 필수. | 200 + 갱신된 node · 400 · 404 · 415 |
| DELETE /v1/nodes/{id} | soft delete. account 격리 위반은 404. | 204 · 400 · 404 |
한 흐름 예시
# 1) 노드 만들고 id 보관
NODE_ID=$(curl -fsS -X POST https://store.parklab.work/v1/nodes \
-H "Authorization: Bearer $STORE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"kind":"Concept","title":"양자 얽힘"}' | jq -r .id)
# 2) 본문 채우기
curl -fsS -X PATCH "https://store.parklab.work/v1/nodes/$NODE_ID" \
-H "Authorization: Bearer $STORE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"body":"두 입자가 측정 전까지 한 시스템처럼 행동하는 현상."}'
# 3) 다시 읽기
curl -fsS "https://store.parklab.work/v1/nodes/$NODE_ID" \
-H "Authorization: Bearer $STORE_TOKEN"
# 4) 목록
curl -fsS "https://store.parklab.work/v1/nodes?limit=10" \
-H "Authorization: Bearer $STORE_TOKEN"
# 5) 정리
curl -fsS -X DELETE "https://store.parklab.work/v1/nodes/$NODE_ID" \
-H "Authorization: Bearer $STORE_TOKEN"
04account 격리
account A 의 토큰으로 account B 의 노드 id 에 접근하면 무조건 404 가 돌아옵니다 — 존재 자체를 알려주지 않습니다. 격리는 두 층에서 강제됩니다.
- application 레이어: 모든 SQL 에
WHERE account_id = $auth.account_id가 들어갑니다. - 그래프 레이어: account 마다 자체 AGE 그래프(
acct_<uuid_hex>)가 있고, cypher 호출자는 graph 이름을 직접 넘길 수 없습니다.
새로 발급받은 토큰으로 처음 호출하면 GET /v1/nodes 는 {"items":[]} 입니다 — 다른 사용자의 노드는 절대 새 시야에 들어오지 않습니다.
05관리자 엔드포인트
다음은 ADMIN_TOKEN 을 가진 관리자에게만 의미가 있습니다. /v1/admin/* 경로는 admin Bearer 가 필요합니다.
account 발급
curl -fsS -X POST https://store.parklab.work/v1/admin/accounts \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "alice"}'
응답(201)에는 id 와 graph_name(자동 derived: acct_<uuid_hex>) 이 들어 있습니다. 같은 이름이 이미 활성 상태면 409 Conflict 가 돌아옵니다.
토큰 발급
curl -fsS -X POST https://store.parklab.work/v1/admin/accounts/$ACCOUNT_ID/tokens \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "alice-laptop"}'
응답(201)의 token 필드가 사용자에게 한 번만 노출되는 plaintext 입니다. prefix (앞 12자) 는 안전하게 로그/감사에 적을 수 있는 식별자로 같이 돌아옵니다. 즉시 사용자에게 안전 채널로 전달.
토큰 회수 / account 삭제
curl -fsS -X DELETE https://store.parklab.work/v1/admin/tokens/$TOKEN_ID \
-H "Authorization: Bearer $ADMIN_TOKEN"
curl -fsS -X DELETE https://store.parklab.work/v1/admin/accounts/$ACCOUNT_ID \
-H "Authorization: Bearer $ADMIN_TOKEN"
둘 다 성공 시 204. 모르는 id 는 404. account 삭제는 그 account 의 노드와 AGE 그래프, 발급된 모든 토큰을 한 트랜잭션에서 함께 제거합니다 (cascade revoke).
관리자 응답 상태표
| endpoint | 응답 |
|---|---|
| POST /v1/admin/accounts | 201 · 400 잘못된 body · 409 이름 중복 · 415 Content-Type |
| DELETE /v1/admin/accounts/{id} | 204 · 400 잘못된 id · 404 없음 |
| POST /v1/admin/accounts/{id}/tokens | 201 · 400 · 404 account 없음 · 415 |
| DELETE /v1/admin/tokens/{id} | 204 · 400 · 404 |
모든 admin 엔드포인트는 Authorization: Bearer $ADMIN_TOKEN 미일치 시 401.
06헬스체크
| endpoint | 의미 | 인증 |
|---|---|---|
| GET /healthz | 프로세스 살아 있음. | 없음 |
| GET /readyz | DB 핑 성공 시 200, 실패 시 503. | 없음 |
07현재 한계와 다음
- 임베딩: 컬럼
embedding vector(1024)는 있지만 BGE-M3 자동 채움은 다음 라운드. 지금은null. - 검색:
POST /v1/search(벡터 + 그래프 hop 결합)는 다음 라운드. - 엣지(관계): 노드는 그래프 vertex 로 만들어지지만 노드 사이의 관계 추가 endpoint 는 다음 라운드. 그 전엔 단순 노드 노트로 사용.
- 운영 자동화: 백업·모니터링·알림은 진행 중.
다음 라운드 목표는 임베딩 자동 갱신 → 하이브리드 검색 → 엣지 CRUD 입니다. 그때까지는 노드 CRUD 와 격리 동작을 자유롭게 테스트해 주세요.