GET/api/issues
프로젝트 이슈 목록 조회(GET) 및 신규 이슈 생성(POST)을 담당한다.
API 라우트: /api/issues
프로젝트 이슈 목록 조회(GET) 및 신규 이슈 생성(POST)을 담당한다.
GET 주요 기능:
- (sortOrder, id) ASC 기반 커서 페이지네이션 — 리스트·타임라인·캘린더 뷰에서 사용
- parentId 필터: 상세 시트 내 자식 이슈 조회
- rootsOnly=1: 트리 lazy-load 최상위 fetch (자식 있음 여부를 childCount 로 전달)
- 각 행에 상태(status)·담당자(assignee) JOIN 포함
POST 주요 기능:
- 이슈 키(projectKey-N) 자동 발급 (projects.issueCounter 원자적 증가)
- 상태 미지정 시 프로젝트 기본 상태 자동 배정
- 형제 이슈 중 최대 sortOrder + 1000 을 초기 정렬값으로 설정
- 활동 로그 기록(logActivity) + SSE 실시간 이벤트 발행(publishProjectEvent)
- description 은 sanitizeRichHtml 로 XSS 방지 처리
권한: 프로젝트 접근 가능 멤버 이상 (requireUser + requireProjectAccess)
@author jijuta ([email protected])
@since 2026-05-03
@updated 2026-06-07 — JSDoc 표준(@author/@since/@param 등) 적용
Responses
200 성공401 미인증(JWT 쿠키 필요)403 권한 없음404 없음
cURLcurl -X GET https://sit.in-bridge.com/api/issues \
-H "Authorization: Bearer $TOKEN"
JavaScriptconst res = await fetch("https://sit.in-bridge.com/api/issues", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
}
});
const data = await res.json();
POST/api/issues
프로젝트 이슈 목록 조회(GET) 및 신규 이슈 생성(POST)을 담당한다.
API 라우트: /api/issues
프로젝트 이슈 목록 조회(GET) 및 신규 이슈 생성(POST)을 담당한다.
GET 주요 기능:
- (sortOrder, id) ASC 기반 커서 페이지네이션 — 리스트·타임라인·캘린더 뷰에서 사용
- parentId 필터: 상세 시트 내 자식 이슈 조회
- rootsOnly=1: 트리 lazy-load 최상위 fetch (자식 있음 여부를 childCount 로 전달)
- 각 행에 상태(status)·담당자(assignee) JOIN 포함
POST 주요 기능:
- 이슈 키(projectKey-N) 자동 발급 (projects.issueCounter 원자적 증가)
- 상태 미지정 시 프로젝트 기본 상태 자동 배정
- 형제 이슈 중 최대 sortOrder + 1000 을 초기 정렬값으로 설정
- 활동 로그 기록(logActivity) + SSE 실시간 이벤트 발행(publishProjectEvent)
- description 은 sanitizeRichHtml 로 XSS 방지 처리
권한: 프로젝트 접근 가능 멤버 이상 (requireUser + requireProjectAccess)
@author jijuta ([email protected])
@since 2026-05-03
@updated 2026-06-07 — JSDoc 표준(@author/@since/@param 등) 적용
Request body
projectIdstring<uuid>required
typeenumrequired
epicstorytasksubtaskbug
Responses
200 성공400 입력 검증 실패(zod)401 미인증(JWT 쿠키 필요)403 권한 없음404 없음
cURLcurl -X POST https://sit.in-bridge.com/api/issues \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"projectId":"00000000-0000-0000-0000-000000000000","title":"string","description":"string","type":"epic","priority":null,"statusId":"00000000-0000-0000-0000-000000000000","assigneeId":"00000000-0000-0000-0000-000000000000","parentId":"00000000-0000-0000-0000-000000000000","startDate":"string","dueDate":"string","storyPoints":null,"repoPath":"string"}'
JavaScriptconst res = await fetch("https://sit.in-bridge.com/api/issues", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"projectId": "00000000-0000-0000-0000-000000000000",
"title": "string",
"description": "string",
"type": "epic",
"priority": null,
"statusId": "00000000-0000-0000-0000-000000000000",
"assigneeId": "00000000-0000-0000-0000-000000000000",
"parentId": "00000000-0000-0000-0000-000000000000",
"startDate": "string",
"dueDate": "string",
"storyPoints": null,
"repoPath": "string"
})
});
const data = await res.json();
GET/api/issues/{issueId}
API 라우트: GET | PATCH | DELETE /api/issues/[issueId]
API 라우트: GET | PATCH | DELETE /api/issues/[issueId]
단일 이슈의 상세 조회, 수정, 소프트 삭제를 처리한다.
GET:
이슈 상세(상태·담당자·부모 join) + 댓글 목록(최신순)을 반환한다.
결과는 Redis 에 15 초 캐시된다. 캐시 히트 시에도 권한 검사는 항상 수행한다.
PATCH:
updateIssueSchema 에 정의된 필드를 부분 업데이트한다.
description 은 sanitizeRichHtml 로 XSS 를 제거한 후 저장한다.
담당자가 새로 지정되면 해당 사용자에게 인앱 알림을 전송한다.
DELETE:
물리 삭제 없이 deletedAt 타임스탬프만 기록(소프트 삭제)한다.
목록 조회에서 isNull(deletedAt) 필터로 자동 제외된다.
권한: 모든 메서드에서 프로젝트 멤버여야 한다.
부작용:
- Redis 캐시 무효화(PATCH/DELETE)
- activity_logs 기록
- SSE(publishProjectEvent) 실시간 브로드캐스트
@author jijuta ([email protected])
@since 2026-05-03
@updated 2026-06-07 — JSDoc 표준(@author/@since/@param 등) 적용
Responses
200 성공401 미인증(JWT 쿠키 필요)403 권한 없음404 없음
cURLcurl -X GET https://sit.in-bridge.com/api/issues/{issueId} \
-H "Authorization: Bearer $TOKEN"
JavaScriptconst res = await fetch("https://sit.in-bridge.com/api/issues/{issueId}", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
}
});
const data = await res.json();
PATCH/api/issues/{issueId}
API 라우트: GET | PATCH | DELETE /api/issues/[issueId]
API 라우트: GET | PATCH | DELETE /api/issues/[issueId]
단일 이슈의 상세 조회, 수정, 소프트 삭제를 처리한다.
GET:
이슈 상세(상태·담당자·부모 join) + 댓글 목록(최신순)을 반환한다.
결과는 Redis 에 15 초 캐시된다. 캐시 히트 시에도 권한 검사는 항상 수행한다.
PATCH:
updateIssueSchema 에 정의된 필드를 부분 업데이트한다.
description 은 sanitizeRichHtml 로 XSS 를 제거한 후 저장한다.
담당자가 새로 지정되면 해당 사용자에게 인앱 알림을 전송한다.
DELETE:
물리 삭제 없이 deletedAt 타임스탬프만 기록(소프트 삭제)한다.
목록 조회에서 isNull(deletedAt) 필터로 자동 제외된다.
권한: 모든 메서드에서 프로젝트 멤버여야 한다.
부작용:
- Redis 캐시 무효화(PATCH/DELETE)
- activity_logs 기록
- SSE(publishProjectEvent) 실시간 브로드캐스트
@author jijuta ([email protected])
@since 2026-05-03
@updated 2026-06-07 — JSDoc 표준(@author/@since/@param 등) 적용
Request body
typeenum
epicstorytasksubtaskbug
Responses
200 성공400 입력 검증 실패(zod)401 미인증(JWT 쿠키 필요)403 권한 없음404 없음
cURLcurl -X PATCH https://sit.in-bridge.com/api/issues/{issueId} \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"title":"string","description":"string","statusId":"00000000-0000-0000-0000-000000000000","type":"epic","priority":null,"assigneeId":"00000000-0000-0000-0000-000000000000","parentId":"00000000-0000-0000-0000-000000000000","startDate":"string","dueDate":"string","storyPoints":null,"sprintId":"00000000-0000-0000-0000-000000000000"}'
JavaScriptconst res = await fetch("https://sit.in-bridge.com/api/issues/{issueId}", {
method: "PATCH",
headers: {
"Authorization": `Bearer ${token}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"title": "string",
"description": "string",
"statusId": "00000000-0000-0000-0000-000000000000",
"type": "epic",
"priority": null,
"assigneeId": "00000000-0000-0000-0000-000000000000",
"parentId": "00000000-0000-0000-0000-000000000000",
"startDate": "string",
"dueDate": "string",
"storyPoints": null,
"sprintId": "00000000-0000-0000-0000-000000000000"
})
});
const data = await res.json();
DELETE/api/issues/{issueId}
API 라우트: GET | PATCH | DELETE /api/issues/[issueId]
API 라우트: GET | PATCH | DELETE /api/issues/[issueId]
단일 이슈의 상세 조회, 수정, 소프트 삭제를 처리한다.
GET:
이슈 상세(상태·담당자·부모 join) + 댓글 목록(최신순)을 반환한다.
결과는 Redis 에 15 초 캐시된다. 캐시 히트 시에도 권한 검사는 항상 수행한다.
PATCH:
updateIssueSchema 에 정의된 필드를 부분 업데이트한다.
description 은 sanitizeRichHtml 로 XSS 를 제거한 후 저장한다.
담당자가 새로 지정되면 해당 사용자에게 인앱 알림을 전송한다.
DELETE:
물리 삭제 없이 deletedAt 타임스탬프만 기록(소프트 삭제)한다.
목록 조회에서 isNull(deletedAt) 필터로 자동 제외된다.
권한: 모든 메서드에서 프로젝트 멤버여야 한다.
부작용:
- Redis 캐시 무효화(PATCH/DELETE)
- activity_logs 기록
- SSE(publishProjectEvent) 실시간 브로드캐스트
@author jijuta ([email protected])
@since 2026-05-03
@updated 2026-06-07 — JSDoc 표준(@author/@since/@param 등) 적용
Responses
200 성공401 미인증(JWT 쿠키 필요)403 권한 없음404 없음
cURLcurl -X DELETE https://sit.in-bridge.com/api/issues/{issueId} \
-H "Authorization: Bearer $TOKEN"
JavaScriptconst res = await fetch("https://sit.in-bridge.com/api/issues/{issueId}", {
method: "DELETE",
headers: {
"Authorization": `Bearer ${token}`
}
});
const data = await res.json();
GET/api/issues/{issueId}/labels
특정 이슈의 라벨을 조회하거나 토글(추가/제거)한다.
API 라우트: GET/POST /api/issues/[issueId]/labels
특정 이슈의 라벨을 조회하거나 토글(추가/제거)한다.
- GET : 이슈에 붙은 라벨 목록 반환 (id·name·color).
- POST: { labelId } 를 받아 이미 붙어있으면 제거, 없으면 추가하는 토글 방식.
변경 후 활동 로그 기록 + Redis SSE 이벤트로 실시간 동기화.
권한: 로그인 사용자 + 해당 프로젝트 접근 권한 (requireProjectAccess).
부작용:
- issueLabels 테이블 insert 또는 delete
- activity_logs 기록 (logActivity)
- Redis Pub/Sub 으로 프로젝트 이벤트 발행 (publishProjectEvent → SSE 구독자에게 전파)
@author jijuta ([email protected])
@since 2026-05-03
@updated 2026-06-07 — JSDoc 표준(@author/@since/@param 등) 적용
Responses
200 성공401 미인증(JWT 쿠키 필요)403 권한 없음404 없음
cURLcurl -X GET https://sit.in-bridge.com/api/issues/{issueId}/labels \
-H "Authorization: Bearer $TOKEN"
JavaScriptconst res = await fetch("https://sit.in-bridge.com/api/issues/{issueId}/labels", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
}
});
const data = await res.json();
POST/api/issues/{issueId}/labels
특정 이슈의 라벨을 조회하거나 토글(추가/제거)한다.
API 라우트: GET/POST /api/issues/[issueId]/labels
특정 이슈의 라벨을 조회하거나 토글(추가/제거)한다.
- GET : 이슈에 붙은 라벨 목록 반환 (id·name·color).
- POST: { labelId } 를 받아 이미 붙어있으면 제거, 없으면 추가하는 토글 방식.
변경 후 활동 로그 기록 + Redis SSE 이벤트로 실시간 동기화.
권한: 로그인 사용자 + 해당 프로젝트 접근 권한 (requireProjectAccess).
부작용:
- issueLabels 테이블 insert 또는 delete
- activity_logs 기록 (logActivity)
- Redis Pub/Sub 으로 프로젝트 이벤트 발행 (publishProjectEvent → SSE 구독자에게 전파)
@author jijuta ([email protected])
@since 2026-05-03
@updated 2026-06-07 — JSDoc 표준(@author/@since/@param 등) 적용
Request body
labelIdstring<uuid>required
Responses
200 성공400 입력 검증 실패(zod)401 미인증(JWT 쿠키 필요)403 권한 없음404 없음
cURLcurl -X POST https://sit.in-bridge.com/api/issues/{issueId}/labels \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"labelId":"00000000-0000-0000-0000-000000000000"}'
JavaScriptconst res = await fetch("https://sit.in-bridge.com/api/issues/{issueId}/labels", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"labelId": "00000000-0000-0000-0000-000000000000"
})
});
const data = await res.json();
GET/api/issues/{issueId}/links
API 라우트: GET|POST /api/issues/[issueId]/links
API 라우트: GET|POST /api/issues/[issueId]/links
이슈 간 관계(링크) 목록 조회 및 생성.
링크는 항상 양방향으로 저장된다(source→target, target→source 짝 row).
GET 은 해당 이슈가 source 인 outgoing 링크만 반환하며, 대상 이슈 메타를 join 해 포함한다.
POST 는 트랜잭션으로 두 방향 모두 INSERT 하고, 중복 시 409 를 반환한다.
권한: 해당 이슈의 프로젝트 멤버여야 한다.
부작용(POST):
- activity_logs 에 issue.link_added 기록
- SSE(publishProjectEvent) 로 실시간 브로드캐스트
@author jijuta ([email protected])
@since 2026-05-12
@updated 2026-06-07 — JSDoc 표준(@author/@since/@param 등) 적용
@updated 2026-06-07 — #62 targetId 직접 전달 시 같은 프로젝트 소속 검증(cross-project 연결 IDOR 차단)
Responses
200 성공401 미인증(JWT 쿠키 필요)403 권한 없음404 없음
cURLcurl -X GET https://sit.in-bridge.com/api/issues/{issueId}/links \
-H "Authorization: Bearer $TOKEN"
JavaScriptconst res = await fetch("https://sit.in-bridge.com/api/issues/{issueId}/links", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
}
});
const data = await res.json();
POST/api/issues/{issueId}/links
API 라우트: GET|POST /api/issues/[issueId]/links
API 라우트: GET|POST /api/issues/[issueId]/links
이슈 간 관계(링크) 목록 조회 및 생성.
링크는 항상 양방향으로 저장된다(source→target, target→source 짝 row).
GET 은 해당 이슈가 source 인 outgoing 링크만 반환하며, 대상 이슈 메타를 join 해 포함한다.
POST 는 트랜잭션으로 두 방향 모두 INSERT 하고, 중복 시 409 를 반환한다.
권한: 해당 이슈의 프로젝트 멤버여야 한다.
부작용(POST):
- activity_logs 에 issue.link_added 기록
- SSE(publishProjectEvent) 로 실시간 브로드캐스트
@author jijuta ([email protected])
@since 2026-05-12
@updated 2026-06-07 — JSDoc 표준(@author/@since/@param 등) 적용
@updated 2026-06-07 — #62 targetId 직접 전달 시 같은 프로젝트 소속 검증(cross-project 연결 IDOR 차단)
Responses
200 성공401 미인증(JWT 쿠키 필요)403 권한 없음404 없음
cURLcurl -X POST https://sit.in-bridge.com/api/issues/{issueId}/links \
-H "Authorization: Bearer $TOKEN"
JavaScriptconst res = await fetch("https://sit.in-bridge.com/api/issues/{issueId}/links", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`
}
});
const data = await res.json();
DELETE/api/issues/{issueId}/links/{linkId}
특정 이슈 링크(issueLinks 행) 하나를 삭제한다.
API 라우트: DELETE /api/issues/[issueId]/links/[linkId]
특정 이슈 링크(issueLinks 행) 하나를 삭제한다.
링크는 양방향으로 저장되므로(source→target, target→source 짝 행),
삭제 시 트랜잭션 안에서 두 행을 모두 제거해 일관성을 유지한다.
권한: 해당 이슈의 프로젝트 멤버여야 한다.
부작용:
- activity_logs 에 issue.link_removed 기록
- SSE(publishProjectEvent) 로 실시간 브로드캐스트
@author jijuta ([email protected])
@since 2026-05-12
@updated 2026-06-07 — JSDoc 표준(@author/@since/@param 등) 적용
Responses
200 성공401 미인증(JWT 쿠키 필요)403 권한 없음404 없음
cURLcurl -X DELETE https://sit.in-bridge.com/api/issues/{issueId}/links/{linkId} \
-H "Authorization: Bearer $TOKEN"
JavaScriptconst res = await fetch("https://sit.in-bridge.com/api/issues/{issueId}/links/{linkId}", {
method: "DELETE",
headers: {
"Authorization": `Bearer ${token}`
}
});
const data = await res.json();
PATCH/api/issues/{issueId}/move
이슈를 계층 트리 안에서 이동시키고 sort_order 를 분수 정렬(fractional indexing)로 재계산한다.
API 라우트: PATCH /api/issues/[issueId]/move
이슈를 계층 트리 안에서 이동시키고 sort_order 를 분수 정렬(fractional indexing)로 재계산한다.
세 가지 이동 모드를 지원한다:
- child : 지정한 부모(parentId 또는 relativeToId)의 자식 끝에 추가
- before : relativeToId 이슈 바로 앞(형제) 위치
- after : relativeToId 이슈 바로 뒤(형제) 위치
사이클 방지를 위해 자손이 자신의 조상이 되는 이동은 재귀 CTE 로 차단한다.
권한: 해당 이슈의 프로젝트 멤버여야 한다.
부작용:
- Redis 캐시 무효화(cacheKeys.issueById)
- activity_logs 에 issue.moved 기록
- SSE(publishProjectEvent) 로 실시간 브로드캐스트
@author jijuta ([email protected])
@since 2026-05-04
@updated 2026-06-07 — JSDoc 표준(@author/@since/@param 등) 적용
Responses
200 성공401 미인증(JWT 쿠키 필요)403 권한 없음404 없음
cURLcurl -X PATCH https://sit.in-bridge.com/api/issues/{issueId}/move \
-H "Authorization: Bearer $TOKEN"
JavaScriptconst res = await fetch("https://sit.in-bridge.com/api/issues/{issueId}/move", {
method: "PATCH",
headers: {
"Authorization": `Bearer ${token}`
}
});
const data = await res.json();
GET/api/issues/{issueId}/watch
특정 이슈에 대한 "구독(watch)" 기능을 담당한다.
API 라우트: /api/issues/[issueId]/watch
특정 이슈에 대한 "구독(watch)" 기능을 담당한다.
구독한 사용자는 이슈 변경(댓글·상태·담당자 등) 시 알림을 수신한다.
- GET : 요청자의 구독 여부 + 전체 구독자 수 반환
- POST : 구독 토글 — 이미 구독 중이면 해제, 미구독이면 구독 등록
권한: 프로젝트 멤버 이상 (requireProjectAccess)
@author jijuta ([email protected])
@since 2026-05-12
@updated 2026-06-07 — JSDoc 표준(@author/@since/@param 등) 적용
Responses
200 성공401 미인증(JWT 쿠키 필요)403 권한 없음404 없음
cURLcurl -X GET https://sit.in-bridge.com/api/issues/{issueId}/watch \
-H "Authorization: Bearer $TOKEN"
JavaScriptconst res = await fetch("https://sit.in-bridge.com/api/issues/{issueId}/watch", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
}
});
const data = await res.json();
POST/api/issues/{issueId}/watch
특정 이슈에 대한 "구독(watch)" 기능을 담당한다.
API 라우트: /api/issues/[issueId]/watch
특정 이슈에 대한 "구독(watch)" 기능을 담당한다.
구독한 사용자는 이슈 변경(댓글·상태·담당자 등) 시 알림을 수신한다.
- GET : 요청자의 구독 여부 + 전체 구독자 수 반환
- POST : 구독 토글 — 이미 구독 중이면 해제, 미구독이면 구독 등록
권한: 프로젝트 멤버 이상 (requireProjectAccess)
@author jijuta ([email protected])
@since 2026-05-12
@updated 2026-06-07 — JSDoc 표준(@author/@since/@param 등) 적용
Responses
200 성공401 미인증(JWT 쿠키 필요)403 권한 없음404 없음
cURLcurl -X POST https://sit.in-bridge.com/api/issues/{issueId}/watch \
-H "Authorization: Bearer $TOKEN"
JavaScriptconst res = await fetch("https://sit.in-bridge.com/api/issues/{issueId}/watch", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`
}
});
const data = await res.json();
GET/api/issues/by-key
이슈 key → id 조회.
GET /api/issues/by-key?projectId=X&key=DFX-5 — 이슈 key → id 조회.
별도 상세 페이지(/issues/[key])가 key 로 진입해 id 를 얻어 상세를 렌더하기 위함.
Responses
200 성공401 미인증(JWT 쿠키 필요)403 권한 없음404 없음
cURLcurl -X GET https://sit.in-bridge.com/api/issues/by-key \
-H "Authorization: Bearer $TOKEN"
JavaScriptconst res = await fetch("https://sit.in-bridge.com/api/issues/by-key", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
}
});
const data = await res.json();