ZUKU API — Social

콘텐츠 소셜(좋아요·북마크·댓글), 알림, DM, 내 프로필 목록, 관리자 회원 API입니다. backend/rs/src/router.rs 실제 경로만 기술합니다.

Base URL

환경 Base URL
프로덕션 https://zuzunza.com/api/v1
로컬 http://localhost:3001/api/v1

목차

  1. 인증 요구사항
  2. 엔드포인트 표
  3. Contents social
  4. Comments
  5. Notifications
  6. DM
  7. Users me lists
  8. Admin users
  9. curl · JS/TS 예제

1. 인증 요구사항

구분 Auth
콘텐츠 댓·북마크 Bearer 필수
댓글 목록 GET 없음(공개). 콘텐츠 없으면 404
댓글 작성·수정·삭제 Bearer 필수
알림 목록 / unread-count Bearer 선택 (없으면 빈/0에 가깝게 동작)
알림 read-all / {id}/read Bearer 필수
DM 전 경로 Bearer 필수 + 대화 참여자만
GET /users/me/{kind} Bearer 필수
Admin users Bearer + role=admin (비관리자 403 FORBIDDEN)

세션 형태: Authorization: Bearer <access_token>.

이 문서의 소셜/DM/알림 쓰기 경로는 X-API-Key를 받지 않습니다(콘텐츠 *생성* 등 다른 경로와 다름).

공통 봉투: { success, data, meta } / { success, error, meta }.

댓글·알림 단건 읽음 등과 달리 댓글 삭제·일부 삭제는 204일 수 있습니다.


2. 엔드포인트 표

Method Path Auth 설명
POST /contents/{id}/like Bearer 좋아요 토글
POST /contents/{id}/bookmark Bearer 북마크 토글
POST /creators/{handle}/follow Bearer 크리에이터 팔로우 토글
GET /contents/{id}/comments 공개 댓글 목록 (page/per_page)
POST /contents/{id}/comments Bearer 댓글 작성 → 201
PATCH /comments/{id} Bearer 내 댓글 수정
DELETE /comments/{id} Bearer 소프트 삭제 → 204
GET /notifications 선택 알림 목록
GET /notifications/unread-count 선택 미읽음 개수만
POST /notifications/read-all Bearer 모두 읽음
POST /notifications/{id}/read Bearer 단건 읽음
GET /dm/conversations Bearer 대화 목록 (before 커서)
POST /dm/conversations Bearer 대화 시작/재사용 → 201
GET /dm/conversations/{id}/messages Bearer+참여자 메시지 목록
POST /dm/conversations/{id}/messages Bearer+참여자 전송 → 201
POST /dm/conversations/{id}/read Bearer+참여자 대화 읽음
GET /users/me/{kind} Bearer 내 작품·좋아요·북마크·글
GET /admin/users Admin 회원 검색 목록
GET /admin/users/{id} Admin 회원 상세
PATCH /admin/users/{id} Admin role / 정지

3. Contents social

POST /contents/{id}/like

토글. 응답:

{ "is_liked": true, "like_count": 42 }

콘텐츠 없음 → 404 CONTENT_NOT_FOUND.

POST /contents/{id}/bookmark

토글. 응답:

{ "is_bookmarked": true, "bookmark_count": 7 }

미인증 → 401. 콘텐츠 없음 → 404 CONTENT_NOT_FOUND.

POST /creators/{handle}/follow

크리에이터 팔로우/언팔로우 토글. Bearer 필수.

성공 200 data:

{ "is_following": true, "follower_count": 1521 }
HTTP code 상황
401 UNAUTHORIZED 세션 없음
404 CREATOR_NOT_FOUND handle 없음·정지
409 CANNOT_FOLLOW_SELF 자기 자신 팔로우

공개 프로필 GET /creators/{handle} 응답의 is_following · follower_count · following_count도 동일 테이블을 사용합니다.

홈 피드 sort=following은 팔로우한 작성자의 작품만 반환합니다.


