Files
Gnuboard7/docs/backend/api/menus.md
T
HeuJung 71abdeaf60 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종 신설.
2026-08-17 01:45:09 +09:00

48 KiB

Menus API 레퍼런스

소유: 코어 · 생성: php artisan api:docgen (실측 기반). @generated 블록은 재생성 시 갱신되며, 사람이 작성한 설명은 보존됩니다.


TL;DR (5초 요약)

1. 이 문서는 실제 API 호출로 실측한 Menus 엔드포인트 레퍼런스입니다
2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 요청 예시(curl) + 실측 응답 필드 표 + 응답 예시(envelope)
3. 응답 필드의 예시값·응답 예시 JSON 은 실제 호출 응답에서 관측된 값입니다
4. 갱신: 코드 변경 후 php artisan api:docgen 재실행
5. 설명(TODO) 칸은 사람이 채웁니다

GET /api/admin/menus

  • 라우트명: api.admin.menus.index
  • 컨트롤러: App\Http\Controllers\Api\Admin\MenuController@index
  • 인증/권한: auth:sanctum + permission:core.menus.read

요청 파라미터

이름 위치 타입 필수 허용값 용도
is_active query boolean 아니오 — 활성 여부 (true 활성 / false 비활성)
filters query array 아니오 max 10 추가 필터 조건 맵 (필드별 조건)
sort_by query string 아니오 created_at, name, slug, order 정렬 기준 필드명
sort_order query string 아니오 asc, desc 정렬 방향 (asc 오름차순 / desc 내림차순)

이 엔드포인트는 확장이 파라미터를 추가할 수 있습니다 (core.menu.list_validation_rules).

요청 예시

GET /api/admin/menus?is_active=1&filters=%EC%98%88%EC%8B%9C%EA%B0%92&sort_by=created_at&sort_order=asc HTTP/1.1
Host: api.example.com
Accept: application/json
Authorization: Bearer {YOUR_TOKEN}

응답 필드 (data 내부)

목록 응답: data.data[] 배열 항목의 필드.

