Files
sam-api/app/Swagger/v1/MenuApi.php
kent 8df7f2cc09 docs: Swagger 미문서화 API 29개 엔드포인트 문서화
- P1: AuthApi.php - token-login 추가
- P1: DepartmentApi.php - departments/tree 추가
- P2: AdminGlobalMenuApi.php 신규 생성 (9개 엔드포인트)
- P3: ModelSetApi.php 신규 생성 (10개 엔드포인트)
- P4: MenuApi.php 동기화 관련 7개 엔드포인트 추가

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-21 14:55:16 +09:00

494 lines
24 KiB
PHP

<?php
namespace App\Swagger\v1;
/**
* @OA\Tag(name="Menu", description="메뉴 관리(목록/조회/등록/수정/삭제/정렬/토글)")
*/
/**
* @OA\Schema(
* schema="Menu",
* type="object",
* description="메뉴 상세",
* required={"id","name"},
*
* @OA\Property(property="id", type="integer", example=12),
* @OA\Property(property="tenant_id", type="integer", nullable=true, example=1, description="null=공용"),
* @OA\Property(property="parent_id", type="integer", nullable=true, example=null),
* @OA\Property(property="name", type="string", example="메뉴 관리"),
* @OA\Property(property="slug", type="string", nullable=true, example="menu.manage"),
* @OA\Property(property="url", type="string", nullable=true, example="/admin/menus"),
* @OA\Property(property="is_active", type="integer", example=1),
* @OA\Property(property="sort_order", type="integer", example=10),
* @OA\Property(property="hidden", type="integer", example=0),
* @OA\Property(property="is_external", type="integer", example=0),
* @OA\Property(property="external_url", type="string", nullable=true, example=null),
* @OA\Property(property="icon", type="string", nullable=true, example="list"),
* @OA\Property(property="created_at", type="string", format="date-time", example="2025-08-15 10:00:00"),
* @OA\Property(property="updated_at", type="string", format="date-time", example="2025-08-15 10:00:00"),
* @OA\Property(property="deleted_at", type="string", format="date-time", nullable=true, example=null)
* )
*
* @OA\Schema(
* schema="MenuBrief",
* type="object",
* description="메뉴 요약",
* required={"id","name"},
*
* @OA\Property(property="id", type="integer", example=12),
* @OA\Property(property="parent_id", type="integer", nullable=true, example=null),
* @OA\Property(property="name", type="string", example="메뉴 관리"),
* @OA\Property(property="slug", type="string", nullable=true, example="menu.manage"),
* @OA\Property(property="url", type="string", nullable=true, example="/admin/menus"),
* @OA\Property(property="is_active", type="integer", example=1),
* @OA\Property(property="sort_order", type="integer", example=10),
* @OA\Property(property="hidden", type="integer", example=0),
* @OA\Property(property="is_external", type="integer", example=0),
* @OA\Property(property="external_url", type="string", nullable=true, example=null),
* @OA\Property(property="icon", type="string", nullable=true, example="list")
* )
*
* @OA\Schema(
* schema="MenuList",
* type="array",
*
* @OA\Items(ref="#/components/schemas/MenuBrief")
* )
*
* @OA\Schema(
* schema="MenuCreateRequest",
* type="object",
* required={"name"},
*
* @OA\Property(property="parent_id", type="integer", nullable=true, example=null, description="상위 메뉴 ID"),
* @OA\Property(property="name", type="string", example="새 메뉴", description="메뉴명"),
* @OA\Property(property="slug", type="string", nullable=true, example="menu.new", description="권한 키로도 활용"),
* @OA\Property(property="url", type="string", nullable=true, example="/admin/new"),
* @OA\Property(property="is_active", type="boolean", example=true),
* @OA\Property(property="sort_order", type="integer", example=0),
* @OA\Property(property="hidden", type="boolean", example=false),
* @OA\Property(property="is_external", type="boolean", example=false),
* @OA\Property(property="external_url", type="string", nullable=true, example=null),
* @OA\Property(property="icon", type="string", nullable=true, example="plus")
* )
*
* @OA\Schema(
* schema="MenuUpdateRequest",
* type="object",
*
* @OA\Property(property="parent_id", type="integer", nullable=true, example=null),
* @OA\Property(property="name", type="string", example="메뉴명 변경"),
* @OA\Property(property="slug", type="string", nullable=true, example="menu.changed"),
* @OA\Property(property="url", type="string", nullable=true, example="/admin/changed"),
* @OA\Property(property="is_active", type="boolean", example=true),
* @OA\Property(property="sort_order", type="integer", example=5),
* @OA\Property(property="hidden", type="boolean", example=false),
* @OA\Property(property="is_external", type="boolean", example=false),
* @OA\Property(property="external_url", type="string", nullable=true, example=null),
* @OA\Property(property="icon", type="string", nullable=true, example="edit")
* )
*/
class MenuApi
{
/**
* @OA\Get(
* path="/api/v1/menus",
* summary="메뉴 목록 조회",
* description="테넌트 범위 내(또는 공용) 메뉴 목록을 반환합니다. parent_id/is_active/hidden 필터 지원.",
* tags={"Menu"},
* security={{"ApiKeyAuth": {}},{"BearerAuth": {}}},
*
* @OA\Parameter(name="parent_id", in="query", required=false, @OA\Schema(type="integer", example=0)),
* @OA\Parameter(name="is_active", in="query", required=false, @OA\Schema(type="integer", example=1)),
* @OA\Parameter(name="hidden", in="query", required=false, @OA\Schema(type="integer", example=0)),
*
* @OA\Response(response=200, description="목록 조회 성공",
*
* @OA\JsonContent(allOf={@OA\Schema(ref="#/components/schemas/ApiResponse"), @OA\Schema(@OA\Property(property="data", ref="#/components/schemas/MenuList"))})
* ),
*
* @OA\Response(response=401, description="인증 실패", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
* @OA\Response(response=403, description="권한 없음", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
* @OA\Response(response=500, description="서버 에러", @OA\JsonContent(ref="#/components/schemas/ErrorResponse"))
* )
*/
public function index() {}
/**
* @OA\Get(
* path="/api/v1/menus/{id}",
* summary="메뉴 단건 조회",
* description="ID로 메뉴 상세 정보를 조회합니다.",
* tags={"Menu"},
* security={{"ApiKeyAuth": {}},{"BearerAuth": {}}},
*
* @OA\Parameter(name="id", in="path", required=true, @OA\Schema(type="integer", example=12)),
*
* @OA\Response(response=200, description="단건 조회 성공",
*
* @OA\JsonContent(allOf={@OA\Schema(ref="#/components/schemas/ApiResponse"), @OA\Schema(@OA\Property(property="data", ref="#/components/schemas/Menu"))})
* ),
*
* @OA\Response(response=404, description="데이터 없음", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
* @OA\Response(response=401, description="인증 실패", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
* @OA\Response(response=403, description="권한 없음", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
* @OA\Response(response=500, description="서버 에러", @OA\JsonContent(ref="#/components/schemas/ErrorResponse"))
* )
*/
public function show() {}
/**
* @OA\Post(
* path="/api/v1/menus",
* summary="메뉴 등록",
* description="새로운 메뉴를 등록합니다.",
* tags={"Menu"},
* security={{"ApiKeyAuth": {}},{"BearerAuth": {}}},
*
* @OA\RequestBody(required=true, @OA\JsonContent(ref="#/components/schemas/MenuCreateRequest")),
*
* @OA\Response(response=200, description="등록 성공",
*
* @OA\JsonContent(allOf={@OA\Schema(ref="#/components/schemas/ApiResponse"), @OA\Schema(@OA\Property(property="data", type="object", @OA\Property(property="id", type="integer", example=12)))})
* ),
*
* @OA\Response(response=400, description="파라미터 오류", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
* @OA\Response(response=401, description="인증 실패", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
*
* @OA\ResponsE(response=403, description="권한 없음", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
*
* @OA\Response(response=500, description="서버 에러", @OA\JsonContent(ref="#/components/schemas/ErrorResponse"))
* )
*/
public function store() {}
/**
* @OA\Patch(
* path="/api/v1/menus/{id}",
* summary="메뉴 수정",
* description="기존 메뉴 정보를 수정합니다(부분 수정).",
* tags={"Menu"},
* security={{"ApiKeyAuth": {}},{"BearerAuth": {}}},
*
* @OA\Parameter(name="id", in="path", required=true, @OA\Schema(type="integer", example=12)),
*
* @OA\RequestBody(required=true, @OA\JsonContent(ref="#/components/schemas/MenuUpdateRequest")),
*
* @OA\Response(response=200, description="수정 성공",
*
* @OA\JsonContent(allOf={@OA\Schema(ref="#/components/schemas/ApiResponse"), @OA\Schema(@OA\Property(property="data", type="object", @OA\Property(property="id", type="integer", example=12)))})
* ),
*
* @OA\Response(response=400, description="파라미터 오류", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
* @OA\Response(response=401, description="인증 실패", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
* @OA\Response(response=403, description="권한 없음", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
* @OA\Response(response=404, description="데이터 없음", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
* @OA\Response(response=500, description="서버 에러", @OA\JsonContent(ref="#/components/schemas/ErrorResponse"))
* )
*/
public function update() {}
/**
* @OA\Delete(
* path="/api/v1/menus/{id}",
* summary="메뉴 삭제(소프트 삭제)",
* description="지정한 메뉴를 소프트 삭제합니다.",
* tags={"Menu"},
* security={{"ApiKeyAuth": {}},{"BearerAuth": {}}},
*
* @OA\Parameter(name="id", in="path", required=true, @OA\Schema(type="integer", example=12)),
*
* @OA\Response(response=200, description="삭제 성공",
*
* @OA\JsonContent(allOf={@OA\Schema(ref="#/components/schemas/ApiResponse"), @OA\Schema(@OA\Property(property="data", type="object", @OA\Property(property="id", type="integer", example=12), @OA\Property(property="deleted", type="boolean", example=true)))})
* ),
*
* @OA\Response(response=401, description="인증 실패", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
* @OA\Response(response=403, description="권한 없음", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
* @OA\Response(response=404, description="데이터 없음", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
* @OA\Response(response=500, description="서버 에러", @OA\JsonContent(ref="#/components/schemas/ErrorResponse"))
* )
*/
public function destroy() {}
/**
* @OA\Post(
* path="/api/v1/menus/reorder",
* summary="메뉴 정렬 변경",
* description="여러 메뉴의 sort_order를 일괄 변경합니다.",
* tags={"Menu"},
* security={{"ApiKeyAuth": {}},{"BearerAuth": {}}},
*
* @OA\RequestBody(required=true, @OA\JsonContent(
* type="array",
*
* @OA\Items(type="object",
*
* @OA\Property(property="id", type="integer", example=12),
* @OA\Property(property="sort_order", type="integer", example=1)
* )
* )),
*
* @OA\Response(response=200, description="정렬 변경 성공", @OA\JsonContent(ref="#/components/schemas/ApiResponse")),
* @OA\Response(response=400, description="파라미터 오류", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
* @OA\Response(response=401, description="인증 실패", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
* @OA\Response(response=403, description="권한 없음", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
* @OA\Response(response=500, description="서버 에러", @OA\JsonContent(ref="#/components/schemas/ErrorResponse"))
* )
*/
public function reorder() {}
/**
* @OA\Post(
* path="/api/v1/menus/{id}/toggle",
* summary="메뉴 상태 토글",
* description="is_active / hidden / is_external 중 하나 이상을 토글합니다.",
* tags={"Menu"},
* security={{"ApiKeyAuth": {}},{"BearerAuth": {}}},
*
* @OA\Parameter(name="id", in="path", required=true, @OA\Schema(type="integer", example=12)),
*
* @OA\RequestBody(required=true, @OA\JsonContent(type="object",
*
* @OA\Property(property="is_active", type="boolean", nullable=true, example=true),
* @OA\Property(property="hidden", type="boolean", nullable=true, example=false),
* @OA\Property(property="is_external", type="boolean", nullable=true, example=false)
* )),
*
* @OA\Response(response=200, description="토글 성공", @OA\JsonContent(ref="#/components/schemas/ApiResponse")),
* @OA\Response(response=400, description="파라미터 오류", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
* @OA\Response(response=401, description="인증 실패", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
* @OA\Response(response=403, description="권한 없음", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
* @OA\Response(response=404, description="데이터 없음", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
* @OA\Response(response=500, description="서버 에러", @OA\JsonContent(ref="#/components/schemas/ErrorResponse"))
* )
*/
public function toggle() {}
/**
* @OA\Get(
* path="/api/v1/menus/trashed",
* summary="삭제된 메뉴 목록 조회",
* description="소프트 삭제된 메뉴 목록을 조회합니다.",
* tags={"Menu"},
* security={{"ApiKeyAuth": {}},{"BearerAuth": {}}},
*
* @OA\Response(response=200, description="삭제된 메뉴 목록 조회 성공",
*
* @OA\JsonContent(
*
* @OA\Property(property="success", type="boolean", example=true),
* @OA\Property(property="message", type="string", example="삭제된 메뉴 목록 조회"),
* @OA\Property(property="data", type="array", @OA\Items(ref="#/components/schemas/Menu"))
* )
* ),
*
* @OA\Response(response=401, description="인증 실패", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
* @OA\Response(response=403, description="권한 없음", @OA\JsonContent(ref="#/components/schemas/ErrorResponse"))
* )
*/
public function trashed() {}
/**
* @OA\Post(
* path="/api/v1/menus/{id}/restore",
* summary="삭제된 메뉴 복원",
* description="소프트 삭제된 메뉴를 복원합니다.",
* tags={"Menu"},
* security={{"ApiKeyAuth": {}},{"BearerAuth": {}}},
*
* @OA\Parameter(name="id", in="path", required=true, description="복원할 메뉴 ID", @OA\Schema(type="integer", example=12)),
*
* @OA\Response(response=200, description="메뉴 복원 성공",
*
* @OA\JsonContent(
*
* @OA\Property(property="success", type="boolean", example=true),
* @OA\Property(property="message", type="string", example="메뉴 복원"),
* @OA\Property(property="data", ref="#/components/schemas/Menu")
* )
* ),
*
* @OA\Response(response=404, description="메뉴 없음", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
* @OA\Response(response=401, description="인증 실패", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
* @OA\Response(response=403, description="권한 없음", @OA\JsonContent(ref="#/components/schemas/ErrorResponse"))
* )
*/
public function restore() {}
/**
* @OA\Get(
* path="/api/v1/menus/available-global",
* summary="복제 가능한 글로벌 메뉴 목록",
* description="테넌트에서 복제(동기화)할 수 있는 글로벌 메뉴 목록을 조회합니다.",
* tags={"Menu"},
* security={{"ApiKeyAuth": {}},{"BearerAuth": {}}},
*
* @OA\Response(response=200, description="글로벌 메뉴 목록 조회 성공",
*
* @OA\JsonContent(
*
* @OA\Property(property="success", type="boolean", example=true),
* @OA\Property(property="message", type="string", example="복제 가능한 글로벌 메뉴 목록"),
* @OA\Property(property="data", type="array",
* @OA\Items(type="object",
* @OA\Property(property="id", type="integer", example=1),
* @OA\Property(property="name", type="string", example="대시보드"),
* @OA\Property(property="url", type="string", nullable=true, example="/dashboard"),
* @OA\Property(property="icon", type="string", nullable=true, example="dashboard"),
* @OA\Property(property="is_synced", type="boolean", example=false, description="이미 동기화된 메뉴 여부"),
* @OA\Property(property="has_updates", type="boolean", example=false, description="업데이트 필요 여부")
* )
* )
* )
* ),
*
* @OA\Response(response=401, description="인증 실패", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
* @OA\Response(response=403, description="권한 없음", @OA\JsonContent(ref="#/components/schemas/ErrorResponse"))
* )
*/
public function availableGlobal() {}
/**
* @OA\Get(
* path="/api/v1/menus/sync-status",
* summary="동기화 상태 조회",
* description="테넌트 메뉴와 글로벌 메뉴 간 동기화 상태를 조회합니다.",
* tags={"Menu"},
* security={{"ApiKeyAuth": {}},{"BearerAuth": {}}},
*
* @OA\Parameter(name="status", in="query", description="상태 필터 (new|synced|modified|outdated)", @OA\Schema(type="string", example="outdated")),
*
* @OA\Response(response=200, description="동기화 상태 조회 성공",
*
* @OA\JsonContent(
*
* @OA\Property(property="success", type="boolean", example=true),
* @OA\Property(property="message", type="string", example="동기화 상태 조회"),
* @OA\Property(property="data", type="object",
* @OA\Property(property="summary", type="object",
* @OA\Property(property="total", type="integer", example=25),
* @OA\Property(property="synced", type="integer", example=20),
* @OA\Property(property="new", type="integer", example=3),
* @OA\Property(property="outdated", type="integer", example=2)
* ),
* @OA\Property(property="items", type="array",
* @OA\Items(type="object",
* @OA\Property(property="global_menu_id", type="integer", example=1),
* @OA\Property(property="tenant_menu_id", type="integer", nullable=true, example=5),
* @OA\Property(property="name", type="string", example="대시보드"),
* @OA\Property(property="status", type="string", example="synced", description="new|synced|modified|outdated"),
* @OA\Property(property="last_synced_at", type="string", format="date-time", nullable=true)
* )
* )
* )
* )
* ),
*
* @OA\Response(response=401, description="인증 실패", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
* @OA\Response(response=403, description="권한 없음", @OA\JsonContent(ref="#/components/schemas/ErrorResponse"))
* )
*/
public function syncStatus() {}
/**
* @OA\Post(
* path="/api/v1/menus/sync",
* summary="메뉴 선택 동기화",
* description="선택한 글로벌 메뉴를 테넌트에 동기화합니다. 신규 생성 또는 기존 메뉴 업데이트.",
* tags={"Menu"},
* security={{"ApiKeyAuth": {}},{"BearerAuth": {}}},
*
* @OA\RequestBody(required=true, @OA\JsonContent(type="object",
*
* @OA\Property(property="menu_ids", type="array", @OA\Items(type="integer"), example={1, 2, 3}, description="동기화할 글로벌 메뉴 ID 목록"),
* @OA\Property(property="force", type="boolean", example=false, description="강제 덮어쓰기 여부 (커스텀 변경사항 무시)")
* )),
*
* @OA\Response(response=200, description="동기화 성공",
*
* @OA\JsonContent(
*
* @OA\Property(property="success", type="boolean", example=true),
* @OA\Property(property="message", type="string", example="메뉴 동기화"),
* @OA\Property(property="data", type="object",
* @OA\Property(property="created", type="integer", example=2),
* @OA\Property(property="updated", type="integer", example=1),
* @OA\Property(property="skipped", type="integer", example=0),
* @OA\Property(property="details", type="array", @OA\Items(type="object",
* @OA\Property(property="menu_id", type="integer"),
* @OA\Property(property="action", type="string", example="created"),
* @OA\Property(property="name", type="string")
* ))
* )
* )
* ),
*
* @OA\Response(response=400, description="파라미터 오류", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
* @OA\Response(response=401, description="인증 실패", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
* @OA\Response(response=403, description="권한 없음", @OA\JsonContent(ref="#/components/schemas/ErrorResponse"))
* )
*/
public function sync() {}
/**
* @OA\Post(
* path="/api/v1/menus/sync-new",
* summary="신규 글로벌 메뉴 일괄 가져오기",
* description="아직 동기화되지 않은 신규 글로벌 메뉴를 일괄 가져옵니다.",
* tags={"Menu"},
* security={{"ApiKeyAuth": {}},{"BearerAuth": {}}},
*
* @OA\Response(response=200, description="신규 메뉴 가져오기 성공",
*
* @OA\JsonContent(
*
* @OA\Property(property="success", type="boolean", example=true),
* @OA\Property(property="message", type="string", example="신규 메뉴 가져오기"),
* @OA\Property(property="data", type="object",
* @OA\Property(property="imported", type="integer", example=5),
* @OA\Property(property="menus", type="array", @OA\Items(ref="#/components/schemas/MenuBrief"))
* )
* )
* ),
*
* @OA\Response(response=401, description="인증 실패", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
* @OA\Response(response=403, description="권한 없음", @OA\JsonContent(ref="#/components/schemas/ErrorResponse"))
* )
*/
public function syncNew() {}
/**
* @OA\Post(
* path="/api/v1/menus/sync-updates",
* summary="변경된 메뉴 일괄 업데이트",
* description="글로벌 메뉴에서 변경된 내용을 테넌트 메뉴에 일괄 반영합니다. 커스텀 수정된 메뉴는 제외됩니다.",
* tags={"Menu"},
* security={{"ApiKeyAuth": {}},{"BearerAuth": {}}},
*
* @OA\Response(response=200, description="업데이트 동기화 성공",
*
* @OA\JsonContent(
*
* @OA\Property(property="success", type="boolean", example=true),
* @OA\Property(property="message", type="string", example="메뉴 업데이트 동기화"),
* @OA\Property(property="data", type="object",
* @OA\Property(property="updated", type="integer", example=3),
* @OA\Property(property="skipped", type="integer", example=2, description="커스텀 수정으로 스킵된 메뉴 수"),
* @OA\Property(property="details", type="array", @OA\Items(type="object",
* @OA\Property(property="menu_id", type="integer"),
* @OA\Property(property="name", type="string"),
* @OA\Property(property="action", type="string", example="updated|skipped")
* ))
* )
* )
* ),
*
* @OA\Response(response=401, description="인증 실패", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
* @OA\Response(response=403, description="권한 없음", @OA\JsonContent(ref="#/components/schemas/ErrorResponse"))
* )
*/
public function syncUpdates() {}
}