4. Comments

GET /contents/{id}/comments

쿼리 기본
page 1
per_page 20

응답: data.comments, data.pagination.

POST /contents/{id}/comments

{ "body": "좋은 작품이에요", "parent_id": null }
  • body 최대 1000자
  • parent_id 선택(대댓글)
  • 201: data.comment

PATCH /comments/{id}

{ "body": "수정한 댓글" }

작성자만. 남의 댓글·없음 → 404 COMMENT_NOT_FOUND(존재 누출 방지).

DELETE /comments/{id}

소프트 삭제(답글 자리 보존). 성공 204.


5. Notifications

GET /notifications

page / per_page. 응답:

{
  "notifications": [ ],
  "unread_count": 3,
  "pagination": { }
}

알림 항목: id, type(like \| comment \| follow \| mention \| system), actor?, message, link?, is_read, created_at.

GET /notifications/unread-count

배지 폴링용. data.unread_count만. (WebSocket/SSE 없음 — 단발 HTTP)

POST /notifications/read-all

data.updated = 갱신된 행 수.

POST /notifications/{id}/read

본인 알림만. 성공: { "id": "…", "is_read": true }.

없으면 404 NOTIFICATION_NOT_FOUND.


6. DM

1:1만 지원. 그룹·E2E·실시간 읽음 동기화는 스코프 밖입니다.

참여자가 아니면 대화 존재 여부도 숨김 → 동일 404 CONVERSATION_NOT_FOUND.

GET /dm/conversations

limit(기본 20), before(대화 id 커서).

응답: conversations, next_cursor(마지막 대화 id).

POST /dm/conversations

{ "user_id": "usr_42" }

또는

{ "handle": "peer_handle" }
  • user_id 우선, 없으면 handle
  • 자기 자신 / 상대 없음 → 422 VALIDATION_ERROR
  • 이미 있으면 기존 대화 요약 반환(목록과 같은 모양) → 201 data.conversation

GET /dm/conversations/{id}/messages

limit(기본 50), before.

next_cursor첫 메시지 id(페이지 방향에 맞춤).

POST /dm/conversations/{id}/messages

{ "body": "안녕하세요" }

본문 최대 2000자. 201 data.message.

POST /dm/conversations/{id}/read

data.marked_count.


7. Users me lists

GET /users/me/{kind}항상 현재 로그인 사용자 기준(서버가 필터).

kind 의미 페이지네이션
contents 또는 authored 내가 올린 작품 page / per_pagefeeds + pagination
likes 또는 liked 내가 좋아요한 작품 동일
bookmarks 또는 bookmarked 내가 북마크한 작품 동일
posts 내 커뮤니티 글 limit(기본 20) + beforeposts + next_cursor

그 외 kind404 ROUTE_NOT_FOUND.


8. Admin users

세 경로 모두 require_admin_user: 미인증 401, 비관리자 403 FORBIDDEN.

GET /admin/users

쿼리: q(검색), page, per_pageusers + pagination.

GET /admin/users/{id}

id는 공개 형식(usr_…) 파싱. 활동 카운트 포함 상세.

PATCH /admin/users/{id}

{
  "role": "user",
  "is_suspended": true,
  "reason": "운영 정책 위반"
}
  • role / is_suspended최소 하나 필요
  • role: user \| admin
  • 자기 자신의 admin 해제·자기 정지 → 409 SELF_ACTION_FORBIDDEN

9. curl · JS/TS 예제

콘텐츠 좋아요 · 북마크 · 댓글

curl -sS -X POST "http://localhost:3001/api/v1/contents/$CID/like" \
  -H "Authorization: Bearer $ACCESS"

curl -sS -X POST "http://localhost:3001/api/v1/contents/$CID/bookmark" \
  -H "Authorization: Bearer $ACCESS"

curl -sS "http://localhost:3001/api/v1/contents/$CID/comments?page=1&per_page=20"