필드 타입 실측 예시값 용도/설명
id integer 19 기본 키 (내부 식별자)
name object {"ko":"대시보드","en":"Dashboard","ja":"ダッシュボード"} 메뉴 이름 (다국어 JSON)
slug string admin-dashboard 메뉴 슬러그
url string /admin/dashboard 메뉴 URL
icon string fas fa-tachometer-alt 메뉴 아이콘
order integer 1 메뉴 순서
is_active boolean true active 여부
parent_id null null 상위 메뉴 ID
extension_type string core 확장 소유 타입: core(코어), module(모듈), plugin(플러그인), NULL(사용자 정의)
extension_identifier string core 확장 식별자 (예: core, sirsoft-board, sirsoft-payment)
children array [] 하위 항목 배열 (계층 트리 — children 관계 파생)
creator null null 생성자 정보 객체 (uuid/name/email — creator 관계 파생)
roles array [{"id":1,"name":{"ko":"관리자","en":"Administrator","ja":"管理… 이 메뉴 노출이 허용된 역할 목록 (원소 id/name/permission_type — roles 관계 파생, permission_type 은 pivot 의 노출 권한 유형)
created_at string 2026-07-31 00:13:57 생성 일시
updated_at string 2026-08-01 11:46:14 최종 수정 일시
is_owner boolean false 현재 인증 사용자가 이 리소스의 소유자인지 여부 (BaseApiResource 표준 메타)
abilities object {"can_create":true,"can_update":true,"can_delete":true} 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반)

응답 예시

HTTP/1.1 200
{
    "success": true,
    "message": "메뉴를 성공적으로 가져왔습니다.",
    "data": {
        "data": [
            {
                "id": 19,
                "name": {
                    "ko": "대시보드",
                    "en": "Dashboard",
                    "ja": "ダッシュボード"
                },
                "slug": "admin-dashboard",
                "url": "/admin/dashboard",
                "icon": "fas fa-tachometer-alt",
                "order": 1,
                "is_active": true,
                "parent_id": null,
                "extension_type": "core",
                "extension_identifier": "core",
                "children": [],
                "creator": null,
                "roles": [
                    {
                        "id": 1,
                        "name": {
                            "ko": "관리자",
                            "en": "Administrator",
                            "ja": "管理者"
                        },
                        "permission_type": "read"
                    },
                    {
                        "id": 156,
                        "name": {
                            "ko": "매니저",
                            "en": "Manager",
                            "ja": "マネージャー"
                        },
                        "permission_type": "read"
                    }
                ],
                "created_at": "2026-07-31 00:13:57",
                "updated_at": "2026-08-01 11:46:14",
                "is_owner": false,
                "abilities": {
                    "can_create": true,
                    "can_update": true,
                    "can_delete": true
                }
            },
            {
                "id": 20,
                "name": {
                    "ko": "환경설정",
                    "en": "Settings",
                    "ja": "環境設定"
                },
                "slug": "admin-settings",
                "url": "/admin/settings",
                "icon": "fas fa-cog",
                "order": 2,
                "is_active": true,
                "parent_id": null,
                "extension_type": "core",
                "extension_identifier": "core",
                "children": [],
                "creator": null,
                "roles": [
                    {
                        "id": 1,
                        "name": {
                            "ko": "관리자",
                            "en": "Administrator",
                            "ja": "管理者"
                        },
                        "permission_type": "read"
                    }
                ],
                "created_at": "2026-07-31 00:13:57",
                "updated_at": "2026-08-01 11:46:14",
                "is_owner": false,
                "abilities": {
                    "can_create": true,
                    "can_update": true,
                    "can_delete": true
                }
            },
            "... (총 17건 중 2건 표시)"
        ],
        "abilities": {
            "can_create": true,
            "can_update": true,
            "can_delete": true
        }
    }
}

에러 응답

상태코드 의미 발생 조건
401 Unauthenticated 유효한 Bearer 토큰이 없거나 만료된 경우
403 Forbidden 요구 권한(core.menus.read)이 없는 경우
422 Unprocessable Entity 요청 파라미터가 검증 규칙을 위반한 경우 (error.errors 에 필드별 메시지)

설명

관리자 메뉴 관리 화면(partials/admin_menu_list/*)의 목록 표시 기준 엔드포인트. is_active 또는 filters 가 있으면 필터링된 관리용 메뉴를, 없으면 최상위 메뉴 전체를 반환한다. filters 는 field(name/slug/url/all)·value·operator(like/eq/starts_with/ends_with) 조합의 배열이며 최대 10개까지 허용된다. name 은 다국어 JSON 객체로 내려오고, 각 항목에 children/creator/roles 관계와 abilities(현재 사용자의 수정/삭제 가능 여부)가 포함된다. 단, 하위 항목(children[])에는 roles 키 자체가 없다 — 상위 메뉴마다 하위 전체의 역할 피벗까지 싣지 않기 위해서다. 빈 배열이 아니라 키를 생략하는 이유는, 빈 배열이 "역할 제한이 없다" 는 사실이 아닌 단언이 되어 그 값을 그대로 수정 요청에 실어 보내면 해당 메뉴의 역할 제한이 통째로 해제되기 때문이다. 하위 메뉴의 역할이 필요하면 GET /api/admin/menus/{menu} 로 단건 조회한다 (단건 응답은 항상 roles 를 포함한다). 인증 계약: auth:sanctum + permission:core.menus.read.

POST /api/admin/menus

  • 라우트명: api.admin.menus.store
  • 컨트롤러: App\Http\Controllers\Api\Admin\MenuController@store
  • 인증/권한: auth:sanctum + permission:core.menus.create

요청 파라미터

이름 위치 타입 필수 허용값 용도
name body string 예 — 대상의 이름/명칭
slug body string 예 max 255 URL 친화 식별자 (slug)
url body string 아니오 max 500 URL
icon body string 아니오 max 100 아이콘
parent_id body integer 아니오 — parent 식별자
order body integer 아니오 min 0 표시 정렬 순서 값 (작을수록 우선)
is_active body boolean 아니오 — 활성 여부 (true 활성 / false 비활성)
extension_type body string 아니오 core, module, plugin 확장 유형 (core/module/plugin/template)
extension_identifier body string 아니오 max 255 확장 식별자
roles body array 아니오 — 이 메뉴 노출을 허용할 역할 ID 배열 (각 원소는 존재하는 role id — 지정 시 노출 허용 역할 목록으로 설정/교체)

이 엔드포인트는 확장이 파라미터를 추가할 수 있습니다 (core.menu.create_validation_rules).

요청 예시

POST /api/admin/menus HTTP/1.1
Host: api.example.com
Accept: application/json
Authorization: Bearer {YOUR_TOKEN}
Content-Type: application/json

{
    "name": "예시 이름",
    "slug": "example-key",
    "url": "https://example.com",
    "icon": "예시값",
    "parent_id": 1,
    "order": 1,
    "is_active": true,
    "extension_type": "core",
    "extension_identifier": "example-key",
    "roles": [
        "예시값"
    ]
}

응답 필드 (data 내부)

단건 응답: data 객체의 필드.

필드 타입 실측 예시값 용도/설명
id integer 39 기본 키 (내부 식별자)
name object {"ko":"실측 예시값","en":"실측 예시값"} 대상의 이름/명칭 (다국어 필드는 로케일별 값 객체)
slug string probe_6a71e0d4afdbc URL 친화 식별자 (slug)
url string https://example.com 메뉴 URL
icon string 실측 예시값 아이콘 식별자 (아이콘 클래스/이름)
order integer 1 메뉴 순서
is_active boolean true active 여부
parent_id integer 1 parent 식별자 (연관 리소스 참조)
extension_type string core 이 리소스를 소유한 확장의 타입 (core/module/plugin/template)
extension_identifier string probe_6a71e0d4afdc2 이 리소스를 소유한 확장의 식별자
parent object {"id":1,"name":{"ko":"게시판 관리","en":"Board Management"},"u… 상위 항목 객체 (parent 관계 파생)
children array [] 하위 항목 배열 (계층 트리 — children 관계 파생)
creator null null 생성자 정보 객체 (uuid/name/email — creator 관계 파생)
roles array [{"id":1,"name":{"ko":"관리자","en":"Administrator","ja":"管理… 보유 역할 목록 (각 원소 id/name/permissions — roles 관계 파생)
created_at string 2026-08-04 21:53:40 생성 일시
updated_at string 2026-08-04 21:53:40 최종 수정 일시
is_owner boolean false 현재 인증 사용자가 이 리소스의 소유자인지 여부 (BaseApiResource 표준 메타)
abilities object {"can_create":true,"can_update":true,"can_delete":true} 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반)

응답 예시

HTTP/1.1 201
{
    "success": true,
    "message": "메뉴가 성공적으로 생성되었습니다.",
    "data": {
        "id": 39,
        "name": {
            "ko": "실측 예시값",
            "en": "실측 예시값"
        },
        "slug": "probe_6a71e0d4afdbc",
        "url": "https://example.com",
        "icon": "실측 예시값",
        "order": 1,
        "is_active": true,
        "parent_id": 1,
        "extension_type": "core",
        "extension_identifier": "probe_6a71e0d4afdc2",
        "parent": {
            "id": 1,
            "name": {
                "ko": "게시판 관리",
                "en": "Board Management"
            },
            "url": null,
            "icon": "fas fa-clipboard-list"
        },
        "children": [],
        "creator": null,
        "roles": [
            {
                "id": 1,
                "name": {
                    "ko": "관리자",
                    "en": "Administrator",
                    "ja": "管理者"
                },
                "permission_type": "read"
            }
        ],
        "created_at": "2026-08-04 21:53:40",
        "updated_at": "2026-08-04 21:53:40",
        "is_owner": false,
        "abilities": {
            "can_create": true,
            "can_update": true,
            "can_delete": true
        }
    }
}

에러 응답

상태코드 의미 발생 조건
401 Unauthenticated 유효한 Bearer 토큰이 없거나 만료된 경우
403 Forbidden 요구 권한(core.menus.create)이 없는 경우
422 Unprocessable Entity 요청 파라미터가 검증 규칙을 위반한 경우 (error.errors 에 필드별 메시지)

설명

새 메뉴를 생성한다. name 은 다국어 값(문자열로 보내면 지원 로케일 전체에 동일 값으로 자동 확장)이며 slug 는 menus 테이블 내 유일해야 한다. parent_id 로 하위 메뉴를 만들 수 있고, roles 배열로 이 메뉴 노출을 허용할 역할 ID 를 지정한다. 성공 시 201 과 함께 생성된 메뉴(관계 eager-load 포함)를 MenuResource 로 반환한다. 관리자 메뉴 관리 화면의 메뉴 추가 폼에서 소비된다. 인증 계약: auth:sanctum + permission:core.menus.create.

GET /api/admin/menus/active

  • 라우트명: api.admin.menus.active
  • 컨트롤러: App\Http\Controllers\Api\Admin\MenuController@active
  • 인증/권한: auth:sanctum + permission:core.menus.read

요청 파라미터

요청 파라미터 없음.

요청 예시

GET /api/admin/menus/active HTTP/1.1
Host: api.example.com
Accept: application/json
Authorization: Bearer {YOUR_TOKEN}

응답 필드 (data 내부)

필드 타입 실측 예시값 용도/설명
id integer 19 기본 키 (내부 식별자)
name object {"ko":"대시보드","en":"Dashboard","ja":"ダッシュボード"} 메뉴 이름 (다국어 JSON)
slug string admin-dashboard 메뉴 슬러그
url string /admin/dashboard 메뉴 URL
icon string fas fa-tachometer-alt 메뉴 아이콘
order integer 1 메뉴 순서
is_active boolean true active 여부
children array [] 하위 항목 배열 (계층 트리 — children 관계 파생)

응답 예시

HTTP/1.1 200
{
    "success": true,
    "message": "메뉴를 성공적으로 가져왔습니다.",
    "data": [
        {
            "id": 19,
            "name": {
                "ko": "대시보드",
                "en": "Dashboard",
                "ja": "ダッシュボード"
            },
            "slug": "admin-dashboard",
            "url": "/admin/dashboard",
            "icon": "fas fa-tachometer-alt",
            "...": "(3개 키 생략, 총 8개)"
        },
        {
            "id": 20,
            "name": {
                "ko": "환경설정",
                "en": "Settings",
                "ja": "環境設定"
            },
            "slug": "admin-settings",
            "url": "/admin/settings",
            "icon": "fas fa-cog",
            "...": "(3개 키 생략, 총 8개)"
        },
        {
            "id": 21,
            "name": {
                "ko": "알림 발송 이력",
                "en": "Notification Logs",
                "ja": "通知発送履歴"
            },
            "slug": "admin-notification-logs",
            "url": "/admin/notification-logs",
            "icon": "fas fa-bell",
            "...": "(3개 키 생략, 총 8개)"
        },
        {
            "id": 22,
            "name": {
                "ko": "본인인증 이력",
                "en": "Identity Logs",
                "ja": "本人認証履歴"
            },
            "slug": "admin-identity-logs",
            "url": "/admin/identity/logs",
            "icon": "fas fa-clipboard-check",
            "...": "(3개 키 생략, 총 8개)"
        },
        {
            "id": 23,
            "name": {
                "ko": "활동 로그",
                "en": "Activity Logs",
                "ja": "アクティビティログ"
            },
            "slug": "admin-activity-logs",
            "url": "/admin/activity-logs",
            "icon": "fas fa-history",
            "...": "(3개 키 생략, 총 8개)"
        },
        "... (총 16건 중 5건 표시)"
    ]
}

에러 응답

상태코드 의미 발생 조건
401 Unauthenticated 유효한 Bearer 토큰이 없거나 만료된 경우
403 Forbidden 요구 권한(core.menus.read)이 없는 경우
422 Unprocessable Entity 요청 파라미터가 검증 규칙을 위반한 경우 (error.errors 에 필드별 메시지)

설명

관리자 레이아웃 전역 부트스트랩 엔드포인트. sirsoft-admin_basic/layouts/_admin_base.json 의 admin_menu data_source(/api/admin/menus/active)가 모든 관리자 페이지 진입 시 자동 호출해, 사이드바 메뉴 트리를 채운다. 인증 사용자의 역할 기준으로 접근 가능한 활성 메뉴만 계층 구조(children 중첩)로 반환하며, data 는 최상위 메뉴 배열이다. 사용자 정보가 없을 때는 모든 활성 메뉴를 fallback 으로 반환한다. 인증 계약: auth:sanctum + permission:core.menus.read.

GET /api/admin/menus/extension/{type}/{identifier}

  • 라우트명: api.admin.menus.by-extension
  • 컨트롤러: App\Http\Controllers\Api\Admin\MenuController@getByExtension
  • 인증/권한: auth:sanctum + permission:core.menus.read

요청 파라미터

이름 위치 타입 필수 허용값 용도
type path string 예 — 확장 소유 타입 (ExtensionOwnerType 으로 파싱 — module: 모듈, plugin: 플러그인. 미유효 값이면 422 menu.invalid_extension_type)
identifier path string 예 — 대상 리소스의 식별자

요청 예시

GET /api/admin/menus/extension/{type}/{identifier} HTTP/1.1
Host: api.example.com
Accept: application/json
Authorization: Bearer {YOUR_TOKEN}

응답 필드 (data 내부)

목록 응답: data.data[] 배열 항목의 필드 + 컬렉션 레벨 data.abilities.

필드 타입 실측 예시값 용도/설명
id integer 15 기본 키 (내부 식별자)
name object {"ko":"게시판 관리","en":"Board Management"} 메뉴 이름 (다국어 JSON)
slug string sirsoft-board 메뉴 슬러그
url string | null null 메뉴 URL (그룹 메뉴는 null)
icon string | null fas fa-clipboard-list 메뉴 아이콘 (Font Awesome 클래스)
order integer 30 메뉴 순서 (작을수록 우선 — order 오름차순 정렬)
is_active boolean true 활성 여부
parent_id integer | null null 상위 메뉴 ID (최상위는 null)
extension_type string module 확장 소유 타입 (module / plugin)
extension_identifier string sirsoft-board 확장 식별자 (요청 path 의 identifier 와 동일)
parent object | null null 상위 항목 객체 (id/name/url/icon — parent 관계 파생)
children array [{"id":16,"name":{"ko":"환경설정"},"slug":"sirsoft-board-settings", ...}] 하위 메뉴 배열 (order 오름차순 — children 관계 파생)
creator object | null null 생성자 정보 객체 (uuid/name/email — creator 관계 파생)
created_at string 2026-07-08 10:44:35 생성 일시
updated_at string 2026-07-08 10:44:35 최종 수정 일시
is_owner boolean false 현재 인증 사용자가 이 리소스의 소유자인지 여부 (BaseApiResource 표준 메타)
abilities object {"can_create":true,"can_update":true,"can_delete":true} 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵

roles 관계는 이 엔드포인트에서 eager-load 되지 않으므로(MenuRepository::getMenusByExtension 은 creator/parent/children 만 로드) 항목에 roles 키가 포함되지 않습니다.

응답 예시

HTTP/1.1 200
{
    "success": true,
    "message": "메뉴를 성공적으로 가져왔습니다.",
    "data": {
        "data": [
            {
                "id": 15,
                "name": {
                    "ko": "게시판 관리",
                    "en": "Board Management"
                },
                "slug": "sirsoft-board",
                "url": null,
                "icon": "fas fa-clipboard-list",
                "order": 30,
                "is_active": true,
                "parent_id": null,
                "extension_type": "module",
                "extension_identifier": "sirsoft-board",
                "parent": null,
                "children": [
                    {
                        "id": 16,
                        "name": {
                            "ko": "환경설정",
                            "en": "Settings"
                        },
                        "slug": "sirsoft-board-settings",
                        "url": "/admin/boards/settings",
                        "icon": "fas fa-cog",
                        "order": 1,
                        "is_active": true,
                        "parent_id": 15,
                        "extension_type": "module",
                        "extension_identifier": "sirsoft-board",
                        "roles": []
                    }
                ],
                "creator": null,
                "created_at": "2026-07-08 10:44:35",
                "updated_at": "2026-07-08 10:44:35",
                "is_owner": false,
                "abilities": {
                    "can_create": true,
                    "can_update": true,
                    "can_delete": true
                }
            }
        ],
        "abilities": {
            "can_create": true,
            "can_update": true,
            "can_delete": true
        }
    }
}

에러 응답

상태코드 의미 발생 조건
401 Unauthenticated 유효한 Bearer 토큰이 없거나 만료된 경우
403 Forbidden 요구 권한(core.menus.read)이 없는 경우
404 Not Found path 파라미터에 해당하는 리소스가 없는 경우
422 Unprocessable Entity 요청 파라미터가 검증 규칙을 위반한 경우 (error.errors 에 필드별 메시지)

설명

특정 확장이 소유한 메뉴 목록을 조회한다. type 은 ExtensionOwnerType(module, plugin)으로 파싱되며, 유효하지 않은 값이면 422 menu.invalid_extension_type 을 반환한다. identifier 는 확장 식별자(예: sirsoft-board)로, 해당 확장이 등록한 메뉴만 MenuCollection 으로 내려준다. 확장 설치/제거 시 그 확장 소유 메뉴를 확인·정리하는 관리 흐름에서 사용된다. 인증 계약: auth:sanctum + permission:core.menus.read.

GET /api/admin/menus/hierarchy

  • 라우트명: api.admin.menus.hierarchy
  • 컨트롤러: App\Http\Controllers\Api\Admin\MenuController@hierarchy
  • 인증/권한: auth:sanctum + permission:core.menus.read

요청 파라미터

요청 파라미터 없음.

요청 예시

GET /api/admin/menus/hierarchy HTTP/1.1
Host: api.example.com
Accept: application/json
Authorization: Bearer {YOUR_TOKEN}

응답 필드 (data 내부)

목록 응답: data.data[] 배열 항목의 필드.

필드 타입 실측 예시값 용도/설명
id integer 19 기본 키 (내부 식별자)
name object {"ko":"대시보드","en":"Dashboard","ja":"ダッシュボード"} 메뉴 이름 (다국어 JSON)
slug string admin-dashboard 메뉴 슬러그
url string /admin/dashboard 메뉴 URL
icon string fas fa-tachometer-alt 메뉴 아이콘
order integer 1 메뉴 순서
is_active boolean true active 여부
children array [] 하위 항목 배열 (계층 트리 — children 관계 파생)

응답 예시

HTTP/1.1 200
{
    "success": true,
    "message": "메뉴를 성공적으로 가져왔습니다.",
    "data": {
        "data": [
            {
                "id": 19,
                "name": {
                    "ko": "대시보드",
                    "en": "Dashboard",
                    "ja": "ダッシュボード"
                },
                "slug": "admin-dashboard",
                "url": "/admin/dashboard",
                "icon": "fas fa-tachometer-alt",
                "order": 1,
                "is_active": true,
                "children": []
            },
            {
                "id": 20,
                "name": {
                    "ko": "환경설정",
                    "en": "Settings",
                    "ja": "環境設定"
                },
                "slug": "admin-settings",
                "url": "/admin/settings",
                "icon": "fas fa-cog",
                "order": 2,
                "is_active": true,
                "children": []
            },
            "... (총 17건 중 2건 표시)"
        ]
    }
}

에러 응답

상태코드 의미 발생 조건
401 Unauthenticated 유효한 Bearer 토큰이 없거나 만료된 경우
403 Forbidden 요구 권한(core.menus.read)이 없는 경우
422 Unprocessable Entity 요청 파라미터가 검증 규칙을 위반한 경우 (error.errors 에 필드별 메시지)

설명

전체 메뉴를 계층 구조로 반환한다. MenuService::getMenuHierarchy() 로 조회한 메뉴를 MenuCollection::toNavigationArray() 로 변환해 최상위 메뉴 + children 중첩 형태로 내려준다. active 와 달리 사용자 역할 기반 접근 필터 없이 메뉴 트리 전체를 노출하므로, 관리자 메뉴 관리 화면의 트리 표현·순서 편집 기준 데이터로 사용된다. 인증 계약: auth:sanctum + permission:core.menus.read.

PUT /api/admin/menus/order

  • 라우트명: api.admin.menus.update-order
  • 컨트롤러: App\Http\Controllers\Api\Admin\MenuController@updateOrder
  • 인증/권한: auth:sanctum + permission:core.menus.update

요청 파라미터

이름 위치 타입 필수 허용값 용도
parent_menus body array 예 min 1 최상위 메뉴의 새 순서 목록 (원소 {id, order} — id: 대상 메뉴, order: 1 이상 표시 순번)
child_menus body array 아니오 — 부모별 하위 메뉴의 새 순서 목록 (부모 그룹핑된 2차원 배열, 각 원소 {id, order} — id: 하위 메뉴, order: 1 이상 표시 순번)
moved_items body array 아니오 — 부모가 바뀐 항목 목록 (원소 {id, new_parent_id} — new_parent_id 는 새 부모 메뉴 ID 또는 null(최상위 이동), 순환 참조(자기 자신·자손 지정) 시 거부)

이 엔드포인트는 확장이 파라미터를 추가할 수 있습니다 (core.menu.update_order_validation_rules).

요청 예시

PUT /api/admin/menus/order HTTP/1.1
Host: api.example.com
Accept: application/json
Authorization: Bearer {YOUR_TOKEN}
Content-Type: application/json

{
    "parent_menus": [
        "예시값"
    ],
    "child_menus": [
        "예시값"
    ],
    "moved_items": [
        "예시값"
    ]
}

응답 필드 (data 내부)

이 엔드포인트는 data 를 반환하지 않습니다 (성공 메시지만 — 컨트롤러가 success('menu.order_update_success') 를 데이터 없이 호출).

응답 예시

HTTP/1.1 200
{
    "success": true,
    "message": "메뉴 순서가 성공적으로 업데이트되었습니다.",
    "data": null
}

에러 응답

상태코드 의미 발생 조건
401 Unauthenticated 유효한 Bearer 토큰이 없거나 만료된 경우
403 Forbidden 요구 권한(core.menus.update)이 없는 경우
422 Unprocessable Entity 요청 파라미터가 검증 규칙을 위반한 경우 (error.errors 에 필드별 메시지). moved_items[].new_parent_id 가 자기 자신·자손을 가리키는 순환 참조인 경우 포함

설명

관리자 메뉴 관리 화면의 드래그 앤 드롭 순서 변경을 반영한다. parent_menus 는 {id, order} 배열로 최상위 메뉴 순서를, child_menus 는 부모별 하위 메뉴 순서를, moved_items 는 부모가 바뀐 항목({id, new_parent_id})을 전달한다. moved_items 의 new_parent_id 에는 순환 참조 방지(NotCircularParent) 검증이 적용되어, 자기 자신이나 자손을 부모로 지정하면 거부된다. 각 id/parent_id 는 실제 메뉴로 존재해야 한다. 응답은 성공 메시지만 반환한다. 인증 계약: auth:sanctum + permission:core.menus.update.

DELETE /api/admin/menus/{menu}

  • 라우트명: api.admin.menus.destroy
  • 컨트롤러: App\Http\Controllers\Api\Admin\MenuController@destroy
  • 인증/권한: auth:sanctum + permission:core.menus.delete

요청 파라미터

이름 위치 타입 필수 허용값 용도
menu path string 예 — 대상 menu의 식별자

요청 예시

DELETE /api/admin/menus/{menu} HTTP/1.1
Host: api.example.com
Accept: application/json
Authorization: Bearer {YOUR_TOKEN}

응답 필드 (data 내부)

이 엔드포인트는 data 를 반환하지 않습니다 (성공 메시지만 — 컨트롤러가 success('menu.delete_success') 를 데이터 없이 호출).

응답 예시

HTTP/1.1 200
{
    "success": true,
    "message": "메뉴가 성공적으로 삭제되었습니다.",
    "data": null
}

에러 응답

상태코드 의미 발생 조건
401 Unauthenticated 유효한 Bearer 토큰이 없거나 만료된 경우
403 Forbidden 요구 권한(core.menus.delete)이 없는 경우
404 Not Found path 파라미터에 해당하는 리소스가 없는 경우
422 Unprocessable Entity 요청 파라미터가 검증 규칙을 위반한 경우 (error.errors 에 필드별 메시지)

설명

지정한 메뉴를 삭제한다. {menu} 는 라우트 모델 바인딩으로 해석되며, 삭제는 MenuService::deleteMenu() 가 수행한다. 성공 시 menu.delete_success 메시지만 반환하고, 삭제 불가 조건은 422 menu.delete_failed 로 내려온다. 관리자 메뉴 관리 화면의 메뉴 삭제 동작에서 소비된다. 인증 계약: auth:sanctum + permission:core.menus.delete.

GET /api/admin/menus/{menu}

  • 라우트명: api.admin.menus.show
  • 컨트롤러: App\Http\Controllers\Api\Admin\MenuController@show
  • 인증/권한: auth:sanctum + permission:core.menus.read

요청 파라미터

이름 위치 타입 필수 허용값 용도
menu path string 예 — 대상 menu의 식별자

요청 예시

GET /api/admin/menus/{menu} HTTP/1.1
Host: api.example.com
Accept: application/json
Authorization: Bearer {YOUR_TOKEN}

응답 필드 (data 내부)

단건 응답: data 객체의 필드.

필드 타입 실측 예시값 용도/설명
id integer 1 기본 키 (내부 식별자)
name object {"ko":"게시판 관리","en":"Board Management"} 메뉴 이름 (다국어 JSON)
slug string sirsoft-board 메뉴 슬러그
url null null 메뉴 URL
icon string fas fa-clipboard-list 메뉴 아이콘
order integer 30 메뉴 순서
is_active boolean true active 여부
parent_id null null 상위 메뉴 ID
extension_type string module 확장 소유 타입: core(코어), module(모듈), plugin(플러그인), NULL(사용자 정의)
extension_identifier string sirsoft-board 확장 식별자 (예: core, sirsoft-board, sirsoft-payment)
parent null null 상위 항목 객체 (parent 관계 파생)
children array [{"id":2,"name":{"ko":"환경설정","en":"Settings"},"slug":"sir… 하위 항목 배열 (계층 트리 — children 관계 파생)
creator null null 생성자 정보 객체 (uuid/name/email — creator 관계 파생)
roles array [{"id":1,"name":{"ko":"관리자","en":"Administrator","ja":"管理… 이 메뉴 노출이 허용된 역할 목록 (원소 id/name/permission_type — roles 관계 파생, permission_type 은 pivot 의 노출 권한 유형)
created_at string 2026-07-30 18:45:11 생성 일시
updated_at string 2026-07-30 18:45:11 최종 수정 일시
is_owner boolean false 현재 인증 사용자가 이 리소스의 소유자인지 여부 (BaseApiResource 표준 메타)
abilities object {"can_create":true,"can_update":true,"can_delete":true} 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반)

응답 예시

HTTP/1.1 200
{
    "success": true,
    "message": "메뉴를 성공적으로 가져왔습니다.",
    "data": {
        "id": 1,
        "name": {
            "ko": "게시판 관리",
            "en": "Board Management"
        },
        "slug": "sirsoft-board",
        "url": null,
        "icon": "fas fa-clipboard-list",
        "order": 30,
        "is_active": true,
        "parent_id": null,
        "extension_type": "module",
        "extension_identifier": "sirsoft-board",
        "parent": null,
        "children": [
            {
                "id": 2,
                "name": {
                    "ko": "환경설정",
                    "en": "Settings"
                },
                "slug": "sirsoft-board-settings",
                "url": "/admin/boards/settings",
                "icon": "fas fa-cog",
                "order": 1,
                "is_active": true,
                "parent_id": 1,
                "extension_type": "module",
                "extension_identifier": "sirsoft-board",
                "roles": []
            },
            {
                "id": 3,
                "name": {
                    "ko": "게시판 목록",
                    "en": "Board List"
                },
                "slug": "sirsoft-board-list",
                "url": "/admin/boards",
                "icon": "fas fa-list",
                "order": 2,
                "is_active": true,
                "parent_id": 1,
                "extension_type": "module",
                "extension_identifier": "sirsoft-board",
                "roles": []
            },
            {
                "id": 4,
                "name": {
                    "ko": "게시판 신고현황",
                    "en": "Board Reports"
                },
                "slug": "sirsoft-board-reports",
                "url": "/admin/boards/reports",
                "icon": "fas fa-flag",
                "order": 3,
                "is_active": true,
                "parent_id": 1,
                "extension_type": "module",
                "extension_identifier": "sirsoft-board",
                "roles": []
            }
        ],
        "creator": null,
        "roles": [
            {
                "id": 1,
                "name": {
                    "ko": "관리자",
                    "en": "Administrator",
                    "ja": "管理者"
                },
                "permission_type": "read"
            }
        ],
        "created_at": "2026-07-30 18:45:11",
        "updated_at": "2026-07-30 18:45:11",
        "is_owner": false,
        "abilities": {
            "can_create": true,
            "can_update": true,
            "can_delete": true
        }
    }
}

에러 응답

상태코드 의미 발생 조건
401 Unauthenticated 유효한 Bearer 토큰이 없거나 만료된 경우
403 Forbidden 요구 권한(core.menus.read)이 없는 경우
404 Not Found path 파라미터에 해당하는 리소스가 없는 경우
422 Unprocessable Entity 요청 파라미터가 검증 규칙을 위반한 경우 (error.errors 에 필드별 메시지)

설명

단일 메뉴의 상세 정보를 조회한다. {menu} 는 라우트 모델 바인딩으로 해석되며, creator/parent/children/roles 관계를 eager-load 해 함께 반환한다. children 에는 하위 메뉴가 order 오름차순으로, 각 하위 메뉴의 roles 까지 포함된다. 관리자 메뉴 관리 화면의 메뉴 편집 폼 초기값 로딩에 사용된다. 인증 계약: auth:sanctum + permission:core.menus.read.

PUT /api/admin/menus/{menu}

  • 라우트명: api.admin.menus.update
  • 컨트롤러: App\Http\Controllers\Api\Admin\MenuController@update
  • 인증/권한: auth:sanctum + permission:core.menus.update

요청 파라미터

이름 위치 타입 필수 허용값 용도
menu path string 예 — 대상 menu의 식별자
name body string 아니오 — 대상의 이름/명칭
slug body string 예 max 255 URL 친화 식별자 (slug)
url body string 아니오 max 500 URL
icon body string 아니오 max 100 아이콘
parent_id body integer 아니오 — parent 식별자
order body integer 아니오 min 0 표시 정렬 순서 값 (작을수록 우선)
is_active body boolean 아니오 — 활성 여부 (true 활성 / false 비활성)
extension_type body string 아니오 core, module, plugin 확장 유형 (core/module/plugin/template)
extension_identifier body string 아니오 max 255 확장 식별자
roles body array 아니오 — 이 메뉴 노출을 허용할 역할 ID 배열 (각 원소는 존재하는 role id — 지정 시 노출 허용 역할 목록으로 설정/교체)

이 엔드포인트는 확장이 파라미터를 추가할 수 있습니다 (core.menu.update_validation_rules).

요청 예시

PUT /api/admin/menus/{menu} HTTP/1.1
Host: api.example.com
Accept: application/json
Authorization: Bearer {YOUR_TOKEN}
Content-Type: application/json

{
    "name": "예시 이름",
    "slug": "example-key",
    "url": "https://example.com",
    "icon": "예시값",
    "parent_id": 1,
    "order": 1,
    "is_active": true,
    "extension_type": "core",
    "extension_identifier": "example-key",
    "roles": [
        "예시값"
    ]
}

응답 필드 (data 내부)

단건 응답: data 객체의 필드 (갱신된 메뉴를 MenuResource 로 반환 — creator/parent/children/roles eager-load).

필드 타입 실측 예시값 용도/설명
id integer 1 기본 키 (내부 식별자)
name object {"ko":"API 문서 샘플 메뉴","en":"API Doc Sample Menu"} 메뉴 이름 (다국어 JSON)
slug string apidoc-sample-menu 메뉴 슬러그
url string | null /admin/apidoc-sample 메뉴 URL
icon string | null fas fa-book 메뉴 아이콘
order integer 35 메뉴 순서
is_active boolean true 활성 여부
parent_id integer | null null 상위 메뉴 ID
extension_type string | null core 확장 소유 타입: core / module / plugin / null(사용자 정의)
extension_identifier string | null core 확장 식별자
parent object | null null 상위 항목 객체 (id/name/url/icon — parent 관계 파생)
children array [] 하위 항목 배열 (order 오름차순 — children 관계 파생)
creator object | null {"uuid":"a234c2b1-…","name":"API 문서 샘플 사용자","email":"apidoc-sample-user@example.com"} 생성자 정보 객체 (creator 관계 파생)
roles array [{"id":1,"name":{"ko":"관리자","en":"Administrator"},"permission_type":"read"}] 이 메뉴 노출이 허용된 역할 목록 (요청 roles 로 교체된 결과)
created_at string 2026-07-08 10:41:24 생성 일시
updated_at string 2026-07-08 12:20:11 최종 수정 일시
is_owner boolean true 현재 인증 사용자가 이 리소스의 소유자인지 여부 (BaseApiResource 표준 메타)
abilities object {"can_create":true,"can_update":true,"can_delete":true} 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵

응답 예시

HTTP/1.1 200
{
    "success": true,
    "message": "메뉴가 성공적으로 업데이트되었습니다.",
    "data": {
        "id": 1,
        "name": {
            "ko": "API 문서 샘플 메뉴",
            "en": "API Doc Sample Menu"
        },
        "slug": "apidoc-sample-menu",
        "url": "/admin/apidoc-sample",
        "icon": "fas fa-book",
        "order": 35,
        "is_active": true,
        "parent_id": null,
        "extension_type": null,
        "extension_identifier": null,
        "parent": null,
        "children": [],
        "creator": {
            "uuid": "a234c2b1-cde8-437f-b28b-23323be2b98d",
            "name": "API 문서 샘플 사용자",
            "email": "apidoc-sample-user@example.com"
        },
        "roles": [
            {
                "id": 1,
                "name": {
                    "ko": "관리자",
                    "en": "Administrator"
                },
                "permission_type": "read"
            }
        ],
        "created_at": "2026-07-08 10:41:24",
        "updated_at": "2026-07-08 12:20:11",
        "is_owner": true,
        "abilities": {
            "can_create": true,
            "can_update": true,
            "can_delete": true
        }
    }
}

에러 응답

상태코드 의미 발생 조건
401 Unauthenticated 유효한 Bearer 토큰이 없거나 만료된 경우
403 Forbidden 요구 권한(core.menus.update)이 없는 경우
404 Not Found path 파라미터에 해당하는 리소스가 없는 경우
422 Unprocessable Entity 요청 파라미터가 검증 규칙을 위반한 경우 (error.errors 에 필드별 메시지)

설명

기존 메뉴 정보를 수정한다. {menu} 는 라우트 모델 바인딩으로 해석되며, 전달된 필드만 갱신한다. slug 는 여전히 유일해야 하고, roles 배열을 보내면 이 메뉴 노출이 허용된 역할 목록을 교체한다. 성공 시 갱신된 메뉴를 관계 eager-load 포함해 MenuResource 로 반환하고, 실패 시 menu.update_failed(422 검증 오류 포함)를 반환한다. 관리자 메뉴 관리 화면의 메뉴 편집 폼 저장에서 소비된다. 인증 계약: auth:sanctum + permission:core.menus.update.

parent_id 에는 자기 자신이나 자신의 하위 메뉴를 지정할 수 없다. 순환이 만들어지는 요청은 422(parent_id)로 거절되며, 순서 변경 엔드포인트(PUT /api/admin/menus/order)와 동일한 기준이 적용된다.

PATCH /api/admin/menus/{menu}/toggle-status

  • 라우트명: api.admin.menus.toggle-status
  • 컨트롤러: App\Http\Controllers\Api\Admin\MenuController@toggleStatus
  • 인증/권한: auth:sanctum + permission:core.menus.update

요청 파라미터

이름 위치 타입 필수 허용값 용도
menu path string 예 — 대상 menu의 식별자

요청 예시

PATCH /api/admin/menus/{menu}/toggle-status HTTP/1.1
Host: api.example.com
Accept: application/json
Authorization: Bearer {YOUR_TOKEN}

응답 필드 (data 내부)

단건 응답: data 객체의 필드.

필드 타입 실측 예시값 용도/설명
id integer 1 기본 키 (내부 식별자)
name object {"ko":"게시판 관리","en":"Board Management"} 대상의 이름/명칭 (다국어 필드는 로케일별 값 객체)
slug string sirsoft-board URL 친화 식별자 (slug)
url null null 메뉴 URL
icon string fas fa-clipboard-list 아이콘 식별자 (아이콘 클래스/이름)
order integer 30 메뉴 순서
is_active boolean false active 여부
parent_id null null parent 식별자 (연관 리소스 참조)
extension_type string module 이 리소스를 소유한 확장의 타입 (core/module/plugin/template)
extension_identifier string sirsoft-board 이 리소스를 소유한 확장의 식별자
created_at string 2026-07-30 18:45:11 생성 일시
updated_at string 2026-08-04 21:53:41 최종 수정 일시
is_owner boolean false 현재 인증 사용자가 이 리소스의 소유자인지 여부 (BaseApiResource 표준 메타)
abilities object {"can_create":true,"can_update":true,"can_delete":true} 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반)

응답 예시

HTTP/1.1 200
{
    "success": true,
    "message": "메뉴가 성공적으로 업데이트되었습니다.",
    "data": {
        "id": 1,
        "name": {
            "ko": "게시판 관리",
            "en": "Board Management"
        },
        "slug": "sirsoft-board",
        "url": null,
        "icon": "fas fa-clipboard-list",
        "order": 30,
        "is_active": false,
        "parent_id": null,
        "extension_type": "module",
        "extension_identifier": "sirsoft-board",
        "created_at": "2026-07-30 18:45:11",
        "updated_at": "2026-08-04 21:53:41",
        "is_owner": false,
        "abilities": {
            "can_create": true,
            "can_update": true,
            "can_delete": true
        }
    }
}

에러 응답

상태코드 의미 발생 조건
401 Unauthenticated 유효한 Bearer 토큰이 없거나 만료된 경우
403 Forbidden 요구 권한(core.menus.update)이 없는 경우
404 Not Found path 파라미터에 해당하는 리소스가 없는 경우
422 Unprocessable Entity 요청 파라미터가 검증 규칙을 위반한 경우 (error.errors 에 필드별 메시지)

설명

지정한 메뉴의 활성화 상태(is_active)를 반대 값으로 토글한다. {menu} 는 라우트 모델 바인딩으로 해석되며, 본문 파라미터 없이 현재 상태를 뒤집는다. 성공 시 갱신된 메뉴를 MenuResource 로 반환한다. 관리자 메뉴 관리 화면의 활성/비활성 스위치에서 소비된다. 인증 계약: auth:sanctum + permission:core.menus.update.