fix(security): KVE-2026-1914/1915/1919 remediation 전건
위임 관리자(부관리자)가 권한·역할·표현식·비밀 콘텐츠 경계를 우회하던 결함군을 계층 대칭성 원칙으로 전건 차단한다. 약한 경로가 정상 응답을 내보내는 것이 유일한 증상이라, 게이트를 생산 지점 한 곳(SSoT)에 두고 같은 데이터를 내보내는 소비 경로 전부가 그 게이트를 경유하도록 맞췄다. - 등급 상한(rank ceiling): 슈퍼관리자 보호·역할/사용자 역할 배정(추가·제거 대칭)·일괄 상태변경·순서변경을 상세 경로와 동일 강도로 재적용. 가드는 DB 쓰기에 선행하여 거부 시 상태 불변. - 레이아웃 표현식: new Function/with 실행을 AST 화이트리스트 평가기로 교체. 비-문자열 computed 키 정규화(normalizeKey)·Object facade(리플렉션 static 제거)·legacy 접근자 차단. 저장측 검증·정적 검사와 3계층 동형. - secret 게이트: 비밀글의 댓글·첨부·문의 독립 경로 재적용, hash 파일서빙 소유권·비밀·발행 상태 검사 통일. - 신뢰 스크립트 호스트: 확장 선언 기반 + same-origin 브라우저 정규화를 런타임·저장측·정적검사 3층 동형화. - 회귀 감지: 단위·Feature·E2E·시나리오 매니페스트 전축 + audit 룰 4종 신설.
This commit is contained in:
1 parent
cd84b63d2b
commit
71abdeaf60
249 files changed
+14354
-1134
No files matched your search
@@ -178,6 +178,21 @@ location ~* \.(js|css|json)$ { expires max; access_log off; }
|
||||
성공 판정은 상태코드가 아니라 **본문의 매직 토큰과 Content-Type** 으로 합니다. 상태코드만 보면
|
||||
"404 대신 200 + 에러 HTML" 이나 catch-all 200 페이지를 반환하는 설정에서 영원히 오판합니다.
|
||||
|
||||
### 보안 게이트 (KVE-2026 대응)
|
||||
|
||||
일부 관리 엔드포인트에는 표준 응답 외에 다음 보안 게이트가 적용됩니다(각 엔드포인트 표에는 별도 표기가 없어도 공통 적용).
|
||||
|
||||
- **등급 상한 (KVE-2026-1919)** — 사용자·역할 쓰기 경로:
|
||||
- `PUT /api/admin/users/{user}`, `POST /api/admin/users/{user}/unlock`: 비-슈퍼관리자 액터가 슈퍼 관리자 계정을 수정·잠금해제하려 하면 `403` (`exceptions.cannot_modify_super_admin`).
|
||||
- `PATCH /api/admin/users/bulk-status`: 비-슈퍼관리자 액터가 포함시킨 슈퍼 관리자 대상은 일괄 처리에서 제외(요청은 `200`, 슈퍼 관리자 상태 불변).
|
||||
- `POST /api/admin/roles`, `PUT /api/admin/roles/{role}`: 비-슈퍼관리자 액터가 자신이 보유하지 않았거나 자신의 범위(scope)보다 넓은 권한을 부여하려 하면 `403` (`exceptions.cannot_grant_unheld_permission`).
|
||||
- `PUT /api/admin/roles/{role}`, `PATCH /api/admin/roles/{role}/toggle-status`: 비-슈퍼관리자 액터가 코어/확장 소유 역할(예: `admin`)을 수정·토글하려 하면 `403` (`exceptions.cannot_modify_protected_role`).
|
||||
- 슈퍼 관리자 액터의 동일 작업은 정상 수행됩니다.
|
||||
- **레이아웃 저장 표현식/URL 검증 (KVE-2026-1915)** — 레이아웃 생성·수정(`POST/PUT /api/admin/layouts*`)의 `content` 검증:
|
||||
- `{{...}}`·`computed`·`init_actions`/`actions` 문자열 값에 위험 토큰이 있으면 `422` (`validation.layout.dangerous_expression`). 차단 토큰은 프로토타입 체인 접근(`.constructor`/`.__proto__`/`.prototype`, `['constructor']`, 원시 `__proto__`)·`Function(`·`eval(`·`import(` 입니다.
|
||||
- `scripts[].src`·`data_sources[].endpoint` 가 same-origin path-only(`/` 시작)가 아니면 `422` (`validation.layout.external_resource_url`). 단, 활성 확장(모듈·플러그인·템플릿)이 자기 manifest 의 `trusted_script_hosts` 로 선언한 호스트는 예외로 허용됩니다 — 이 목록은 확장 배포물이 정하며 요청으로 바꿀 수 없습니다.
|
||||
- 정상 표현식(조건·계산·목록 가공·화살표 함수·템플릿 리터럴·경로 조립)은 통과합니다.
|
||||
|
||||
## 코어 API 레퍼런스
|
||||
|
||||
<!-- @generated:start:api-readme-index -->
|
||||
|
||||
@@ -261,7 +261,7 @@ HTTP/1.1 200
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.activities.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.activities.delete`)이 없는 경우 |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) |
|
||||
|
||||
<!-- @generated:end -->
|
||||
@@ -313,7 +313,7 @@ HTTP/1.1 200
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.activities.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.activities.delete`)이 없는 경우 |
|
||||
| 404 | Not Found | path 파라미터에 해당하는 리소스가 없는 경우 |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) |
|
||||
|
||||
|
||||
@@ -535,17 +535,58 @@ Content-Type: application/json
|
||||
|
||||
**응답 필드** (`data` 내부)
|
||||
|
||||
<!-- 실측 제외: http-422 — 응답 필드는 사람이 작성하세요. -->
|
||||
_단건 응답: `data` 객체의 필드 (`AuthService::completeTwoFactor()` 가 로그인 세션을 발급해 반환한 배열 — `user` 만 `UserResource` 로 감싼다). 성공 시 페이로드는 일반 로그인(`POST /api/auth/login`)과 동일하다._
|
||||
|
||||
| 필드 | 타입 | 실측 예시값 | 용도/설명 |
|
||||
| --- | --- | --- | --- |
|
||||
| user | object | `{"uuid":"a234c2b1-…","name":"홍길동","is_admin":false, …}` | 로그인한 사용자 정보 (`UserResource` — 필드 전수는 `GET /api/auth/user` 응답 필드 표의 기본(코어) 필드와 동일) |
|
||||
| token | string | `75\|WgPUplvLGTv8YIj4507uIR6dEOHTXyNUed…` | 발급된 Sanctum 접근 토큰 평문 (이후 `Authorization: Bearer` 헤더로 사용, 발급 시 1회만 노출) |
|
||||
| token_type | string | `Bearer` | 토큰 타입 (항상 `Bearer`) |
|
||||
|
||||
> 위 문서의 실측이 `422` 로 관측된 것은 유효한 challenge 없이 프로브가 호출됐기 때문이다. 정상 흐름(비밀번호 단계가 돌려준 `challenge_id` + 올바른 코드)에서는 `200` 과 위 페이로드가 반환된다.
|
||||
|
||||
**응답 예시**
|
||||
|
||||
<!-- 실측 제외: http-422 — 응답 예시는 사람이 작성하세요. -->
|
||||
```http
|
||||
HTTP/1.1 200
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "로그인이 성공했습니다.",
|
||||
"data": {
|
||||
"user": {
|
||||
"uuid": "a234c2b1-cde8-437f-b28b-23323be2b98d",
|
||||
"name": "API 문서 샘플 사용자",
|
||||
"email": "apidoc-sample-user@example.com",
|
||||
"language": "ko",
|
||||
"status": "active",
|
||||
"is_admin": false,
|
||||
"is_owner": true,
|
||||
"abilities": {
|
||||
"can_read": true,
|
||||
"can_create": true,
|
||||
"can_update": true,
|
||||
"can_delete": true,
|
||||
"can_assign_roles": true
|
||||
}
|
||||
},
|
||||
"token": "{MASKED}",
|
||||
"token_type": "Bearer"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> `user` 객체는 지면 절약을 위해 축약했습니다. 실제로는 `UserResource` 필드 전수가 내려옵니다.
|
||||
|
||||
**에러 응답**
|
||||
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthorized | 코드가 틀렸거나(`auth.two_factor_failed`), challenge 의 `purpose` 가 `login` 이 아니거나, 확인된 사용자가 없거나 `active` 상태가 아닌 경우. **세 사유를 같은 응답으로 뭉뚱그린다** — 구분해 내보내면 challenge 유효성 탐색에 쓰인다 |
|
||||
| 422 | Unprocessable Entity | `challenge_id`/`code` 형식 위반 |
|
||||
| 429 | Too Many Requests | `throttle:auth-login` 초과 (로그인과 같은 제한을 공유하므로 코드 대입 시도도 함께 억제된다) |
|
||||
|
||||
<!-- @generated:end -->
|
||||
|
||||
|
||||
@@ -126,7 +126,7 @@ _단건 응답: `data` 객체의 필드._
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.settings.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.settings.update`)이 없는 경우 |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) |
|
||||
|
||||
<!-- @generated:end -->
|
||||
|
||||
@@ -214,7 +214,7 @@ HTTP/1.1 200
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.dashboard.activities`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.dashboard.read`)이 없는 경우 |
|
||||
|
||||
<!-- @generated:end -->
|
||||
|
||||
@@ -323,7 +323,7 @@ HTTP/1.1 200
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.dashboard.activities`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.notification-logs.read`)이 없는 경우 |
|
||||
|
||||
<!-- @generated:end -->
|
||||
|
||||
@@ -395,7 +395,7 @@ HTTP/1.1 200
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.dashboard.activities`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.dashboard.read`)이 없는 경우 |
|
||||
|
||||
<!-- @generated:end -->
|
||||
|
||||
@@ -481,7 +481,7 @@ HTTP/1.1 200
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.dashboard.activities`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.dashboard.read`)이 없는 경우 |
|
||||
|
||||
<!-- @generated:end -->
|
||||
|
||||
|
||||
@@ -234,7 +234,7 @@ HTTP/1.1 200
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.admin.identity.logs.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.admin.identity.logs.purge`)이 없는 경우 |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) |
|
||||
|
||||
<!-- @generated:end -->
|
||||
@@ -352,7 +352,7 @@ HTTP/1.1 200
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.admin.identity.logs.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.admin.identity.messages.read`)이 없는 경우 |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) |
|
||||
|
||||
<!-- @generated:end -->
|
||||
@@ -513,7 +513,7 @@ HTTP/1.1 201
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.admin.identity.logs.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.admin.identity.messages.update`)이 없는 경우 |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) |
|
||||
| 500 | Internal Server Error | 정의 생성 중 예외 발생 (`IDV 메시지 정의 생성에 실패했습니다.`) |
|
||||
|
||||
@@ -713,7 +713,7 @@ HTTP/1.1 200
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.admin.identity.logs.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.admin.identity.messages.read`)이 없는 경우 |
|
||||
| 404 | Not Found | path 파라미터에 해당하는 리소스가 없는 경우 |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) |
|
||||
|
||||
@@ -1018,7 +1018,7 @@ HTTP/1.1 200
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.admin.identity.logs.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.admin.identity.messages.update`)이 없는 경우 |
|
||||
| 404 | Not Found | path 파라미터에 해당하는 리소스가 없는 경우 |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) |
|
||||
|
||||
@@ -1145,7 +1145,7 @@ HTTP/1.1 200
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.admin.identity.logs.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.admin.identity.messages.update`)이 없는 경우 |
|
||||
| 404 | Not Found | path 파라미터에 해당하는 리소스가 없는 경우 |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) |
|
||||
|
||||
@@ -1417,7 +1417,7 @@ HTTP/1.1 200
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.admin.identity.logs.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.admin.identity.messages.update`)이 없는 경우 |
|
||||
| 404 | Not Found | path 파라미터에 해당하는 리소스가 없는 경우 |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) |
|
||||
|
||||
@@ -1509,7 +1509,7 @@ HTTP/1.1 200
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.admin.identity.logs.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.admin.identity.messages.update`)이 없는 경우 |
|
||||
| 404 | Not Found | path 파라미터에 해당하는 리소스가 없는 경우 |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) |
|
||||
|
||||
@@ -1652,7 +1652,7 @@ HTTP/1.1 200
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.admin.identity.logs.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.admin.identity.policies.read`)이 없는 경우 |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) |
|
||||
|
||||
<!-- @generated:end -->
|
||||
@@ -2215,7 +2215,7 @@ HTTP/1.1 200
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.admin.identity.logs.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.admin.identity.providers.read`)이 없는 경우 |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) |
|
||||
|
||||
<!-- @generated:end -->
|
||||
|
||||
+106
-8
@@ -40,21 +40,86 @@ Authorization: Bearer {YOUR_TOKEN} (optional.sanctum: 비회원은 헤더 생
|
||||
|
||||
**응답 필드** (`data` 내부)
|
||||
|
||||
<!-- 실측 제외: unresolved-path-param — 응답 필드는 사람이 작성하세요. -->
|
||||
이 엔드포인트는 `ResponseHelper` 봉투(`success`/`message`/`data`)를 쓰지 않는다. **레이아웃 JSON 자체**를 최상위로 그대로 반환하므로 `data` 래퍼가 없다 — 템플릿 엔진이 실제 레이아웃 응답과 동일하게 소비할 수 있어야 하기 때문이다.
|
||||
|
||||
최상위 키는 레이아웃 JSON 스키마를 따르며, 상속(`extends`) 병합과 모듈/플러그인 layout extension 적용이 **끝난 결과물**이다.
|
||||
|
||||
| 필드 | 타입 | 설명 |
|
||||
| --- | --- | --- |
|
||||
| version | string | 레이아웃 스키마 버전 |
|
||||
| layout_name | string | 레이아웃 식별명 |
|
||||
| meta | object | 제목·설명·`auth_required`·`is_base`·SEO 설정 등 페이지 메타 |
|
||||
| components | array | 렌더링 컴포넌트 트리 (상속 병합 + extension 주입 완료 상태) |
|
||||
| slots | object | 슬롯 정의 (베이스 레이아웃일 때) |
|
||||
| data_sources | array | API 데이터 소스 정의 |
|
||||
| computed | object | 계산된 값 정의 |
|
||||
| state / init_state / initLocal / initGlobal / initIsolated | object | 초기 상태 정의 |
|
||||
| init_actions | array | 최초 렌더 시 실행할 액션 |
|
||||
| actions / named_actions | object | 재사용 액션 정의 |
|
||||
| modals | object | 모달 정의 |
|
||||
| errorHandling | object | 에러 핸들링 정의 |
|
||||
| globalHeaders | array | 공통 요청 헤더 (`pattern` + `headers` 쌍) |
|
||||
| permissions | object | 레이아웃 권한 선언 |
|
||||
|
||||
> `extends` 키는 병합 후 결과에는 남지 않는다 (병합 입력으로만 쓰인다). 실제 포함되는 키 집합은 저장된 레이아웃 내용에 따라 달라진다.
|
||||
|
||||
**응답 예시**
|
||||
|
||||
<!-- 실측 제외: unresolved-path-param — 응답 예시는 사람이 작성하세요. -->
|
||||
```http
|
||||
HTTP/1.1 200
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"version": "1.0",
|
||||
"layout_name": "product_list",
|
||||
"meta": {
|
||||
"title": "상품 목록",
|
||||
"auth_required": false
|
||||
},
|
||||
"data_sources": [
|
||||
{
|
||||
"id": "products",
|
||||
"endpoint": "/api/modules/sirsoft-ecommerce/products",
|
||||
"method": "GET"
|
||||
}
|
||||
],
|
||||
"components": [
|
||||
{
|
||||
"type": "basic",
|
||||
"name": "Div",
|
||||
"props": { "className": "container mx-auto" },
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**에러 응답**
|
||||
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 404 | Not Found | path 파라미터에 해당하는 리소스가 없는 경우 |
|
||||
| 404 | Not Found | 토큰에 해당하는 미리보기가 없거나 **만료된 경우** (`templates.layout_not_found`). 조회 시 `notExpired()` 스코프가 적용되므로 만료 토큰은 미존재와 동일하게 404 다 |
|
||||
|
||||
<!-- @generated:end -->
|
||||
|
||||
**설명** <!-- TODO: 이 엔드포인트의 용도·주의사항·예시 시나리오를 작성하세요 -->
|
||||
**설명**
|
||||
|
||||
레이아웃 편집기에서 **저장하지 않은 편집 중 내용**을 실제 화면으로 확인하기 위한 미리보기 서빙 엔드포인트다. 인증이 아니라 **토큰 자체가 보안 메커니즘**이며(`optional.sanctum` — 비회원도 접근 가능), 토큰 유효기간은 발급 시점부터 **30분**이다.
|
||||
|
||||
미리보기 종류는 두 가지다.
|
||||
|
||||
| `preview_type` | 동작 |
|
||||
| --- | --- |
|
||||
| `layout` (기본) | 편집 중인 레이아웃 content 를 기준으로 `extends` 상속을 병합한 뒤 모듈/플러그인 extension 을 적용한다 |
|
||||
| `extension` | 대표 레이아웃을 먼저 병합하고, 편집 중인 **확장 content** 를 그 확장 자리에 임시 치환한 상태로 extension 을 적용한다 |
|
||||
|
||||
주의사항:
|
||||
|
||||
- 토큰은 만료되면 되살릴 수 없다. 편집기에서 미리보기를 다시 열면 새 토큰이 발급된다.
|
||||
- 확장 미리보기의 임시 치환은 요청 처리 중에만 유효하며, `finally` 로 항상 해제되므로 다른 요청에 새지 않는다.
|
||||
- 응답이 봉투 없는 원본 JSON 이므로, 이 URL 을 `data_sources` 로 소비할 때 `{{x?.data?.…}}` 가 아니라 최상위 키를 직접 참조한다.
|
||||
|
||||
|
||||
### GET /api/layouts/preview/{token}.json
|
||||
@@ -180,21 +245,54 @@ Authorization: Bearer {YOUR_TOKEN} (optional.sanctum: 비회원은 헤더 생
|
||||
|
||||
**응답 필드** (`data` 내부)
|
||||
|
||||
<!-- 실측 제외: unresolved-path-param — 응답 필드는 사람이 작성하세요. -->
|
||||
확장자 없는 형태(`GET /api/layouts/preview/{token}`)와 **같은 컨트롤러 메서드**이므로 응답이 동일하다. `ResponseHelper` 봉투 없이 병합·확장 적용이 끝난 **레이아웃 JSON 자체**를 최상위로 반환한다. 필드 표는 위 항목을 참조한다.
|
||||
|
||||
**응답 예시**
|
||||
|
||||
<!-- 실측 제외: unresolved-path-param — 응답 예시는 사람이 작성하세요. -->
|
||||
```http
|
||||
HTTP/1.1 200
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"version": "1.0",
|
||||
"layout_name": "product_list",
|
||||
"meta": {
|
||||
"title": "상품 목록",
|
||||
"auth_required": false
|
||||
},
|
||||
"data_sources": [
|
||||
{
|
||||
"id": "products",
|
||||
"endpoint": "/api/modules/sirsoft-ecommerce/products",
|
||||
"method": "GET"
|
||||
}
|
||||
],
|
||||
"components": [
|
||||
{
|
||||
"type": "basic",
|
||||
"name": "Div",
|
||||
"props": { "className": "container mx-auto" },
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**에러 응답**
|
||||
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 404 | Not Found | path 파라미터에 해당하는 리소스가 없는 경우 |
|
||||
| 404 | Not Found | 토큰에 해당하는 미리보기가 없거나 **만료된 경우** (`templates.layout_not_found`). 서버의 정적 최적화 블록이 `.json` 확장자를 가로채도 404 가 되므로, 이 경우 확장자 없는 형태로 재요청한다 |
|
||||
|
||||
<!-- @generated:end -->
|
||||
|
||||
**설명** <!-- TODO: 이 엔드포인트의 용도·주의사항·예시 시나리오를 작성하세요 -->
|
||||
**설명**
|
||||
|
||||
확장자 붙은 형태의 미리보기 서빙이다. 동작·토큰 수명(30분)·미리보기 종류는 확장자 없는 `GET /api/layouts/preview/{token}` 항목과 동일하다.
|
||||
|
||||
두 형태가 함께 등록되는 이유는 서버 설정 차이 때문이다. 정규식 location(`location ~* \.(js|css|json)$`)이 프리픽스 location 보다 먼저 매칭되는 nginx 구성에서는 `.json` 으로 끝나는 동적 응답이 `try_files ... /index.php` 폴백 없이 404 가 된다. 그래서 라우트는 `Route::dualSuffix()` 로 두 형태를 동시에 등록하고, 클라이언트는 `/api/system/asset-probe` 프로브 결과에 따라 어느 쪽을 쓸지 결정한다. URL 조립은 서버측 `App\Support\AssetUrl`, 프론트측 `resources/js/core/support/assetUrl.ts` 가 담당하며 직접 문자열로 조립하지 않는다.
|
||||
|
||||
|
||||
### GET /api/layouts/{templateIdentifier}/{layoutName}.json
|
||||
|
||||
@@ -369,7 +369,7 @@ _단건 응답: `data` 객체의 필드 (`UserResource::toArray()` 산물 — GE
|
||||
| created_at | string | `2026-07-08 10:41:24` | 생성 일시 (사용자 시간대 기준 문자열) |
|
||||
| updated_at | string | `2026-07-08 11:02:10` | 수정 일시 (사용자 시간대 기준 문자열) |
|
||||
| is_owner | boolean | `true` | 현재 인증 사용자가 이 리소스의 소유자인지 여부 (BaseApiResource 표준 메타) |
|
||||
| abilities | object | `{"can_read":false,"can_create":false,"can_update":false,"can_delete":false,"can_assign_roles":false}` | 현재 사용자의 이 리소스에 대한 권한 맵 (core.users.read/create/update/delete, core.permissions.update 기준. 슈퍼관리자 계정은 `can_delete` 가 항상 false) |
|
||||
| abilities | object | `{"can_read":false,"can_create":false,"can_update":false,"can_delete":false,"can_assign_roles":false}` | 현재 사용자의 이 리소스에 대한 권한 맵 (core.users.read/create/update/delete 기준. `can_assign_roles` 는 `core.users.update` — 역할 부여는 사용자 관리의 일부. 슈퍼관리자 계정은 `can_delete` 가 항상 false) |
|
||||
|
||||
관계형 필드(`modules`, `plugins`, `menus`, `roles`, `permissions`, `consents`, `terms_consent`, `privacy_consent`)와 카운트 필드(`modules_count`, `plugins_count`, `menus_count`)는 해당 관계가 로드된 경우에만 응답에 포함된다 (프로필 수정 응답에서는 로드하지 않으므로 나타나지 않는다).
|
||||
|
||||
|
||||
@@ -324,7 +324,7 @@ HTTP/1.1 201
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.menus.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.menus.create`)이 없는 경우 |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) |
|
||||
|
||||
<!-- @generated:end -->
|
||||
@@ -742,7 +742,7 @@ HTTP/1.1 200
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.menus.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.menus.update`)이 없는 경우 |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지). `moved_items[].new_parent_id` 가 자기 자신·자손을 가리키는 순환 참조인 경우 포함 |
|
||||
|
||||
<!-- @generated:end -->
|
||||
@@ -798,7 +798,7 @@ HTTP/1.1 200
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.menus.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.menus.delete`)이 없는 경우 |
|
||||
| 404 | Not Found | path 파라미터에 해당하는 리소스가 없는 경우 |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) |
|
||||
|
||||
@@ -1102,7 +1102,7 @@ HTTP/1.1 200
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.menus.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.menus.update`)이 없는 경우 |
|
||||
| 404 | Not Found | path 파라미터에 해당하는 리소스가 없는 경우 |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) |
|
||||
|
||||
@@ -1198,7 +1198,7 @@ HTTP/1.1 200
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.menus.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.menus.update`)이 없는 경우 |
|
||||
| 404 | Not Found | path 파라미터에 해당하는 리소스가 없는 경우 |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) |
|
||||
|
||||
|
||||
+108
-29
@@ -176,7 +176,7 @@ HTTP/1.1 200
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.modules.read \| core.menus.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.modules.read\|core.menus.read`)이 없는 경우 |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) |
|
||||
|
||||
<!-- @generated:end -->
|
||||
@@ -266,7 +266,7 @@ _단건 응답: `data` 객체의 필드 (`data.module` 은 목록과 동일한 `
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.modules.read \| core.menus.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.modules.activate`)이 없는 경우 |
|
||||
| 409 | Conflict | `force` 없이 호출했고 필요한 의존 확장이 미충족인 경우 (`error` 에 `warning`, `missing_modules`, `missing_plugins` 포함) |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) |
|
||||
| 500 | Internal Server Error | 활성화 처리 중 예외 발생 (`module.activate_failed`) |
|
||||
@@ -333,7 +333,7 @@ _단건 응답: `data` 객체의 필드._
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.modules.read \| core.menus.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.modules.install`)이 없는 경우 |
|
||||
| 422 | Unprocessable Entity | 업데이트 확인 처리가 실패한 경우 (`modules.check_updates_failed`) |
|
||||
| 500 | Internal Server Error | 업데이트 확인 중 예외 발생 |
|
||||
|
||||
@@ -439,7 +439,7 @@ _단건 응답: `data` 는 비활성화된 모듈의 `ModuleResource` 객체 (
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.modules.read \| core.menus.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.modules.activate`)이 없는 경우 |
|
||||
| 409 | Conflict | `force` 없이 호출했고 이 모듈에 의존하는 활성 확장이 있는 경우 (`error` 에 `warning`, `dependent_templates`, `dependent_modules`, `dependent_plugins` 포함) |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) |
|
||||
| 500 | Internal Server Error | 비활성화 처리 중 예외 발생 (`module.deactivate_failed`) |
|
||||
@@ -560,7 +560,7 @@ HTTP/1.1 201
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.modules.read \| core.menus.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.modules.install`)이 없는 경우 |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터 검증 실패, 또는 설치 파이프라인이 던진 검증 오류 (의존 확장 cascade 설치 실패·이미 설치됨 등 — `error.errors` 에 필드별 메시지) |
|
||||
| 500 | Internal Server Error | 설치 처리 중 예외 발생 (`modules.installation_failed`) |
|
||||
|
||||
@@ -671,7 +671,7 @@ HTTP/1.1 201
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.modules.read \| core.menus.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.modules.install`)이 없는 경우 |
|
||||
| 422 | Unprocessable Entity | 파일 검증 실패(ZIP 아님·50MB 초과), 또는 ZIP 처리 오류 (module.json 미존재/형식 오류·식별자 누락·이미 설치됨) |
|
||||
| 500 | Internal Server Error | 설치 처리 중 예외 발생 (`module.install_failed`) |
|
||||
|
||||
@@ -779,7 +779,7 @@ HTTP/1.1 201
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.modules.read \| core.menus.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.modules.install`)이 없는 경우 |
|
||||
| 422 | Unprocessable Entity | URL 형식 검증 실패, 또는 GitHub 처리 오류 (저장소 미존재·다운로드 실패·module.json 형식 오류·이미 설치됨) |
|
||||
| 500 | Internal Server Error | 설치 처리 중 예외 발생 (`module.install_failed`) |
|
||||
|
||||
@@ -1001,7 +1001,7 @@ _단건 응답: `data` 객체의 필드._
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.modules.read \| core.menus.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.modules.install`)이 없는 경우 |
|
||||
| 422 | Unprocessable Entity | 파일 검증 실패(ZIP 아님·50MB 초과), 또는 미리보기 처리 실패 (`module.preview_failed` — `error.error` 에 사유) |
|
||||
|
||||
<!-- @generated:end -->
|
||||
@@ -1104,7 +1104,7 @@ _단건 응답: `data` 는 갱신된 모듈의 `ModuleResource` 객체 (목록
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.modules.read \| core.menus.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.modules.activate`)이 없는 경우 |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터 검증 실패, 또는 레이아웃 갱신 실패 (모듈 미존재·비활성 상태 — `modules.refresh_layouts_failed`) |
|
||||
| 500 | Internal Server Error | 갱신 처리 중 예외 발생 (`module.refresh_layouts_failed`) |
|
||||
|
||||
@@ -1155,7 +1155,7 @@ _이 엔드포인트는 `data` 를 반환하지 않습니다 (성공 메시지
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.modules.read \| core.menus.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.modules.uninstall`)이 없는 경우 |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) |
|
||||
| 500 | Internal Server Error | 제거 처리 중 예외 발생 (`module.uninstall_failed`) |
|
||||
|
||||
@@ -1242,7 +1242,7 @@ HTTP/1.1 200
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.modules.read \| core.menus.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.modules.read`)이 없는 경우 |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) |
|
||||
|
||||
<!-- @generated:end -->
|
||||
@@ -1300,7 +1300,7 @@ HTTP/1.1 200
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.modules.read \| core.menus.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.modules.read`)이 없는 경우 |
|
||||
| 404 | Not Found | path 파라미터에 해당하는 리소스가 없는 경우 |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) |
|
||||
|
||||
@@ -1374,7 +1374,7 @@ HTTP/1.1 200
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.modules.read \| core.menus.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.modules.read`)이 없는 경우 |
|
||||
| 404 | Not Found | path 파라미터에 해당하는 리소스가 없는 경우 |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) |
|
||||
|
||||
@@ -1433,7 +1433,7 @@ HTTP/1.1 200
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.modules.read \| core.menus.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.modules.read`)이 없는 경우 |
|
||||
| 404 | Not Found | path 파라미터에 해당하는 리소스가 없는 경우 |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) |
|
||||
|
||||
@@ -1550,7 +1550,7 @@ _단건 응답: `data` 객체의 필드 (`ModuleResource::toDetailArray()` + 주
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.modules.read \| core.menus.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.modules.read`)이 없는 경우 |
|
||||
| 404 | Not Found | 해당 식별자의 모듈이 활성/_pending/_bundled 어디에도 없는 경우 (`module.not_found`) |
|
||||
| 500 | Internal Server Error | 조회 중 예외 발생 (`module.fetch_failed`) |
|
||||
|
||||
@@ -1620,7 +1620,7 @@ _단건 응답: `data` 객체의 필드._
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.modules.read \| core.menus.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.modules.read`)이 없는 경우 |
|
||||
| 404 | Not Found | 해당 식별자의 모듈이 활성/_pending/_bundled 어디에도 없는 경우 (`module.not_found`) |
|
||||
| 422 | Unprocessable Entity | 수정 레이아웃 확인 실패 (`modules.check_modified_layouts_failed` — `error.errors.module_name`) |
|
||||
| 500 | Internal Server Error | 확인 처리 중 예외 발생 |
|
||||
@@ -1728,7 +1728,7 @@ _단건 응답: `data` 객체의 필드 (`target` + `dependencies[]` + `language
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.modules.read \| core.menus.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.modules.install`)이 없는 경우 |
|
||||
| 404 | Not Found | 해당 식별자의 모듈이 활성/_pending/_bundled 어디에도 없는 경우 (`module.not_found`) |
|
||||
| 500 | Internal Server Error | 대상 확장을 찾을 수 없거나 프리뷰 빌드 중 예외 발생 (`module.fetch_failed`) |
|
||||
|
||||
@@ -1821,7 +1821,7 @@ _단건 응답: `data` 객체의 필드._
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.modules.read \| core.menus.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.modules.uninstall`)이 없는 경우 |
|
||||
| 404 | Not Found | 해당 식별자의 모듈을 찾을 수 없는 경우 (`module.not_found`) |
|
||||
| 500 | Internal Server Error | 삭제 정보 조회 중 예외 발생 (`module.uninstall_info_failed`) |
|
||||
|
||||
@@ -1932,7 +1932,7 @@ _단건 응답: `data` 는 업데이트된 모듈의 `ModuleResource` 객체 (
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.modules.read \| core.menus.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.modules.install`)이 없는 경우 |
|
||||
| 404 | Not Found | 해당 식별자의 모듈을 찾을 수 없는 경우 (`module.not_found`) |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터 검증 실패, 또는 업데이트 실패 (업데이트 소스 없음·다운그레이드 차단·코어 버전 비호환 — `error.errors.module_name` 에 사유) |
|
||||
| 500 | Internal Server Error | 업데이트 처리 중 예외 발생 (`modules.errors.update_failed`) |
|
||||
@@ -1965,11 +1965,26 @@ Accept: application/json
|
||||
|
||||
**응답 필드** (`data` 내부)
|
||||
|
||||
<!-- 실측 제외: http-422 — 응답 필드는 사람이 작성하세요. -->
|
||||
_이 엔드포인트는 표준 JSON 봉투가 아니라 **모듈 에셋 파일 본문** 을 그대로 반환한다 — `data` 구조가 없다._
|
||||
|
||||
| 항목 | 값 | 설명 |
|
||||
| --- | --- | --- |
|
||||
| Content-Type | `파일 확장자에 따른 MIME (예: text/javascript, image/png)` | 서빙 대상의 MIME 타입 |
|
||||
| Cache-Control | `public, max-age=31536000, immutable` (프로덕션) / `no-cache` (그 외) | 환경에 따라 갈린다 |
|
||||
| ETag | `{md5(mtime+size)}` | `If-None-Match` 가 일치하면 본문 없이 `304` |
|
||||
|
||||
**응답 예시**
|
||||
|
||||
<!-- 실측 제외: http-422 — 응답 예시는 사람이 작성하세요. -->
|
||||
```http
|
||||
HTTP/1.1 200
|
||||
Content-Type: text/javascript
|
||||
Cache-Control: public, max-age=31536000, immutable
|
||||
ETag: "9f2c…"
|
||||
|
||||
(function(){ /* 모듈 에셋 본문 */ })();
|
||||
```
|
||||
|
||||
> 같은 ETag 로 재요청하면 본문 없이 `304 Not Modified` 가 반환된다.
|
||||
|
||||
**에러 응답**
|
||||
|
||||
@@ -2137,15 +2152,35 @@ Accept: application/json
|
||||
|
||||
**응답 필드** (`data` 내부)
|
||||
|
||||
<!-- 실측 제외: http-200 — 응답 필드는 사람이 작성하세요. -->
|
||||
_이 엔드포인트는 표준 JSON 봉투가 아니라 **활성 모듈 CSS 를 병합한 번들 본문** 을 그대로 반환한다 — `data` 구조가 없다._
|
||||
|
||||
| 항목 | 값 | 설명 |
|
||||
| --- | --- | --- |
|
||||
| Content-Type | `text/css` | 서빙 대상의 MIME 타입 |
|
||||
| Cache-Control | `public, max-age=31536000, immutable` (프로덕션) / `no-cache` (그 외) | 환경에 따라 갈린다 |
|
||||
| ETag | `{md5(mtime+size)}` | `If-None-Match` 가 일치하면 본문 없이 `304` |
|
||||
|
||||
**응답 예시**
|
||||
|
||||
<!-- 실측 제외: http-200 — 응답 예시는 사람이 작성하세요. -->
|
||||
```http
|
||||
HTTP/1.1 200
|
||||
Content-Type: text/css
|
||||
Cache-Control: public, max-age=31536000, immutable
|
||||
ETag: "9f2c…"
|
||||
|
||||
/* module-a */ .a{}
|
||||
/* module-b */ .b{}
|
||||
```
|
||||
|
||||
> 같은 ETag 로 재요청하면 본문 없이 `304 Not Modified` 가 반환된다.
|
||||
|
||||
**에러 응답**
|
||||
|
||||
_대표 에러 없음 (공개 조회). <!-- TODO: 도메인 특이 에러가 있으면 보강 -->_
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 200 | OK (빈 본문) | 활성 확장이 없거나 병합할 에셋이 없는 경우 — 오류가 아니라 빈 번들이다 |
|
||||
|
||||
> 개별 확장의 병합이 실패하면 그 확장만 건너뛰고 나머지는 그대로 병합된다(실패 격리). 건너뛴 사실은 서버 로그(warning)에 남는다.
|
||||
|
||||
<!-- @generated:end -->
|
||||
|
||||
@@ -2172,15 +2207,36 @@ Accept: application/json
|
||||
|
||||
**응답 필드** (`data` 내부)
|
||||
|
||||
<!-- 실측 제외: http-200 — 응답 필드는 사람이 작성하세요. -->
|
||||
_이 엔드포인트는 표준 JSON 봉투가 아니라 **활성 모듈 JS(IIFE)를 병합한 번들 본문** 을 그대로 반환한다 — `data` 구조가 없다._
|
||||
|
||||
| 항목 | 값 | 설명 |
|
||||
| --- | --- | --- |
|
||||
| Content-Type | `text/javascript` | 서빙 대상의 MIME 타입 |
|
||||
| Cache-Control | `public, max-age=31536000, immutable` (프로덕션) / `no-cache` (그 외) | 환경에 따라 갈린다 |
|
||||
| ETag | `{md5(mtime+size)}` | `If-None-Match` 가 일치하면 본문 없이 `304` |
|
||||
|
||||
**응답 예시**
|
||||
|
||||
<!-- 실측 제외: http-200 — 응답 예시는 사람이 작성하세요. -->
|
||||
```http
|
||||
HTTP/1.1 200
|
||||
Content-Type: text/javascript
|
||||
Cache-Control: public, max-age=31536000, immutable
|
||||
ETag: "9f2c…"
|
||||
|
||||
(function(){/* module-a */})()
|
||||
;
|
||||
(function(){/* module-b */})()
|
||||
```
|
||||
|
||||
> 같은 ETag 로 재요청하면 본문 없이 `304 Not Modified` 가 반환된다.
|
||||
|
||||
**에러 응답**
|
||||
|
||||
_대표 에러 없음 (공개 조회). <!-- TODO: 도메인 특이 에러가 있으면 보강 -->_
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 200 | OK (빈 본문) | 활성 확장이 없거나 병합할 에셋이 없는 경우 — 오류가 아니라 빈 번들이다 |
|
||||
|
||||
> 개별 확장의 병합이 실패하면 그 확장만 건너뛰고 나머지는 그대로 병합된다(실패 격리). 건너뛴 사실은 서버 로그(warning)에 남는다.
|
||||
|
||||
<!-- @generated:end -->
|
||||
|
||||
@@ -2211,7 +2267,13 @@ Accept: application/json
|
||||
|
||||
|
||||
|
||||
<!-- 실측 응답에 필드 없음(빈 목록 등) — 데이터가 있는 상태로 재실측하거나 사람이 작성. -->
|
||||
_`data` 는 모듈 의 `components.json` 내용을 그대로 담은 **컴포넌트 맵**이다 (고정 필드 집합이 아니라 컴포넌트명 → 정의 매핑)._
|
||||
|
||||
| 필드 | 타입 | 예시값 | 용도/설명 |
|
||||
| --- | --- | --- | --- |
|
||||
| (컴포넌트명) | object | `{"type":"composite","props":{…}}` | 컴포넌트 정의. 키는 레이아웃 JSON 의 `name` 과 일치한다 |
|
||||
|
||||
> 파일이 없거나 비어 있으면 `data` 는 빈 객체(`{}`)다 — 오류가 아니다.
|
||||
|
||||
**응답 예시**
|
||||
|
||||
@@ -2281,7 +2343,24 @@ _이 엔드포인트는 표준 `success/message/data` 봉투를 사용하지 않
|
||||
|
||||
**응답 예시**
|
||||
|
||||
<!-- 실측 제외: http-404 — 응답 예시는 사람이 작성하세요. -->
|
||||
```http
|
||||
HTTP/1.1 200
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "설정을 조회했습니다.",
|
||||
"data": {
|
||||
"ProductCard": {
|
||||
"type": "composite",
|
||||
"props": {
|
||||
"product": "object"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**에러 응답**
|
||||
|
||||
|
||||
@@ -464,7 +464,7 @@ HTTP/1.1 200
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.settings.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.settings.update`)이 없는 경우 |
|
||||
| 404 | Not Found | path 파라미터에 해당하는 리소스가 없는 경우 |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) |
|
||||
|
||||
@@ -663,7 +663,7 @@ HTTP/1.1 200
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.settings.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.settings.update`)이 없는 경우 |
|
||||
| 404 | Not Found | path 파라미터에 해당하는 리소스가 없는 경우 |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) |
|
||||
|
||||
@@ -797,7 +797,7 @@ HTTP/1.1 200
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.settings.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.settings.update`)이 없는 경우 |
|
||||
| 404 | Not Found | path 파라미터에 해당하는 리소스가 없는 경우 |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) |
|
||||
|
||||
|
||||
@@ -235,7 +235,7 @@ HTTP/1.1 200
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.notification-logs.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.notification-logs.delete`)이 없는 경우 |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) |
|
||||
|
||||
<!-- @generated:end -->
|
||||
@@ -287,7 +287,7 @@ HTTP/1.1 200
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.notification-logs.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.notification-logs.delete`)이 없는 경우 |
|
||||
| 404 | Not Found | path 파라미터에 해당하는 리소스가 없는 경우 |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) |
|
||||
|
||||
|
||||
@@ -200,7 +200,7 @@ _단건 응답: `data` 객체의 필드 (`NotificationTemplateResource`)._
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.settings.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.settings.update`)이 없는 경우 |
|
||||
| 404 | Not Found | path 파라미터에 해당하는 리소스가 없는 경우 |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) |
|
||||
|
||||
@@ -302,7 +302,7 @@ HTTP/1.1 200
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.settings.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.settings.update`)이 없는 경우 |
|
||||
| 404 | Not Found | path 파라미터에 해당하는 리소스가 없는 경우 |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) |
|
||||
|
||||
@@ -404,7 +404,7 @@ HTTP/1.1 200
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.settings.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.settings.update`)이 없는 경우 |
|
||||
| 404 | Not Found | path 파라미터에 해당하는 리소스가 없는 경우 |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) |
|
||||
|
||||
|
||||
@@ -202,7 +202,7 @@ HTTP/1.1 200
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.notifications.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.notifications.delete`)이 없는 경우 |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) |
|
||||
|
||||
<!-- @generated:end -->
|
||||
@@ -260,7 +260,7 @@ HTTP/1.1 200
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.notifications.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.notifications.update`)이 없는 경우 |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) |
|
||||
|
||||
<!-- @generated:end -->
|
||||
@@ -329,7 +329,7 @@ HTTP/1.1 200
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.notifications.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.notifications.update`)이 없는 경우 |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) |
|
||||
|
||||
<!-- @generated:end -->
|
||||
@@ -441,7 +441,7 @@ HTTP/1.1 200
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.notifications.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.notifications.delete`)이 없는 경우 |
|
||||
| 404 | Not Found | path 파라미터에 해당하는 리소스가 없는 경우 |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) |
|
||||
|
||||
@@ -526,7 +526,7 @@ HTTP/1.1 200
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.notifications.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.notifications.update`)이 없는 경우 |
|
||||
| 404 | Not Found | path 파라미터에 해당하는 리소스가 없는 경우 |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) |
|
||||
|
||||
@@ -664,7 +664,7 @@ HTTP/1.1 200
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.notifications.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.user-notifications.read`)이 없는 경우 |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) |
|
||||
|
||||
<!-- @generated:end -->
|
||||
@@ -722,7 +722,7 @@ HTTP/1.1 200
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.notifications.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.user-notifications.delete`)이 없는 경우 |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) |
|
||||
|
||||
<!-- @generated:end -->
|
||||
@@ -780,7 +780,7 @@ HTTP/1.1 200
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.notifications.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.user-notifications.update`)이 없는 경우 |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) |
|
||||
|
||||
<!-- @generated:end -->
|
||||
@@ -849,7 +849,7 @@ HTTP/1.1 200
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.notifications.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.user-notifications.update`)이 없는 경우 |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) |
|
||||
|
||||
<!-- @generated:end -->
|
||||
@@ -907,7 +907,7 @@ HTTP/1.1 200
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.notifications.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.user-notifications.read`)이 없는 경우 |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) |
|
||||
|
||||
<!-- @generated:end -->
|
||||
@@ -961,7 +961,7 @@ HTTP/1.1 200
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.notifications.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.user-notifications.delete`)이 없는 경우 |
|
||||
| 404 | Not Found | path 파라미터에 해당하는 리소스가 없는 경우 |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) |
|
||||
|
||||
@@ -1046,7 +1046,7 @@ HTTP/1.1 200
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.notifications.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.user-notifications.update`)이 없는 경우 |
|
||||
| 404 | Not Found | path 파라미터에 해당하는 리소스가 없는 경우 |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) |
|
||||
|
||||
|
||||
@@ -279,7 +279,7 @@ _단건 응답: `data` 객체의 필드 (`UserResource::toArray()` 산물 — `s
|
||||
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 403 | Forbidden | 요구 권한(`core.profile.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.profile.update`)이 없는 경우 |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) |
|
||||
|
||||
<!-- @generated:end -->
|
||||
|
||||
@@ -273,7 +273,7 @@ HTTP/1.1 201
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.permissions.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.permissions.create`)이 없는 경우 |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) |
|
||||
|
||||
<!-- @generated:end -->
|
||||
@@ -414,7 +414,7 @@ HTTP/1.1 200
|
||||
|
||||
**설명**
|
||||
|
||||
셀렉트 UI(사용자 폼·메뉴 편집의 역할 선택 등)에 채울 활성 역할 목록을 제공한다. 별도 권한 미들웨어가 없어 인증만 되면 호출 가능하지만, 내부에서 권한에 따라 범위가 갈린다. `core.permissions.read` 권한 보유자는 전체 활성 역할을 받고(사용자에게 역할을 부여하는 관리 용도), 미보유자는 자신에게 부여된 활성 역할만 받는다(자기 정보 폼 표시 용도). 응답의 `abilities.can_assign_roles` 는 `core.permissions.update` 권한 보유 여부를 나타낸다.
|
||||
셀렉트 UI(사용자 폼·메뉴 편집의 역할 선택 등)에 채울 활성 역할 목록을 제공한다. 별도 권한 미들웨어가 없어 인증만 되면 호출 가능하지만, 내부에서 권한에 따라 범위가 갈린다. `core.permissions.read` 권한 보유자는 전체 활성 역할을 받고(사용자에게 역할을 부여하는 관리 용도), 미보유자는 자신에게 부여된 활성 역할만 받는다(자기 정보 폼 표시 용도). 응답의 `abilities.can_assign_roles` 는 `core.users.update`(사용자 관리) 권한 보유 여부를 나타낸다 — 역할 부여는 사용자 관리의 일부이지 역할 정의 수정(`core.permissions.update`)이 아니다. 부여 가능한 개별 역할의 범위는 서버 상한(권한 상승 가드)이 역할별로 강제한다.
|
||||
|
||||
|
||||
### DELETE /api/admin/roles/{role}
|
||||
@@ -444,14 +444,24 @@ _이 엔드포인트는 `data` 를 반환하지 않습니다 (성공 메시지
|
||||
|
||||
**응답 예시**
|
||||
|
||||
<!-- 실측 제외: http-403 — 응답 예시는 사람이 작성하세요. -->
|
||||
```http
|
||||
HTTP/1.1 200
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "역할이 성공적으로 삭제되었습니다.",
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
**에러 응답**
|
||||
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.permissions.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.permissions.delete`)이 없는 경우 |
|
||||
| 404 | Not Found | path 파라미터에 해당하는 리소스가 없는 경우 |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) |
|
||||
|
||||
@@ -652,7 +662,7 @@ HTTP/1.1 200
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.permissions.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.permissions.update`)이 없는 경우 |
|
||||
| 404 | Not Found | path 파라미터에 해당하는 리소스가 없는 경우 |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) |
|
||||
|
||||
@@ -754,7 +764,7 @@ HTTP/1.1 200
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.permissions.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.permissions.update`)이 없는 경우 |
|
||||
| 404 | Not Found | path 파라미터에 해당하는 리소스가 없는 경우 |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) |
|
||||
|
||||
|
||||
@@ -286,7 +286,7 @@ HTTP/1.1 201
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.schedules.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.schedules.create`)이 없는 경우 |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) |
|
||||
|
||||
<!-- @generated:end -->
|
||||
@@ -346,7 +346,7 @@ HTTP/1.1 200
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.schedules.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.schedules.delete`)이 없는 경우 |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) |
|
||||
|
||||
<!-- @generated:end -->
|
||||
@@ -415,7 +415,7 @@ HTTP/1.1 200
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.schedules.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.schedules.update`)이 없는 경우 |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) |
|
||||
|
||||
<!-- @generated:end -->
|
||||
@@ -467,7 +467,7 @@ HTTP/1.1 200
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.schedules.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.schedules.delete`)이 없는 경우 |
|
||||
| 404 | Not Found | 지정한 `historyId` 의 실행 이력이 존재하지 않는 경우 (`schedule.history_not_found`) |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) |
|
||||
|
||||
@@ -588,7 +588,7 @@ HTTP/1.1 200
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.schedules.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.schedules.delete`)이 없는 경우 |
|
||||
| 404 | Not Found | path 파라미터에 해당하는 리소스가 없는 경우 |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) |
|
||||
|
||||
@@ -842,7 +842,7 @@ HTTP/1.1 200
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.schedules.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.schedules.update`)이 없는 경우 |
|
||||
| 404 | Not Found | path 파라미터에 해당하는 리소스가 없는 경우 |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) |
|
||||
|
||||
@@ -946,7 +946,7 @@ HTTP/1.1 201
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.schedules.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.schedules.create`)이 없는 경우 |
|
||||
| 404 | Not Found | path 파라미터에 해당하는 리소스가 없는 경우 |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) |
|
||||
|
||||
@@ -1147,7 +1147,7 @@ HTTP/1.1 200
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.schedules.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.schedules.run`)이 없는 경우 |
|
||||
| 404 | Not Found | path 파라미터에 해당하는 리소스가 없는 경우 |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) |
|
||||
|
||||
|
||||
@@ -133,7 +133,7 @@ HTTP/1.1 200
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.settings.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.settings.update`)이 없는 경우 |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) |
|
||||
| 500 | Internal Server Error | 캐시 무효화(`invalidateByLayout` / `clearAll`) 중 예외가 발생한 경우 (`messages.error_occurred`) |
|
||||
|
||||
@@ -206,7 +206,7 @@ HTTP/1.1 200
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.settings.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.settings.update`)이 없는 경우 |
|
||||
|
||||
<!-- @generated:end -->
|
||||
|
||||
@@ -411,7 +411,7 @@ HTTP/1.1 200
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.settings.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.settings.update`)이 없는 경우 |
|
||||
| 500 | Internal Server Error | 워밍업 처리 중 예외가 발생한 경우 (`messages.error_occurred`) |
|
||||
|
||||
<!-- @generated:end -->
|
||||
|
||||
@@ -72,7 +72,7 @@ _단건 응답: `data` 객체의 필드._
|
||||
| notifications | object | `{"channels":[{"id":"mail","is_active":true,"sort_order":1…` | 알림 탭 설정 그룹. channels 는 알림 채널 목록으로 각 원소가 id(채널 식별자)·is_active(활성 여부)·sort_order(표시 순서)를 가짐 |
|
||||
| identity | object | `{"default_provider":"g7:core.mail","purpose_providers":{"…` | 본인인증(IDV) 탭 설정 그룹 (기본 provider·목적별 provider 매핑(purpose_providers)·챌린지 유효시간(분)·최대 시도 횟수) |
|
||||
| available_drivers | object | `{"storage":[{"id":"local","label":{"ko":"로컬","en":"Local"…` | 드라이버 선택지 카탈로그 (DriverRegistryService 산물). 종류별(storage/public_asset/cache/session/queue 등) 선택 가능한 드라이버 목록을 id/다국어 label 형태로 제공. `public_asset` 은 공개 자산 직접 URL 서빙 디스크 선택지 (코어 none/public/s3 + 플러그인 훅 등록분) |
|
||||
| _meta | object | `{"limits":{"upload_max_file_size_min":1,"upload_max_file_…` | 화면 검증 메타 — `limits` 는 각 설정 항목의 min/max 경계값 맵 (`config/core.php` 의 `settings_limits` 가 SSoT, 화면 입력 힌트와 FormRequest 검증이 같은 값을 공유) |
|
||||
| _meta | object | `{"limits":{"upload_max_file_size_min":1,"upload_max_file_…` | 설정값이 아니라 화면이 쓰는 메타. `limits` 는 각 설정 항목의 min/max 경계값 맵 (`config/core.php` 의 `settings_limits` 가 SSoT, 화면 입력 힌트와 FormRequest 검증이 같은 값을 공유) |
|
||||
| abilities | object | `{"can_update":true}` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) |
|
||||
|
||||
**응답 예시**
|
||||
@@ -510,7 +510,7 @@ _단건 응답: `data` 객체의 필드._
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.settings.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.settings.update`)이 없는 경우 |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) |
|
||||
|
||||
<!-- @generated:end -->
|
||||
@@ -623,7 +623,7 @@ _단건 응답: `data` 객체의 필드._
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.settings.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.settings.update`)이 없는 경우 |
|
||||
| 500 | Internal Server Error | 백업 파일 생성에 실패한 경우 (`settings.backup_failed`) |
|
||||
|
||||
<!-- @generated:end -->
|
||||
@@ -672,7 +672,7 @@ _이 엔드포인트는 `data` 를 반환하지 않습니다 (성공 메시지
|
||||
| --- | --- | --- |
|
||||
| 400 | Bad Request | 백업이 수행되지 않은 경우 (`settings.backup_failed`) |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.settings.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.settings.update`)이 없는 경우 |
|
||||
| 500 | Internal Server Error | 백업 처리 중 예외가 발생한 경우 (`settings.backup_error`) |
|
||||
|
||||
<!-- @generated:end -->
|
||||
@@ -721,7 +721,7 @@ _이 엔드포인트는 `data` 를 반환하지 않습니다 (성공 메시지
|
||||
| --- | --- | --- |
|
||||
| 400 | Bad Request | 캐시 정리가 수행되지 않은 경우 (`settings.cache_clear_failed`) |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.settings.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.settings.update`)이 없는 경우 |
|
||||
| 500 | Internal Server Error | 캐시 정리 중 예외가 발생한 경우 (`settings.cache_clear_error`) |
|
||||
|
||||
<!-- @generated:end -->
|
||||
@@ -782,7 +782,7 @@ _단건 응답: `data` 객체의 필드 (GeoIpDatabaseService::updateDatabase()
|
||||
| --- | --- | --- |
|
||||
| 400 | Bad Request | MaxMind 라이선스 키가 설정되지 않은 경우 (`missing_license_key`) |
|
||||
| 401 | Unauthorized | MaxMind 라이선스 키가 유효하지 않은 경우 (`unauthorized`) |
|
||||
| 403 | Forbidden | 요구 권한(`core.settings.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.settings.update`)이 없는 경우 |
|
||||
| 500 | Internal Server Error | MaxMind 연결 실패(`connection_failed`) 또는 다운로드·압축 해제 실패 |
|
||||
|
||||
<!-- @generated:end -->
|
||||
@@ -836,7 +836,7 @@ HTTP/1.1 200
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.settings.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.settings.update`)이 없는 경우 |
|
||||
|
||||
<!-- @generated:end -->
|
||||
|
||||
@@ -898,7 +898,7 @@ _단건 응답: `data` 객체의 필드._
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthorized | 본문 `password` 가 요청자 본인의 비밀번호와 일치하지 않는 경우 (`settings.invalid_password`) |
|
||||
| 403 | Forbidden | 요구 권한(`core.settings.read`)이 없거나, FormRequest 가 `super_admin` 역할이 아닌 사용자를 거부한 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.settings.update`)이 없거나, FormRequest 가 `super_admin` 역할이 아닌 사용자를 거부한 경우 |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) |
|
||||
| 500 | Internal Server Error | `.env` 기록/config 캐시 재생성 실패 (`settings.app_key_regenerate_failed`) |
|
||||
|
||||
@@ -957,7 +957,7 @@ _이 엔드포인트는 `data` 를 반환하지 않습니다 (성공 메시지
|
||||
| --- | --- | --- |
|
||||
| 400 | Bad Request | 복원이 수행되지 않은 경우 (`settings.restore_failed`) |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.settings.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.settings.update`)이 없는 경우 |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) |
|
||||
| 500 | Internal Server Error | 백업 파일을 읽을 수 없는 등 복원 중 예외 발생 (`settings.restore_error`) |
|
||||
|
||||
@@ -1007,7 +1007,7 @@ _단건 응답: `data` 객체의 필드._
|
||||
| php_memory_limit | string | `512M` | PHP `memory_limit` ini 값 |
|
||||
| max_execution_time | string | `36000초` | PHP `max_execution_time` ini 값 (초 단위 접미사 부착) |
|
||||
| upload_max_filesize | string | `2G` | PHP `upload_max_filesize` ini 값 |
|
||||
| opcache | object | `{"loaded":true,"enabled":true}` | PHP OPcache 상태 — `loaded`(확장 로드 여부) / `enabled`(런타임 활성화 여부, `opcache.enable` 설정 기준) |
|
||||
| opcache | object | `{"loaded":true,"enabled":true}` | OPcache 상태 (`OpcacheStatus::probe()`). `loaded` 는 확장 적재 여부, `enabled` 는 `opcache.enable` 지시자 값이며 `ini_get` 이 차단·미정의인 환경에서는 **확인 불가를 뜻하는 `null`** 이 된다 (false 와 구분된다) |
|
||||
| install_path | string | `C:\Users\HeuJung\htdocs\g7_2` | 애플리케이션 설치 루트 경로 (`base_path()`) |
|
||||
| config_path | string | `C:\Users\HeuJung\htdocs\g7_2\storage\…` | 설정 파일 저장 경로 (`storage/app/settings`) |
|
||||
| log_path | string | `C:\Users\HeuJung\htdocs\g7_2\storage\…` | 로그 파일 저장 경로 (`storage/logs`) |
|
||||
@@ -1236,7 +1236,7 @@ _단건 응답: `data` 객체의 필드 (DriverConnectionTester::testAll() 산
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.settings.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.settings.update`)이 없는 경우 |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) |
|
||||
| 500 | Internal Server Error | 테스트 실행 중 예외가 발생한 경우 (`settings.driver_test_error`) |
|
||||
|
||||
@@ -1330,7 +1330,7 @@ _단건 응답: `data` 객체의 필드 (발송 성공 시에만 반환)._
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.settings.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.settings.update`)이 없는 경우 |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) |
|
||||
| 500 | Internal Server Error | 발송 실패 시 — `message` 는 `테스트 메일 발송에 실패했습니다.`, `error` 에 원본 예외 메시지(SMTP 인증 실패·연결 거부 등)가 담김 |
|
||||
|
||||
@@ -1449,7 +1449,7 @@ _이 엔드포인트는 `data` 를 반환하지 않습니다 (성공 메시지
|
||||
| --- | --- | --- |
|
||||
| 400 | Bad Request | 저장이 수행되지 않은 경우 (`settings.update_failed`) |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.settings.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.settings.update`)이 없는 경우 |
|
||||
| 404 | Not Found | path 파라미터에 해당하는 리소스가 없는 경우 |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) |
|
||||
| 500 | Internal Server Error | 저장 중 예외가 발생한 경우 (`settings.update_error`) |
|
||||
|
||||
@@ -37,19 +37,50 @@ Accept: application/json
|
||||
|
||||
**응답 필드** (`data` 내부)
|
||||
|
||||
<!-- 실측 제외: http-200 — 응답 필드는 사람이 작성하세요. -->
|
||||
이 엔드포인트는 JSON 봉투(`success`/`message`/`data`)를 쓰지 않는다. 정적 자산으로 오인될 응답을 그대로 흉내내는 것이 목적이라, 본문은 자바스크립트이고 `data` 구조가 존재하지 않는다.
|
||||
|
||||
| 항목 | 값 | 설명 |
|
||||
| --- | --- | --- |
|
||||
| Content-Type | `application/javascript; charset=utf-8` | 정적 `.js` 응답과 동일한 형태 |
|
||||
| Cache-Control | `no-store, no-cache, must-revalidate, max-age=0` | 프로브는 매 요청 실측이어야 하므로 캐시 금지 |
|
||||
| Pragma | `no-cache` | 구형 프록시 대응 |
|
||||
| X-Content-Type-Options | `nosniff` | MIME 스니핑 차단 |
|
||||
| 본문 매직 토큰 | `G7_ASSET_PROBE_OK` | 성공 판정용. 상태코드가 아니라 **이 토큰의 존재**로 판정한다 |
|
||||
|
||||
**응답 예시**
|
||||
|
||||
<!-- 실측 제외: http-200 — 응답 예시는 사람이 작성하세요. -->
|
||||
```http
|
||||
HTTP/1.1 200
|
||||
Content-Type: application/javascript; charset=utf-8
|
||||
Cache-Control: no-store, no-cache, must-revalidate, max-age=0
|
||||
```
|
||||
|
||||
```javascript
|
||||
/* G7 asset URL mode probe */
|
||||
window.__g7AssetProbe = 'G7_ASSET_PROBE_OK';
|
||||
```
|
||||
|
||||
**에러 응답**
|
||||
|
||||
_대표 에러 없음 (공개 조회). <!-- TODO: 도메인 특이 에러가 있으면 보강 -->_
|
||||
_에러 응답이 정의되어 있지 않다. 이 라우트는 DB 에 접근하지 않으므로 설치 전에도 응답하며, 도달하기만 하면 항상 `200` 이다. 도달하지 못해 `404`/`5xx` 가 관측되면 그것은 에러가 아니라 **판정 입력**이다 (아래 설명 참조)._
|
||||
|
||||
<!-- @generated:end -->
|
||||
|
||||
**설명** <!-- TODO: 이 엔드포인트의 용도·주의사항·예시 시나리오를 작성하세요 -->
|
||||
**설명**
|
||||
|
||||
서버(nginx/Apache)의 정적 최적화 블록이 확장자 붙은 동적 응답을 가로채는지 판정하기 위한 **대조군** 엔드포인트다. 확장자가 없으므로 정적 블록의 표적이 되지 않는다. 클라이언트는 이 URL 과 `/api/system/asset-probe.js` 를 쌍으로 요청해 다음과 같이 판정한다.
|
||||
|
||||
| `asset-probe.js` | `asset-probe` (본 엔드포인트) | 판정 |
|
||||
| --- | --- | --- |
|
||||
| 성공 | 성공 | `extension` — 확장자 붙은 URL 을 그대로 써도 된다 |
|
||||
| 실패 | 성공 | `extensionless` — 정적 블록 가로채기 확정. 확장자 없는 형태로 전환한다 |
|
||||
| 실패 | 실패 | 자산 URL 모드 문제가 아니다 (PHP/라우팅 장애) — 별도 안내 |
|
||||
|
||||
주의사항:
|
||||
|
||||
- **판정은 상태코드가 아니라 본문으로 한다.** `res.ok && body.includes('G7_ASSET_PROBE_OK')` 로 확인해야 한다. 상태코드만 보면 "404 대신 200 + 에러 HTML" 이나 catch-all 200 페이지를 반환하는 설정에서 영원히 `extension` 으로 오판하고, 재감지를 몇 번 눌러도 같은 오답이 나온다.
|
||||
- **감지는 반드시 브라우저에서 수행한다.** 서버측에서 자기 `APP_URL` 로 curl 하면 loopback 이 nginx vhost·SSL·프록시 체인을 우회하거나 다른 vhost 를 타서 오판한다.
|
||||
- **`public/` 하위에 실물 `asset-probe.js` 를 두지 않는다.** 실제 파일이 있으면 nginx 가 그것을 성공적으로 서빙해 거짓 양성이 된다.
|
||||
|
||||
|
||||
### GET /api/system/asset-probe.js
|
||||
@@ -72,17 +103,49 @@ Accept: application/json
|
||||
|
||||
**응답 필드** (`data` 내부)
|
||||
|
||||
<!-- 실측 제외: http-404 — 응답 필드는 사람이 작성하세요. -->
|
||||
확장자 없는 대조군(`GET /api/system/asset-probe`)과 **같은 컨트롤러 메서드**이므로 응답 형태가 동일하다. JSON 봉투를 쓰지 않으며 `data` 구조가 없다.
|
||||
|
||||
| 항목 | 값 | 설명 |
|
||||
| --- | --- | --- |
|
||||
| Content-Type | `application/javascript; charset=utf-8` | 정적 `.js` 응답과 동일한 형태 |
|
||||
| Cache-Control | `no-store, no-cache, must-revalidate, max-age=0` | 프로브는 매 요청 실측이어야 하므로 캐시 금지 |
|
||||
| Pragma | `no-cache` | 구형 프록시 대응 |
|
||||
| X-Content-Type-Options | `nosniff` | MIME 스니핑 차단 |
|
||||
| 본문 매직 토큰 | `G7_ASSET_PROBE_OK` | 성공 판정용. 상태코드가 아니라 **이 토큰의 존재**로 판정한다 |
|
||||
|
||||
**응답 예시**
|
||||
|
||||
<!-- 실측 제외: http-404 — 응답 예시는 사람이 작성하세요. -->
|
||||
애플리케이션까지 도달했을 때 (`extension` 모드 가능):
|
||||
|
||||
```http
|
||||
HTTP/1.1 200
|
||||
Content-Type: application/javascript; charset=utf-8
|
||||
Cache-Control: no-store, no-cache, must-revalidate, max-age=0
|
||||
```
|
||||
|
||||
```javascript
|
||||
/* G7 asset URL mode probe */
|
||||
window.__g7AssetProbe = 'G7_ASSET_PROBE_OK';
|
||||
```
|
||||
|
||||
서버의 정적 최적화 블록이 가로챘을 때 (`extensionless` 모드 확정) — 응답은 서버 설정에 좌우되며 매직 토큰이 없다:
|
||||
|
||||
```http
|
||||
HTTP/1.1 404
|
||||
Content-Type: text/html
|
||||
```
|
||||
|
||||
> 위 문서의 실측이 `404` 로 관측된 것이 이 경우다. 정규식 location(`location ~* \.(js|css|json)$`)이 프리픽스 location 보다 먼저 매칭되어, 확장자 붙은 동적 응답이 `try_files ... /index.php` 폴백 기회 없이 404 가 된다.
|
||||
|
||||
**에러 응답**
|
||||
|
||||
_대표 에러 없음 (공개 조회). <!-- TODO: 도메인 특이 에러가 있으면 보강 -->_
|
||||
_에러 응답이 정의되어 있지 않다. 이 라우트는 DB 에 접근하지 않으므로 애플리케이션에 도달하면 항상 `200` 이다. `404`/`5xx` 는 에러가 아니라 **판정 입력**이며, 대조군이 성공했다면 `extensionless` 모드로 확정한다._
|
||||
|
||||
<!-- @generated:end -->
|
||||
|
||||
**설명** <!-- TODO: 이 엔드포인트의 용도·주의사항·예시 시나리오를 작성하세요 -->
|
||||
**설명**
|
||||
|
||||
자산 URL 모드 감지의 **표적** 엔드포인트다. 확장자(`.js`)로 끝나므로 서버의 정적 최적화 블록이 가로채는지 여부가 그대로 드러난다. 판정표와 주의사항은 대조군 `GET /api/system/asset-probe` 항목을 참조한다.
|
||||
|
||||
이 프로브가 실패하고 대조군이 성공하면, 코드는 확장자 없는 URL 형태를 써야 한다. 서버측 URL 조립은 `App\Support\AssetUrl`, 프론트측은 `resources/js/core/support/assetUrl.ts` 가 같은 규칙을 공유하므로 한쪽만 바꾸면 그 자산만 404 가 된다. 라우트 등록은 단일 `Route::get()` 이 아니라 `Route::dualSuffix()` / `dualSuffixSegment()` / `dualAsset()` 로 확장자 형태와 확장자 없는 형태를 동시에 등록한다.
|
||||
|
||||
+286
-59
@@ -293,7 +293,7 @@ _단건 응답: `data` 객체의 필드._
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.templates.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.templates.activate`)이 없는 경우 |
|
||||
| 409 | Conflict | 필요한 의존 모듈/플러그인이 충족되지 않은 경우 (`errors` 에 `warning`, `missing_modules`, `missing_plugins`, `message`) — `force=true` 로 우회 가능 |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) |
|
||||
| 500 | Server Error | 활성화 처리 실패 (이미 활성 상태·미설치·코어 버전 비호환 등) |
|
||||
@@ -360,7 +360,7 @@ _단건 응답: `data` 객체의 필드._
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.templates.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.templates.install`)이 없는 경우 |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) |
|
||||
| 500 | Server Error | 업데이트 확인 처리 실패 (GitHub API 호출 실패 등) |
|
||||
|
||||
@@ -470,7 +470,7 @@ _단건 응답: `data` 객체의 필드 (TemplateResource)._
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.templates.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.templates.activate`)이 없는 경우 |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) |
|
||||
| 500 | Server Error | 비활성화 처리 실패 (템플릿 미존재 등) |
|
||||
|
||||
@@ -590,7 +590,7 @@ _단건 응답: `data` 객체의 필드 (TemplateResource + cascade 결과)._
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.templates.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.templates.install`)이 없는 경우 |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터 검증 위반 또는 설치 실패 (이미 설치됨·manifest 오류·cascade 의존 확장 설치 실패 등 — `errors` 에 번역된 사유) |
|
||||
| 500 | Server Error | 설치 처리 중 예기치 못한 오류 |
|
||||
|
||||
@@ -703,7 +703,7 @@ _단건 응답: `data` 객체의 필드 (TemplateResource — 목록 응답 항
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.templates.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.templates.install`)이 없는 경우 |
|
||||
| 422 | Unprocessable Entity | 파일 검증 위반 또는 ZIP 처리 실패 (template.json 누락/무효, 이미 설치된 식별자, 잘못된 디렉토리명 등) |
|
||||
| 500 | Server Error | 설치 처리 중 예기치 못한 오류 |
|
||||
|
||||
@@ -813,7 +813,7 @@ _단건 응답: `data` 객체의 필드 (TemplateResource — 목록 응답 항
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.templates.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.templates.install`)이 없는 경우 |
|
||||
| 422 | Unprocessable Entity | URL 검증 위반 또는 설치 실패 (유효하지 않은 GitHub URL, 저장소 없음, 다운로드 실패, 이미 설치된 식별자 등) |
|
||||
| 500 | Server Error | 설치 처리 중 예기치 못한 오류 |
|
||||
|
||||
@@ -862,7 +862,7 @@ _이 엔드포인트는 `data` 를 반환하지 않습니다 (`data`: `null`,
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.templates.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.templates.layouts.edit`)이 없는 경우 |
|
||||
| 404 | Not Found | path 파라미터에 해당하는 리소스가 없는 경우 |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) |
|
||||
| 500 | Server Error | 첨부 파일 삭제 실패 (스토리지/DB 삭제 실패 — `첨부 파일 삭제에 실패했습니다.`) |
|
||||
@@ -944,7 +944,7 @@ _단건 응답: `data` 객체의 필드._
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.templates.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.templates.install`)이 없는 경우 |
|
||||
| 422 | Unprocessable Entity | 파일 검증 위반 또는 ZIP 열기 실패 (`manifest 미리보기에 실패했습니다.` + `errors.error`) |
|
||||
|
||||
<!-- @generated:end -->
|
||||
@@ -1053,7 +1053,7 @@ _단건 응답: `data` 객체의 필드 (TemplateResource — 갱신 후 템플
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.templates.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.templates.activate`)이 없는 경우 |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터 검증 위반 또는 레이아웃 갱신 실패 (레이아웃 JSON 무효, layout_name 누락 등) |
|
||||
| 500 | Server Error | 레이아웃 갱신 처리 중 예기치 못한 오류 |
|
||||
|
||||
@@ -1105,7 +1105,7 @@ _이 엔드포인트는 `data` 를 반환하지 않습니다 (`data`: `null`,
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.templates.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.templates.uninstall`)이 없는 경우 |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터 검증 위반 또는 제거 실패 (활성 상태·파일 삭제 실패 등 — `errors.identifier` 에 번역된 사유) |
|
||||
| 500 | Server Error | 제거 처리 중 예기치 못한 오류 |
|
||||
|
||||
@@ -1319,7 +1319,7 @@ HTTP/1.1 200
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.templates.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.templates.layouts.edit`)이 없는 경우 |
|
||||
| 404 | Not Found | path 파라미터에 해당하는 리소스가 없는 경우 |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) |
|
||||
|
||||
@@ -1426,18 +1426,41 @@ Authorization: Bearer {YOUR_TOKEN}
|
||||
|
||||
**응답 필드** (`data` 내부)
|
||||
|
||||
<!-- 실측 제외: http-404 — 응답 필드는 사람이 작성하세요. -->
|
||||
_`data` 객체의 필드 (`BroadcastCatalogService::collect` + 요청 식별자)._
|
||||
|
||||
| 필드 | 타입 | 예시값 | 용도/설명 |
|
||||
| --- | --- | --- | --- |
|
||||
| identifier | string | `sirsoft-admin_basic` | 요청한 템플릿 식별자 (요청값 반향) |
|
||||
| channels | array | `[{"name":"core.notifications","source":{"kind":"core"}}]` | 구독 가능한 브로드캐스트 채널 목록. `source.kind` 는 `core`/`module`/`plugin` 이며 확장 채널에는 `source.identifier` 가 붙는다 (활성 확장만 수집) |
|
||||
| events | array | `[]` | 정적 이벤트 카탈로그. 이벤트는 동적 발행이라 **항상 빈 배열**이며, 편집기는 자유 텍스트 입력으로 폴백한다 |
|
||||
|
||||
**응답 예시**
|
||||
|
||||
<!-- 실측 제외: http-404 — 응답 예시는 사람이 작성하세요. -->
|
||||
```http
|
||||
HTTP/1.1 200
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "요청이 성공했습니다.",
|
||||
"data": {
|
||||
"identifier": "sirsoft-admin_basic",
|
||||
"channels": [
|
||||
{ "name": "core.notifications", "source": { "kind": "core" } },
|
||||
{ "name": "module.sirsoft-board.posts", "source": { "kind": "module", "identifier": "sirsoft-board" } }
|
||||
],
|
||||
"events": []
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**에러 응답**
|
||||
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.templates.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.templates.layouts.edit`)이 없는 경우 |
|
||||
| 404 | Not Found | path 파라미터에 해당하는 리소스가 없는 경우 |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) |
|
||||
|
||||
@@ -1517,18 +1540,31 @@ Authorization: Bearer {YOUR_TOKEN}
|
||||
|
||||
**응답 필드** (`data` 내부)
|
||||
|
||||
<!-- 실측 제외: http-404 — 응답 필드는 사람이 작성하세요. -->
|
||||
_이 엔드포인트는 표준 JSON 봉투가 아니라 **편집기 미리보기용 컴포넌트 CSS 본문** 을 그대로 반환한다 — `data` 구조가 없다._
|
||||
|
||||
| 항목 | 값 | 설명 |
|
||||
| --- | --- | --- |
|
||||
| Content-Type | `text/css; charset=UTF-8` | 서빙 대상의 MIME 타입 |
|
||||
| Cache-Control | `public, max-age=31536000, immutable` (프로덕션) / `no-cache` (그 외) | 환경에 따라 갈린다 |
|
||||
| ETag | `{md5(mtime+size)}` | `If-None-Match` 가 일치하면 본문 없이 `304` |
|
||||
|
||||
**응답 예시**
|
||||
|
||||
<!-- 실측 제외: http-404 — 응답 예시는 사람이 작성하세요. -->
|
||||
```http
|
||||
HTTP/1.1 200
|
||||
Content-Type: text/css; charset=UTF-8
|
||||
|
||||
.g7-card{border-radius:.5rem}
|
||||
```
|
||||
|
||||
> CSS 가 없는 템플릿도 **빈 본문 200** 으로 응답한다 — 편집기 부팅이 실패하지 않게 하기 위한 폴백이며 404 가 아니다.
|
||||
|
||||
**에러 응답**
|
||||
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.templates.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.templates.layouts.edit`)이 없는 경우 |
|
||||
| 404 | Not Found | path 파라미터에 해당하는 리소스가 없는 경우 |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) |
|
||||
|
||||
@@ -1626,18 +1662,36 @@ Authorization: Bearer {YOUR_TOKEN}
|
||||
|
||||
**응답 필드** (`data` 내부)
|
||||
|
||||
<!-- 실측 제외: http-404 — 응답 필드는 사람이 작성하세요. -->
|
||||
_`data` 는 템플릿의 `components.json` 내용을 그대로 담은 **컴포넌트 맵**이다 (고정 필드 집합이 아니라 컴포넌트명 → 정의 매핑)._
|
||||
|
||||
| 필드 | 타입 | 예시값 | 용도/설명 |
|
||||
| --- | --- | --- | --- |
|
||||
| (컴포넌트명) | object | `{"type":"basic","tag":"div"}` | 컴포넌트 정의. 키는 레이아웃 JSON 의 `name` 과 일치한다 |
|
||||
|
||||
> 활성/`_bundled` 어디에도 `components.json` 이 없으면 `404`(`templates.layout_not_found`)다.
|
||||
|
||||
**응답 예시**
|
||||
|
||||
<!-- 실측 제외: http-404 — 응답 예시는 사람이 작성하세요. -->
|
||||
```http
|
||||
HTTP/1.1 200
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "설정을 조회했습니다.",
|
||||
"data": {
|
||||
"Card": { "type": "composite", "props": { "title": "string" } }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**에러 응답**
|
||||
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.templates.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.templates.layouts.edit`)이 없는 경우 |
|
||||
| 404 | Not Found | path 파라미터에 해당하는 리소스가 없는 경우 |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) |
|
||||
|
||||
@@ -1736,11 +1790,32 @@ Authorization: Bearer {YOUR_TOKEN}
|
||||
|
||||
**응답 필드** (`data` 내부)
|
||||
|
||||
<!-- 실측 제외: http-404 — 응답 필드는 사람이 작성하세요. -->
|
||||
_`data` 객체의 필드 (`EditorSpecAssembler::assemble`)._
|
||||
|
||||
| 필드 | 타입 | 예시값 | 용도/설명 |
|
||||
| --- | --- | --- | --- |
|
||||
| identifier | string | `sirsoft-admin_basic` | 요청한 템플릿 식별자 (요청값 반향) |
|
||||
| spec | object \| null | `{"palette":[…],"styleControls":{…}}` | 합본된 편집기 스펙. 분할 매니페스트(`editor-spec/` + `$include`)는 **활성 디렉토리 기준**으로 합쳐지며(`_bundled` 폴백 없음), 미분할 원본 파일은 그대로 반환된다. 스펙이 없으면 `null` (404 아님) |
|
||||
|
||||
**응답 예시**
|
||||
|
||||
<!-- 실측 제외: http-404 — 응답 예시는 사람이 작성하세요. -->
|
||||
```http
|
||||
HTTP/1.1 200
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "편집기 스펙을 조회했습니다.",
|
||||
"data": {
|
||||
"identifier": "sirsoft-admin_basic",
|
||||
"spec": {
|
||||
"palette": [{ "name": "Card", "label": "카드" }],
|
||||
"styleControls": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**에러 응답**
|
||||
|
||||
@@ -93553,7 +93628,7 @@ HTTP/1.1 200
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.templates.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.templates.layouts.edit`)이 없는 경우 |
|
||||
| 404 | Not Found | path 파라미터에 해당하는 리소스가 없는 경우 |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) |
|
||||
|
||||
@@ -93612,7 +93687,7 @@ _단건 응답: `data` 는 템플릿 `lang/{locale}.json` 의 내용을 그대
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.templates.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.templates.layouts.edit`)이 없는 경우 |
|
||||
| 404 | Not Found | path 파라미터에 해당하는 리소스가 없는 경우 |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) |
|
||||
|
||||
@@ -93726,18 +93801,38 @@ Authorization: Bearer {YOUR_TOKEN}
|
||||
|
||||
**응답 필드** (`data` 내부)
|
||||
|
||||
<!-- 실측 제외: http-404 — 응답 필드는 사람이 작성하세요. -->
|
||||
_`data` 객체의 필드._
|
||||
|
||||
| 필드 | 타입 | 예시값 | 용도/설명 |
|
||||
| --- | --- | --- | --- |
|
||||
| identifier | string | `sirsoft-admin_basic` | 요청한 템플릿 식별자 (요청값 반향) |
|
||||
| permissions | array | `[{"identifier":"core.users.read","name":"사용자 조회"}]` | 레이아웃 조건식에 쓸 수 있는 권한 후보 목록 (현재 로케일로 해석된 표시명 포함) |
|
||||
|
||||
**응답 예시**
|
||||
|
||||
<!-- 실측 제외: http-404 — 응답 예시는 사람이 작성하세요. -->
|
||||
```http
|
||||
HTTP/1.1 200
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "설정을 조회했습니다.",
|
||||
"data": {
|
||||
"identifier": "sirsoft-admin_basic",
|
||||
"permissions": [
|
||||
{ "identifier": "core.users.read", "name": "사용자 조회" }
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**에러 응답**
|
||||
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.templates.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.templates.layouts.edit`)이 없는 경우 |
|
||||
| 404 | Not Found | path 파라미터에 해당하는 리소스가 없는 경우 |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) |
|
||||
|
||||
@@ -93929,18 +94024,45 @@ Authorization: Bearer {YOUR_TOKEN}
|
||||
|
||||
**응답 필드** (`data` 내부)
|
||||
|
||||
<!-- 실측 제외: http-404 — 응답 필드는 사람이 작성하세요. -->
|
||||
_`data` 는 템플릿 `routes.json` 에 모듈·플러그인 라우트를 병합하고 각 라우트에 출처를 태깅한 결과다._
|
||||
|
||||
| 필드 | 타입 | 예시값 | 용도/설명 |
|
||||
| --- | --- | --- | --- |
|
||||
| version | string | `1.0.0` | routes.json 스키마 버전 |
|
||||
| routes | array | `[{"path":"/","layout":"home","source":{"kind":"template","identifier":"sirsoft-basic"}}]` | 라우트 목록. **`source` 태깅이 필수**다 — 편집기 라우트 트리가 `source.kind` 로 그룹핑하므로 태깅이 없으면 클라이언트가 라우트 트리 렌더에서 실패한다 |
|
||||
|
||||
> 공개 라우트 엔드포인트와 달리 **비활성 템플릿도 조회 가능**하고 `_bundled` 폴백이 적용된다 (편집 대상이 활성일 필요가 없다).
|
||||
|
||||
**응답 예시**
|
||||
|
||||
<!-- 실측 제외: http-404 — 응답 예시는 사람이 작성하세요. -->
|
||||
```http
|
||||
HTTP/1.1 200
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "라우트를 조회했습니다.",
|
||||
"data": {
|
||||
"version": "1.0.0",
|
||||
"routes": [
|
||||
{
|
||||
"path": "/",
|
||||
"layout": "home",
|
||||
"auth_required": false,
|
||||
"source": { "kind": "template", "identifier": "sirsoft-basic" }
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**에러 응답**
|
||||
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.templates.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.templates.layouts.edit`)이 없는 경우 |
|
||||
| 404 | Not Found | path 파라미터에 해당하는 리소스가 없는 경우 |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) |
|
||||
|
||||
@@ -94025,7 +94147,7 @@ _단건 응답: `data` 객체의 필드._
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.templates.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.templates.layouts.edit`)이 없는 경우 |
|
||||
| 404 | Not Found | path 파라미터에 해당하는 리소스가 없는 경우 |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) |
|
||||
|
||||
@@ -94045,8 +94167,8 @@ _단건 응답: `data` 객체의 필드._
|
||||
| 이름 | 위치 | 타입 | 필수 | 허용값 | 용도 |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| identifier | path | string | 예 | — | 대상 리소스의 식별자 |
|
||||
| extensions | query | string | 아니오 | — | <!-- TODO: 용도 --> |
|
||||
| page_type | query | string | 아니오 | — | <!-- TODO: 용도 --> |
|
||||
| extensions | query | string | 아니오 | JSON 배열 문자열 | 후보를 수집할 확장 선언. `[{"type":"module","id":"sirsoft-board"}]` 형태의 JSON 문자열이며, `type`·`id` 가 모두 문자열인 항목만 채택된다(그 외는 조용히 무시). 편집 중인 레이아웃이 아직 활성화하지 않은 확장의 후보까지 보려 할 때 쓴다 |
|
||||
| page_type | query | string | 아니오 | — | 치환 변수(`vars`) 후보를 좁힐 페이지 유형. 빈 문자열은 미지정과 같게 처리된다 |
|
||||
|
||||
> 이 엔드포인트는 확장이 파라미터를 추가할 수 있습니다 (`core.seo_candidate.index_validation_rules`).
|
||||
|
||||
@@ -94307,18 +94429,42 @@ Authorization: Bearer {YOUR_TOKEN}
|
||||
|
||||
**응답 필드** (`data` 내부)
|
||||
|
||||
<!-- 실측 제외: http-404 — 응답 필드는 사람이 작성하세요. -->
|
||||
_`data` 객체의 필드 (`SeoCandidateService::collect` + 요청 식별자)._
|
||||
|
||||
| 필드 | 타입 | 예시값 | 용도/설명 |
|
||||
| --- | --- | --- | --- |
|
||||
| identifier | string | `sirsoft-admin_basic` | 요청한 템플릿 식별자 (요청값 반향) |
|
||||
| page_types | array | `[{"id":"board_list","label":"게시판 목록"}]` | 선택 가능한 페이지 유형 후보 (활성 확장 + 요청이 선언한 확장 기준) |
|
||||
| toggle_settings | array | `[{"key":"use_og","label":"오픈그래프 사용"}]` | SEO 토글 설정 후보 (현재 로케일로 해석) |
|
||||
| vars | array | `[{"name":"post.title","label":"게시글 제목"}]` | 메타 템플릿에 넣을 수 있는 치환 변수 후보 (`page_type` 을 주면 그 유형으로 좁혀진다) |
|
||||
| extensions | array | `[{"type":"module","id":"sirsoft-board","name":"게시판"}]` | 후보를 제공한 활성 확장 목록 |
|
||||
|
||||
**응답 예시**
|
||||
|
||||
<!-- 실측 제외: http-404 — 응답 예시는 사람이 작성하세요. -->
|
||||
```http
|
||||
HTTP/1.1 200
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "요청이 성공했습니다.",
|
||||
"data": {
|
||||
"identifier": "sirsoft-admin_basic",
|
||||
"page_types": [{ "id": "board_list", "label": "게시판 목록" }],
|
||||
"toggle_settings": [{ "key": "use_og", "label": "오픈그래프 사용" }],
|
||||
"vars": [{ "name": "post.title", "label": "게시글 제목" }],
|
||||
"extensions": [{ "type": "module", "id": "sirsoft-board", "name": "게시판" }]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**에러 응답**
|
||||
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.templates.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.templates.layouts.edit`)이 없는 경우 |
|
||||
| 404 | Not Found | path 파라미터에 해당하는 리소스가 없는 경우 |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) |
|
||||
|
||||
@@ -94461,7 +94607,7 @@ _단건 응답: `data` 객체의 필드._
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.templates.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.templates.layouts.edit`)이 없는 경우 |
|
||||
| 404 | Not Found | path 파라미터에 해당하는 리소스가 없는 경우 |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) |
|
||||
|
||||
@@ -94531,7 +94677,7 @@ HTTP/1.1 200
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.templates.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.templates.layouts.edit`)이 없는 경우 |
|
||||
| 404 | Not Found | path 파라미터에 해당하는 리소스가 없는 경우 |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) |
|
||||
|
||||
@@ -94612,7 +94758,7 @@ _단건 응답: `data` 객체의 필드._
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.templates.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.templates.layouts.edit`)이 없는 경우 |
|
||||
| 404 | Not Found | 대상 템플릿이 존재하지 않는 경우 (`템플릿을 찾을 수 없습니다.`) |
|
||||
| 422 | Unprocessable Entity | 파일 검증 위반 (이미지 아님, 지원하지 않는 형식 jpg/jpeg/png/gif/webp/svg 외, 크기 초과 등) |
|
||||
| 500 | Server Error | 스토리지 저장 실패 (`첨부 파일 업로드에 실패했습니다.`) |
|
||||
@@ -94924,7 +95070,7 @@ _단건 응답: `data` 객체의 필드._
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.templates.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.templates.layouts.edit`)이 없는 경우 |
|
||||
| 404 | Not Found | 대상 템플릿이 존재하지 않는 경우 |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`ids` 누락 또는 빈 배열 등) |
|
||||
| 500 | Server Error | 삭제 트랜잭션 실패 |
|
||||
@@ -95005,7 +95151,7 @@ _목록 응답: `data` 는 커스텀 다국어 키 배열입니다 (페이지네
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.templates.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.templates.layouts.edit`)이 없는 경우 |
|
||||
| 404 | Not Found | 대상 템플릿이 존재하지 않는 경우 |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) |
|
||||
|
||||
@@ -95090,7 +95236,7 @@ _단건 응답: `data` 객체의 필드 (생성된 커스텀 다국어 키, HTTP
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.templates.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.templates.layouts.edit`)이 없는 경우 |
|
||||
| 404 | Not Found | 대상 템플릿이 존재하지 않는 경우 |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) |
|
||||
| 500 | Server Error | 키 생성 트랜잭션 실패 |
|
||||
@@ -95141,7 +95287,7 @@ _이 엔드포인트는 `data` 를 반환하지 않습니다 (`data`: `null`,
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.templates.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.templates.layouts.edit`)이 없는 경우 |
|
||||
| 404 | Not Found | 대상 템플릿이 없거나, 해당 키가 없거나, 그 키가 경로의 템플릿 소속이 아닌 경우 (교차 템플릿 삭제 차단) |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) |
|
||||
| 500 | Server Error | 삭제 트랜잭션 실패 |
|
||||
@@ -95229,7 +95375,7 @@ _단건 응답: `data` 객체의 필드 (수정된 커스텀 다국어 키)._
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.templates.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.templates.layouts.edit`)이 없는 경우 |
|
||||
| 404 | Not Found | 대상 템플릿이 없거나, 해당 키가 없거나, 그 키가 경로의 템플릿 소속이 아닌 경우 |
|
||||
| 409 | Conflict | 낙관적 잠금 충돌 — 다른 사용자가 먼저 수정 (`errors`: `error=concurrent_modification`, `current_version`, `your_version`, `resource`) |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) |
|
||||
@@ -95336,7 +95482,7 @@ _단건 응답: `data` 객체의 필드._
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.templates.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.templates.install`)이 없는 경우 |
|
||||
| 404 | Not Found | path 파라미터에 해당하는 리소스가 없는 경우 |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) |
|
||||
| 500 | Server Error | 대상 템플릿을 찾을 수 없거나 프리뷰 구성 실패 |
|
||||
@@ -95667,7 +95813,7 @@ _단건 응답: `data` 객체의 필드 (수정된 LayoutExtensionResource)._
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.templates.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.templates.layouts.edit`)이 없는 경우 |
|
||||
| 404 | Not Found | 템플릿/확장이 없거나, 확장이 경로의 템플릿 소속이 아닌 경우 |
|
||||
| 409 | Conflict | 낙관적 잠금 충돌 — 다른 사용자가 먼저 수정 (`errors`: `error=concurrent_modification`, `current_version`, `your_version`, `resource`) |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) |
|
||||
@@ -95899,7 +96045,7 @@ _단건 응답: `data` 객체의 필드 (복원 결과로 새로 기록된 버
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.templates.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.templates.layouts.edit`)이 없는 경우 |
|
||||
| 404 | Not Found | 템플릿/확장/버전이 없거나, 확장이 경로의 템플릿 소속이 아닌 경우 |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) |
|
||||
| 500 | Server Error | 복원 트랜잭션 실패 |
|
||||
@@ -96441,7 +96587,7 @@ _단건 응답: `data` 객체의 필드 (수정된 LayoutResource)._
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.templates.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.templates.layouts.edit`)이 없는 경우 |
|
||||
| 404 | Not Found | 대상 템플릿이 존재하지 않는 경우 |
|
||||
| 409 | Conflict | 낙관적 잠금 충돌 — 다른 사용자가 먼저 수정 (`errors`: `error=concurrent_modification`, `current_version`, `your_version`, `resource`) |
|
||||
| 422 | Unprocessable Entity | content 구조 검증 위반 (레이아웃 구조/슬롯/데이터소스 병합/엔드포인트 화이트리스트/외부 URL 차단/권한 구조 규칙) |
|
||||
@@ -96678,7 +96824,7 @@ _단건 응답: `data` 객체의 필드 (복원 결과로 새로 기록된 버
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.templates.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.templates.layouts.edit`)이 없는 경우 |
|
||||
| 404 | Not Found | 대상 템플릿·레이아웃·버전이 없는 경우 |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) |
|
||||
| 500 | Server Error | 복원 트랜잭션 실패 |
|
||||
@@ -96828,7 +96974,7 @@ _단건 응답: `data` 객체의 필드._
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.templates.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.templates.uninstall`)이 없는 경우 |
|
||||
| 404 | Not Found | 해당 식별자의 템플릿이 없는 경우 (`템플릿을 찾을 수 없습니다.`) |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) |
|
||||
| 500 | Server Error | 삭제 정보 조회 실패 |
|
||||
@@ -96944,7 +97090,7 @@ _업데이트할 내용이 없거나 템플릿 정보를 다시 읽지 못한
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.templates.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.templates.install`)이 없는 경우 |
|
||||
| 404 | Not Found | path 파라미터에 해당하는 리소스가 없는 경우 |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터 검증 위반 또는 업데이트 실패 (미설치, 다운그레이드 차단, 다운로드 실패, 코어 버전 비호환 등 — `errors.template_name` 에 번역된 사유) |
|
||||
| 500 | Server Error | 업데이트 처리 중 예기치 못한 오류 |
|
||||
@@ -96977,11 +97123,24 @@ Accept: application/json
|
||||
|
||||
**응답 필드** (`data` 내부)
|
||||
|
||||
<!-- 실측 제외: http-422 — 응답 필드는 사람이 작성하세요. -->
|
||||
_이 엔드포인트는 표준 JSON 봉투가 아니라 **템플릿 에셋 파일 본문** 을 그대로 반환한다 — `data` 구조가 없다._
|
||||
|
||||
| 항목 | 값 | 설명 |
|
||||
| --- | --- | --- |
|
||||
| Content-Type | `파일 확장자에 따른 MIME (예: text/javascript, text/css, image/png)` | 서빙 대상의 MIME 타입 |
|
||||
| Cache-Control | `public, max-age=31536000, immutable` (프로덕션) / `no-cache` (그 외) | 환경에 따라 갈린다 |
|
||||
| ETag | `{md5(mtime+size)}` | `If-None-Match` 가 일치하면 본문 없이 `304` |
|
||||
|
||||
**응답 예시**
|
||||
|
||||
<!-- 실측 제외: http-422 — 응답 예시는 사람이 작성하세요. -->
|
||||
```http
|
||||
HTTP/1.1 200
|
||||
Content-Type: text/javascript
|
||||
|
||||
(function(){ /* 템플릿 에셋 본문 */ })();
|
||||
```
|
||||
|
||||
> 같은 ETag 로 재요청하면 본문 없이 `304 Not Modified` 가 반환된다.
|
||||
|
||||
**에러 응답**
|
||||
|
||||
@@ -97069,7 +97228,13 @@ Accept: application/json
|
||||
|
||||
|
||||
|
||||
<!-- 실측 응답에 필드 없음(빈 목록 등) — 데이터가 있는 상태로 재실측하거나 사람이 작성. -->
|
||||
_`data` 는 템플릿의 `components.json` 내용을 그대로 담은 **컴포넌트 맵**이다 (고정 필드 집합이 아니라 컴포넌트명 → 정의 매핑)._
|
||||
|
||||
| 필드 | 타입 | 예시값 | 용도/설명 |
|
||||
| --- | --- | --- | --- |
|
||||
| (컴포넌트명) | object | `{"type":"basic","tag":"div"}` | 컴포넌트 정의. 키는 레이아웃 JSON 의 `name` 과 일치한다 |
|
||||
|
||||
> 활성/`_bundled` 어디에도 `components.json` 이 없으면 `404`(`templates.layout_not_found`)다.
|
||||
|
||||
**응답 예시**
|
||||
|
||||
@@ -97137,7 +97302,19 @@ _이 엔드포인트는 `success`/`message`/`data` 봉투를 사용하지 않습
|
||||
|
||||
**응답 예시**
|
||||
|
||||
<!-- 실측 제외: http-404 — 응답 예시는 사람이 작성하세요. -->
|
||||
```http
|
||||
HTTP/1.1 200
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "설정을 조회했습니다.",
|
||||
"data": {
|
||||
"Card": { "type": "composite", "props": { "title": "string" } }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**에러 응답**
|
||||
|
||||
@@ -97396,11 +97573,35 @@ Accept: application/json
|
||||
|
||||
**응답 필드** (`data` 내부)
|
||||
|
||||
<!-- 실측 제외: http-404 — 응답 필드는 사람이 작성하세요. -->
|
||||
_`data` 는 템플릿의 `template.json` 매니페스트 내용이다 (고정 필드 집합이 아니라 매니페스트 그대로)._
|
||||
|
||||
| 필드 | 타입 | 예시값 | 용도/설명 |
|
||||
| --- | --- | --- | --- |
|
||||
| identifier | string | `sirsoft-basic` | 템플릿 식별자 |
|
||||
| version | string | `1.1.1` | 템플릿 버전 |
|
||||
| type | string | `user` | 템플릿 유형 (`admin`/`user`) |
|
||||
| (그 외 매니페스트 키) | mixed | — | `template.json` 이 선언한 나머지 키가 그대로 실린다 |
|
||||
|
||||
> **활성 템플릿만** 조회된다 — 비활성이거나 매니페스트가 없으면 `404`. 응답은 1시간 캐시된다.
|
||||
|
||||
**응답 예시**
|
||||
|
||||
<!-- 실측 제외: http-404 — 응답 예시는 사람이 작성하세요. -->
|
||||
```http
|
||||
HTTP/1.1 200
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "설정을 조회했습니다.",
|
||||
"data": {
|
||||
"identifier": "sirsoft-basic",
|
||||
"name": "Sirsoft Basic",
|
||||
"version": "1.1.1",
|
||||
"type": "user"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**에러 응답**
|
||||
|
||||
@@ -189529,11 +189730,37 @@ Accept: application/json
|
||||
|
||||
**응답 필드** (`data` 내부)
|
||||
|
||||
<!-- 실측 제외: http-404 — 응답 필드는 사람이 작성하세요. -->
|
||||
_`data` 는 템플릿 `routes.json` 에 활성 모듈·플러그인 라우트를 병합한 결과다._
|
||||
|
||||
| 필드 | 타입 | 예시값 | 용도/설명 |
|
||||
| --- | --- | --- | --- |
|
||||
| version | string | `1.0.0` | routes.json 스키마 버전 |
|
||||
| routes | array | `[{"path":"/","layout":"home","source":{"kind":"template","identifier":"sirsoft-basic"}}]` | 라우트 목록 (각 항목에 출처 `source` 태깅) |
|
||||
|
||||
> 응답은 `?v=` 쿼리를 포함한 키로 캐시된다. 다만 확장 업데이트 중 활성 디렉토리가 비어 라우트가 빠진 **열화 스냅샷은 캐시에 남기지 않는다** — 남기면 업데이트가 끝난 뒤에도 그 확장의 화면이 캐시 만료까지 404 로 남는다.
|
||||
|
||||
**응답 예시**
|
||||
|
||||
<!-- 실측 제외: http-404 — 응답 예시는 사람이 작성하세요. -->
|
||||
```http
|
||||
HTTP/1.1 200
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "라우트를 조회했습니다.",
|
||||
"data": {
|
||||
"version": "1.0.0",
|
||||
"routes": [
|
||||
{
|
||||
"path": "/",
|
||||
"layout": "home",
|
||||
"source": { "kind": "template", "identifier": "sirsoft-basic" }
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**에러 응답**
|
||||
|
||||
|
||||
@@ -378,7 +378,7 @@ HTTP/1.1 201
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.users.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.users.create`)이 없는 경우 |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) |
|
||||
| 500 | Internal Server Error | 사용자 생성 중 예외 발생 (`user.create_failed`, `errors.error` 에 예외 메시지) |
|
||||
|
||||
@@ -456,7 +456,7 @@ HTTP/1.1 200
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.users.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.users.update`)이 없는 경우 |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지 — 예: 요청자 본인 UUID 포함 시 `ExcludeCurrentUser` 위반) |
|
||||
| 500 | Internal Server Error | 일괄 변경 중 예외 발생 (`user.bulk_update_status_failed`) |
|
||||
|
||||
@@ -1018,17 +1018,19 @@ HTTP/1.1 200
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "사용자가 삭제되었습니다.",
|
||||
"message": "사용자가 성공적으로 삭제되었습니다.",
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
> 슈퍼 관리자 계정을 대상으로 하면 삭제되지 않고 `422`(`exceptions.cannot_delete_super_admin`)로 거부된다. 삭제는 CASCADE 에 의존하지 않고 연관 데이터를 명시적으로 정리한 뒤 수행되며, 정리 단계에서 실패하면 `422` 와 함께 `error.errors.general[0]` 에 상세 사유가 담긴다.
|
||||
|
||||
**에러 응답**
|
||||
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.users.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.users.delete`)이 없는 경우 |
|
||||
| 404 | Not Found | path 파라미터에 해당하는 리소스가 없는 경우 |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) |
|
||||
|
||||
@@ -1111,7 +1113,7 @@ _단건 응답: `data` 객체의 필드._
|
||||
| withdrawn_at | null | `null` | withdrawn 일시 |
|
||||
| blocked_at | null | `null` | blocked 일시 |
|
||||
| failed_login_attempts | integer | `0` | 연속 로그인 실패 횟수 |
|
||||
| locked_permanently | boolean | `false` | 무기한 잠금 여부 (보안 설정의 잠금 시간이 `0` 이면 자동 해제 없이 관리자가 직접 풀어야 한다) |
|
||||
| locked_permanently | boolean | `false` | 영구 잠금 여부. true 면 `locked_until` 과 무관하게 잠금이 유지되며, 해제는 성공 로그인 또는 관리자의 잠금 해제로만 이뤄진다 (잠금 시간 설정이 `0`= 무기한일 때 세워진다) |
|
||||
| locked_until | null | `null` | 계정 잠금 해제 시각 (NULL = 잠금 없음) |
|
||||
| is_locked | boolean | `false` | locked 여부 |
|
||||
| notify_post_complete | boolean | `false` | 게시글 작성 완료 알림 수신 여부 (게시판 모듈 알림 설정) |
|
||||
@@ -1446,7 +1448,7 @@ HTTP/1.1 200
|
||||
| 상태코드 | 의미 | 발생 조건 |
|
||||
| --- | --- | --- |
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.users.read`)이 없는 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`core.users.update`)이 없는 경우 |
|
||||
| 404 | Not Found | path 파라미터에 해당하는 리소스가 없는 경우 |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지 — 마지막 관리자 본인의 admin 역할 제거 시도 시 `user.last_admin_role_cannot_remove` 포함) |
|
||||
| 500 | Internal Server Error | 수정 중 예외 발생 (`user.update_failed`, `errors.error` 에 예외 메시지) |
|
||||
|
||||
Reference in new issue
Block a user