curl -sS -X POST "http://localhost:3001/api/v1/contents/$CID/comments" \
  -H "Authorization: Bearer $ACCESS" \
  -H "Content-Type: application/json" \
  -d '{"body":"좋아요!"}'
const base = "http://localhost:3001/api/v1";
const headers = {
  Authorization: `Bearer ${accessToken}`,
  "Content-Type": "application/json",
};

await fetch(`${base}/contents/${contentId}/like`, {
  method: "POST",
  headers: { Authorization: headers.Authorization },
});

const commentsRes = await fetch(
  `${base}/contents/${contentId}/comments?page=1&per_page=20`,
);
const commentsJson = await commentsRes.json();

await fetch(`${base}/contents/${contentId}/comments`, {
  method: "POST",
  headers,
  body: JSON.stringify({ body: "좋아요!" }),
});

알림

curl -sS "http://localhost:3001/api/v1/notifications/unread-count" \
  -H "Authorization: Bearer $ACCESS"

curl -sS -X POST "http://localhost:3001/api/v1/notifications/read-all" \
  -H "Authorization: Bearer $ACCESS"
const unread = await fetch(`${base}/notifications/unread-count`, {
  headers: { Authorization: `Bearer ${accessToken}` },
}).then((r) => r.json());

await fetch(`${base}/notifications/read-all`, {
  method: "POST",
  headers: { Authorization: `Bearer ${accessToken}` },
});

DM

curl -sS -X POST "http://localhost:3001/api/v1/dm/conversations" \
  -H "Authorization: Bearer $ACCESS" \
  -H "Content-Type: application/json" \
  -d '{"handle":"friend"}'

curl -sS "http://localhost:3001/api/v1/dm/conversations/$CONV/messages?limit=50" \
  -H "Authorization: Bearer $ACCESS"

curl -sS -X POST "http://localhost:3001/api/v1/dm/conversations/$CONV/messages" \
  -H "Authorization: Bearer $ACCESS" \
  -H "Content-Type: application/json" \
  -d '{"body":"안녕"}'

curl -sS -X POST "http://localhost:3001/api/v1/dm/conversations/$CONV/read" \
  -H "Authorization: Bearer $ACCESS"
const convRes = await fetch(`${base}/dm/conversations`, {
  method: "POST",
  headers,
  body: JSON.stringify({ handle: "friend" }),
});
const { conversation } = (await convRes.json()).data;

await fetch(`${base}/dm/conversations/${conversation.id}/messages`, {
  method: "POST",
  headers,
  body: JSON.stringify({ body: "안녕" }),
});

await fetch(`${base}/dm/conversations/${conversation.id}/read`, {
  method: "POST",
  headers: { Authorization: headers.Authorization },
});

내 목록

curl -sS "http://localhost:3001/api/v1/users/me/likes?page=1&per_page=20" \
  -H "Authorization: Bearer $ACCESS"

curl -sS "http://localhost:3001/api/v1/users/me/posts?limit=20" \
  -H "Authorization: Bearer $ACCESS"
const likes = await fetch(`${base}/users/me/likes?page=1&per_page=20`, {
  headers: { Authorization: `Bearer ${accessToken}` },
}).then((r) => r.json());

const myPosts = await fetch(`${base}/users/me/posts?limit=20`, {
  headers: { Authorization: `Bearer ${accessToken}` },
}).then((r) => r.json());

오류 코드 (이 문서 범위)

code HTTP 상황
UNAUTHORIZED 401 Bearer 필요
FORBIDDEN 403 비관리자 admin API
CONTENT_NOT_FOUND 404 콘텐츠
COMMENT_NOT_FOUND 404 댓글
NOTIFICATION_NOT_FOUND 404 알림
CONVERSATION_NOT_FOUND 404 DM(비참여자 포함)
VALIDATION_ERROR 422 본문·상대 지정 등
SELF_ACTION_FORBIDDEN 409 관리자 자기 강등/정지
ROUTE_NOT_FOUND 404 잘못된 /users/me/{kind}

*ZUKU API · Social · router.rs 기준*