refactor: [authz] 역할/권한 API 품질 개선

- Validator::make를 FormRequest로 분리 (6개 생성)
- 하드코딩 한글 문자열을 i18n 키로 교체
- RoleMenuPermission 데드코드 제거
- Role 모델 SpatieRole 상속으로 일원화
- 권한 변경 후 캐시 무효화 추가 (AccessService::bumpVersion)
- 미문서화 8개 Swagger 엔드포인트 추가
- 역할/권한 라우트에 perm.map+permission 미들웨어 추가
This commit is contained in:
김보곤
2026-02-20 21:59:26 +09:00
parent 555fd196f5
commit 1dd9057540
21 changed files with 1400 additions and 271 deletions

View File

@@ -46368,7 +46368,7 @@
"Role"
],
"summary": "역할 목록 조회",
"description": "테넌트 범위 내 역할 목록을 페이징으로 반환합니다. (q로 이름/설명 검색)",
"description": "테넌트 범위 내 역할 목록을 페이징으로 반환합니다. (q로 이름/설명 검색, is_hidden으로 필터)",
"operationId": "2fe3440eb56182754caf817600b13375",
"parameters": [
{
@@ -46397,6 +46397,16 @@
"type": "string",
"example": "read"
}
},
{
"name": "is_hidden",
"in": "query",
"description": "숨김 상태 필터",
"required": false,
"schema": {
"type": "boolean",
"example": false
}
}
],
"responses": {
@@ -46853,6 +46863,148 @@
]
}
},
"/api/v1/roles/stats": {
"get": {
"tags": [
"Role"
],
"summary": "역할 통계 조회",
"description": "테넌트 범위 내 역할 통계(전체/공개/숨김/사용자 보유)를 반환합니다.",
"operationId": "419d5a08537494bf256b10661e221944",
"responses": {
"200": {
"description": "통계 조회 성공",
"content": {
"application/json": {
"schema": {
"allOf": [
{
"$ref": "#/components/schemas/ApiResponse"
},
{
"properties": {
"data": {
"$ref": "#/components/schemas/RoleStats"
}
},
"type": "object"
}
]
}
}
}
},
"401": {
"description": "인증 실패",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "서버 에러",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
},
"security": [
{
"ApiKeyAuth": []
},
{
"BearerAuth": []
}
]
}
},
"/api/v1/roles/active": {
"get": {
"tags": [
"Role"
],
"summary": "활성 역할 목록 (드롭다운용)",
"description": "숨겨지지 않은 활성 역할 목록을 이름순으로 반환합니다. (id, name, description만 포함)",
"operationId": "8663eac59de3903354a3d5dd4502a5bf",
"responses": {
"200": {
"description": "목록 조회 성공",
"content": {
"application/json": {
"schema": {
"allOf": [
{
"$ref": "#/components/schemas/ApiResponse"
},
{
"properties": {
"data": {
"type": "array",
"items": {
"properties": {
"id": {
"type": "integer",
"example": 1
},
"name": {
"type": "string",
"example": "admin"
},
"description": {
"type": "string",
"example": "관리자",
"nullable": true
}
},
"type": "object"
}
}
},
"type": "object"
}
]
}
}
}
},
"401": {
"description": "인증 실패",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "서버 에러",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
},
"security": [
{
"ApiKeyAuth": []
},
{
"BearerAuth": []
}
]
}
},
"/api/v1/roles/{id}/permissions": {
"get": {
"tags": [
@@ -47245,6 +47397,467 @@
]
}
},
"/api/v1/role-permissions/menus": {
"get": {
"tags": [
"RolePermission"
],
"summary": "권한 매트릭스용 메뉴 트리 조회",
"description": "활성 메뉴를 플랫 배열(depth 포함)로 반환하고, 사용 가능한 권한 유형 목록을 함께 반환합니다.",
"operationId": "1eea6074af7fe23108049fc436ae4b8f",
"responses": {
"200": {
"description": "조회 성공",
"content": {
"application/json": {
"schema": {
"allOf": [
{
"$ref": "#/components/schemas/ApiResponse"
},
{
"properties": {
"data": {
"$ref": "#/components/schemas/PermissionMenuTree"
}
},
"type": "object"
}
]
}
}
}
},
"401": {
"description": "인증 실패",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "서버 에러",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
},
"security": [
{
"ApiKeyAuth": []
},
{
"BearerAuth": []
}
]
}
},
"/api/v1/roles/{id}/permissions/matrix": {
"get": {
"tags": [
"RolePermission"
],
"summary": "역할의 권한 매트릭스 조회",
"description": "해당 역할에 부여된 메뉴별 권한 매트릭스를 반환합니다.",
"operationId": "18e9a32f62613b9cd3d41e79f500d122",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "integer"
},
"example": 1
}
],
"responses": {
"200": {
"description": "조회 성공",
"content": {
"application/json": {
"schema": {
"allOf": [
{
"$ref": "#/components/schemas/ApiResponse"
},
{
"properties": {
"data": {
"$ref": "#/components/schemas/RolePermissionMatrix"
}
},
"type": "object"
}
]
}
}
}
},
"404": {
"description": "역할 없음",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "인증 실패",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "서버 에러",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
},
"security": [
{
"ApiKeyAuth": []
},
{
"BearerAuth": []
}
]
}
},
"/api/v1/roles/{id}/permissions/toggle": {
"post": {
"tags": [
"RolePermission"
],
"summary": "특정 메뉴의 특정 권한 토글",
"description": "지정한 메뉴+권한 유형의 부여 상태를 반전합니다. 하위 메뉴에 재귀적으로 전파합니다.",
"operationId": "cd6302edade7b8f79c39a85f8c369638",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "integer"
},
"example": 1
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RolePermissionToggleRequest"
}
}
}
},
"responses": {
"200": {
"description": "토글 성공",
"content": {
"application/json": {
"schema": {
"allOf": [
{
"$ref": "#/components/schemas/ApiResponse"
},
{
"properties": {
"data": {
"$ref": "#/components/schemas/RolePermissionToggleResponse"
}
},
"type": "object"
}
]
}
}
}
},
"404": {
"description": "역할 없음",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "검증 실패",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "인증 실패",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "서버 에러",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
},
"security": [
{
"ApiKeyAuth": []
},
{
"BearerAuth": []
}
]
}
},
"/api/v1/roles/{id}/permissions/allow-all": {
"post": {
"tags": [
"RolePermission"
],
"summary": "모든 권한 허용",
"description": "해당 역할에 모든 활성 메뉴의 모든 권한 유형을 일괄 부여합니다.",
"operationId": "ab526a580d6926ef0971582b9aeb1d58",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "integer"
},
"example": 1
}
],
"responses": {
"200": {
"description": "성공",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiResponse"
}
}
}
},
"404": {
"description": "역할 없음",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "인증 실패",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "서버 에러",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
},
"security": [
{
"ApiKeyAuth": []
},
{
"BearerAuth": []
}
]
}
},
"/api/v1/roles/{id}/permissions/deny-all": {
"post": {
"tags": [
"RolePermission"
],
"summary": "모든 권한 거부",
"description": "해당 역할의 모든 메뉴 권한을 일괄 제거합니다.",
"operationId": "f0120556f6104f5778f13349a5eec469",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "integer"
},
"example": 1
}
],
"responses": {
"200": {
"description": "성공",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiResponse"
}
}
}
},
"404": {
"description": "역할 없음",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "인증 실패",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "서버 에러",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
},
"security": [
{
"ApiKeyAuth": []
},
{
"BearerAuth": []
}
]
}
},
"/api/v1/roles/{id}/permissions/reset": {
"post": {
"tags": [
"RolePermission"
],
"summary": "기본 권한으로 초기화 (view만 허용)",
"description": "해당 역할의 모든 권한을 제거한 후, 모든 활성 메뉴에 view 권한만 부여합니다.",
"operationId": "7d0ce4d8a4116908a9639c70dc7dba61",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "integer"
},
"example": 1
}
],
"responses": {
"200": {
"description": "성공",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiResponse"
}
}
}
},
"404": {
"description": "역할 없음",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "인증 실패",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "서버 에러",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
},
"security": [
{
"ApiKeyAuth": []
},
{
"BearerAuth": []
}
]
}
},
"/api/v1/sales": {
"get": {
"tags": [
@@ -85344,6 +85957,18 @@
"type": "string",
"example": "api"
},
"is_hidden": {
"type": "boolean",
"example": false
},
"permissions_count": {
"type": "integer",
"example": 12
},
"users_count": {
"type": "integer",
"example": 3
},
"created_at": {
"type": "string",
"format": "date-time",
@@ -85404,6 +86029,11 @@
"type": "string",
"example": "메뉴 관리 역할",
"nullable": true
},
"is_hidden": {
"description": "숨김 여부",
"type": "boolean",
"example": false
}
},
"type": "object"
@@ -85418,6 +86048,32 @@
"type": "string",
"example": "설명 변경",
"nullable": true
},
"is_hidden": {
"type": "boolean",
"example": false
}
},
"type": "object"
},
"RoleStats": {
"description": "역할 통계",
"properties": {
"total": {
"type": "integer",
"example": 5
},
"visible": {
"type": "integer",
"example": 3
},
"hidden": {
"type": "integer",
"example": 2
},
"with_users": {
"type": "integer",
"example": 4
}
},
"type": "object"
@@ -85630,6 +86286,164 @@
}
]
},
"PermissionMenuTree": {
"description": "권한 매트릭스용 메뉴 트리",
"properties": {
"menus": {
"type": "array",
"items": {
"properties": {
"id": {
"type": "integer",
"example": 1
},
"parent_id": {
"type": "integer",
"example": null,
"nullable": true
},
"name": {
"type": "string",
"example": "대시보드"
},
"url": {
"type": "string",
"example": "/dashboard",
"nullable": true
},
"icon": {
"type": "string",
"example": "dashboard",
"nullable": true
},
"sort_order": {
"type": "integer",
"example": 1
},
"is_active": {
"type": "boolean",
"example": true
},
"depth": {
"type": "integer",
"example": 0
},
"has_children": {
"type": "boolean",
"example": true
}
},
"type": "object"
}
},
"permission_types": {
"type": "array",
"items": {
"type": "string"
},
"example": [
"view",
"create",
"update",
"delete",
"approve",
"export",
"manage"
]
}
},
"type": "object"
},
"RolePermissionMatrix": {
"description": "역할의 권한 매트릭스",
"properties": {
"role": {
"properties": {
"id": {
"type": "integer",
"example": 1
},
"name": {
"type": "string",
"example": "admin"
},
"description": {
"type": "string",
"example": "관리자",
"nullable": true
}
},
"type": "object"
},
"permission_types": {
"type": "array",
"items": {
"type": "string"
},
"example": [
"view",
"create",
"update",
"delete",
"approve",
"export",
"manage"
]
},
"permissions": {
"description": "메뉴ID를 키로 한 권한 맵",
"type": "object",
"example": {
"101": {
"view": true,
"create": true
},
"102": {
"view": true
}
},
"additionalProperties": true
}
},
"type": "object"
},
"RolePermissionToggleRequest": {
"required": [
"menu_id",
"permission_type"
],
"properties": {
"menu_id": {
"description": "메뉴 ID",
"type": "integer",
"example": 101
},
"permission_type": {
"description": "권한 유형 (view, create, update, delete, approve, export, manage)",
"type": "string",
"example": "view"
}
},
"type": "object"
},
"RolePermissionToggleResponse": {
"properties": {
"menu_id": {
"type": "integer",
"example": 101
},
"permission_type": {
"type": "string",
"example": "view"
},
"granted": {
"description": "토글 후 권한 부여 상태",
"type": "boolean",
"example": true
}
},
"type": "object"
},
"Sale": {
"description": "매출 정보",
"properties": {