docs: MIGRATION_TRACKER 커밋 정보 추가
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
# MNG 메뉴 시스템: DB 기반 동적 메뉴 전환 계획
|
||||
|
||||
> 작성일: 2025-12-16
|
||||
> 수정일: 2025-12-16 (Laravel 12 미들웨어 등록 방식, 확장 테이블 설계 반영)
|
||||
> 목적: mng 사이드바를 DB 기반으로 전환하여 직원별 권한에 따라 메뉴 동적 표시
|
||||
> 선택: **Option A - DB 메뉴 기반**
|
||||
|
||||
@@ -30,7 +31,7 @@
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ 목표 mng 메뉴 시스템 │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ DB (menus 테이블, tenant_id=1) │
|
||||
│ DB (menus + menu_mng_extensions 테이블, tenant_id=1) │
|
||||
│ ├── 일반 메뉴 (역할/부서/개인 권한으로 제어) │
|
||||
│ ├── 개발도구 메뉴 (슈퍼관리자 전용) │
|
||||
│ └── R&D Labs 메뉴 (슈퍼관리자 전용) │
|
||||
@@ -42,7 +43,7 @@
|
||||
|
||||
### 1.3 DB 테이블 구조
|
||||
|
||||
**menus 테이블** (기존)
|
||||
**menus 테이블** (기존 - 수정하지 않음)
|
||||
```
|
||||
id, tenant_id, parent_id, global_menu_id
|
||||
name, url, icon, sort_order
|
||||
@@ -50,6 +51,30 @@ is_active, hidden, is_customized, is_external, external_url
|
||||
created_by, updated_by, deleted_by, created_at, updated_at, deleted_at
|
||||
```
|
||||
|
||||
> ⚠️ **중요**: menus 테이블은 API, React 등 28개 이상 파일에서 사용 중이므로 직접 수정하지 않음
|
||||
|
||||
**menu_mng_extensions 테이블** (신규 - mng 전용 확장)
|
||||
```sql
|
||||
CREATE TABLE menu_mng_extensions (
|
||||
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
menu_id BIGINT UNSIGNED NOT NULL, -- menus 테이블 FK
|
||||
menu_type ENUM('normal', 'dev_tool', 'lab') DEFAULT 'normal',
|
||||
route_name VARCHAR(100) NULL, -- Laravel 라우트 이름
|
||||
section ENUM('main', 'dev_tools', 'labs') DEFAULT 'main',
|
||||
lab_tab CHAR(1) NULL, -- R&D Labs용: 's', 'a', 'm'
|
||||
blade_component VARCHAR(100) NULL, -- 커스텀 Blade 컴포넌트
|
||||
css_class VARCHAR(100) NULL, -- 추가 CSS 클래스
|
||||
requires_super_admin BOOLEAN DEFAULT FALSE, -- 슈퍼관리자 전용
|
||||
created_at TIMESTAMP NULL,
|
||||
updated_at TIMESTAMP NULL,
|
||||
|
||||
FOREIGN KEY (menu_id) REFERENCES menus(id) ON DELETE CASCADE,
|
||||
INDEX idx_menu_id (menu_id),
|
||||
INDEX idx_section (section),
|
||||
INDEX idx_menu_type (menu_type)
|
||||
);
|
||||
```
|
||||
|
||||
**permissions 테이블** (Spatie)
|
||||
```
|
||||
id, tenant_id, name, guard_name, created_at, updated_at
|
||||
@@ -93,24 +118,99 @@ menu:{menu_id}.delete # 삭제 권한
|
||||
|
||||
### Phase 1: DB 스키마 및 시딩 (1-2일)
|
||||
|
||||
#### 3.1.1 mng 메뉴용 컬럼 추가 (선택적)
|
||||
#### 3.1.1 확장 테이블 마이그레이션
|
||||
|
||||
```php
|
||||
// 마이그레이션: add_mng_flag_to_menus_table
|
||||
Schema::table('menus', function (Blueprint $table) {
|
||||
$table->string('menu_type', 20)->default('normal')
|
||||
->comment('메뉴 유형: normal, dev_tool, lab')
|
||||
->after('external_url');
|
||||
$table->string('route_name', 100)->nullable()
|
||||
->comment('Laravel 라우트 이름')
|
||||
->after('menu_type');
|
||||
});
|
||||
// database/migrations/xxxx_create_menu_mng_extensions_table.php
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('menu_mng_extensions', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('menu_id')
|
||||
->constrained('menus')
|
||||
->onDelete('cascade');
|
||||
$table->enum('menu_type', ['normal', 'dev_tool', 'lab'])->default('normal');
|
||||
$table->string('route_name', 100)->nullable()
|
||||
->comment('Laravel 라우트 이름');
|
||||
$table->enum('section', ['main', 'dev_tools', 'labs'])->default('main');
|
||||
$table->char('lab_tab', 1)->nullable()
|
||||
->comment('R&D Labs용: s, a, m');
|
||||
$table->string('blade_component', 100)->nullable()
|
||||
->comment('커스텀 Blade 컴포넌트');
|
||||
$table->string('css_class', 100)->nullable()
|
||||
->comment('추가 CSS 클래스');
|
||||
$table->boolean('requires_super_admin')->default(false)
|
||||
->comment('슈퍼관리자 전용 여부');
|
||||
$table->timestamps();
|
||||
|
||||
$table->index('menu_id');
|
||||
$table->index('section');
|
||||
$table->index('menu_type');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('menu_mng_extensions');
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
#### 3.1.2 mng 메뉴 시더 생성
|
||||
#### 3.1.2 확장 테이블 모델
|
||||
|
||||
```php
|
||||
// app/Models/MenuMngExtension.php
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class MenuMngExtension extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'menu_id',
|
||||
'menu_type',
|
||||
'route_name',
|
||||
'section',
|
||||
'lab_tab',
|
||||
'blade_component',
|
||||
'css_class',
|
||||
'requires_super_admin',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'requires_super_admin' => 'boolean',
|
||||
];
|
||||
|
||||
public function menu(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Menu::class);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.1.3 mng 메뉴 시더 생성
|
||||
|
||||
```php
|
||||
// database/seeders/MngMenuSeeder.php
|
||||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use App\Models\Menu;
|
||||
use App\Models\MenuMngExtension;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
class MngMenuSeeder extends Seeder
|
||||
{
|
||||
public function run(): void
|
||||
@@ -119,31 +219,125 @@ class MngMenuSeeder extends Seeder
|
||||
|
||||
$menus = [
|
||||
// 일반 메뉴
|
||||
['name' => '대시보드', 'url' => '/dashboard', 'icon' => 'home', 'route_name' => 'dashboard', 'menu_type' => 'normal'],
|
||||
[
|
||||
'name' => '대시보드',
|
||||
'url' => '/dashboard',
|
||||
'icon' => 'home',
|
||||
'extension' => [
|
||||
'route_name' => 'dashboard',
|
||||
'menu_type' => 'normal',
|
||||
'section' => 'main',
|
||||
],
|
||||
],
|
||||
|
||||
// 그룹: 프로젝트 관리
|
||||
['name' => '프로젝트 관리', 'url' => null, 'icon' => 'folder', 'menu_type' => 'normal', 'children' => [
|
||||
['name' => '프로젝트 대시보드', 'url' => '/project-management', 'route_name' => 'pm.index'],
|
||||
['name' => '프로젝트', 'url' => '/project-management/projects', 'route_name' => 'pm.projects.index'],
|
||||
['name' => '일일 스크럼', 'url' => '/daily-logs', 'route_name' => 'daily-logs.index'],
|
||||
]],
|
||||
[
|
||||
'name' => '프로젝트 관리',
|
||||
'url' => null,
|
||||
'icon' => 'folder',
|
||||
'extension' => ['menu_type' => 'normal', 'section' => 'main'],
|
||||
'children' => [
|
||||
[
|
||||
'name' => '프로젝트 대시보드',
|
||||
'url' => '/project-management',
|
||||
'extension' => ['route_name' => 'pm.index'],
|
||||
],
|
||||
[
|
||||
'name' => '프로젝트',
|
||||
'url' => '/project-management/projects',
|
||||
'extension' => ['route_name' => 'pm.projects.index'],
|
||||
],
|
||||
[
|
||||
'name' => '일일 스크럼',
|
||||
'url' => '/daily-logs',
|
||||
'extension' => ['route_name' => 'daily-logs.index'],
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
// ... 기타 메뉴 그룹
|
||||
|
||||
// 개발도구 (슈퍼관리자 전용)
|
||||
['name' => '개발 도구', 'url' => null, 'icon' => 'cog', 'menu_type' => 'dev_tool', 'children' => [
|
||||
['name' => 'API 플로우 테스터', 'url' => '/dev-tools/flow-tester', 'route_name' => 'dev-tools.flow-tester.index'],
|
||||
['name' => 'API 요청 로그', 'url' => '/dev-tools/api-logs', 'route_name' => 'dev-tools.api-logs.index'],
|
||||
]],
|
||||
[
|
||||
'name' => '개발 도구',
|
||||
'url' => null,
|
||||
'icon' => 'cog',
|
||||
'extension' => [
|
||||
'menu_type' => 'dev_tool',
|
||||
'section' => 'dev_tools',
|
||||
'requires_super_admin' => true,
|
||||
],
|
||||
'children' => [
|
||||
[
|
||||
'name' => 'API 플로우 테스터',
|
||||
'url' => '/dev-tools/flow-tester',
|
||||
'extension' => [
|
||||
'route_name' => 'dev-tools.flow-tester.index',
|
||||
'requires_super_admin' => true,
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'API 요청 로그',
|
||||
'url' => '/dev-tools/api-logs',
|
||||
'extension' => [
|
||||
'route_name' => 'dev-tools.api-logs.index',
|
||||
'requires_super_admin' => true,
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
// R&D Labs (슈퍼관리자 전용)
|
||||
['name' => 'R&D Labs', 'url' => null, 'icon' => 'beaker', 'menu_type' => 'lab', 'children' => [
|
||||
// S, A, M 탭 구조
|
||||
]],
|
||||
[
|
||||
'name' => 'R&D Labs',
|
||||
'url' => null,
|
||||
'icon' => 'beaker',
|
||||
'extension' => [
|
||||
'menu_type' => 'lab',
|
||||
'section' => 'labs',
|
||||
'requires_super_admin' => true,
|
||||
],
|
||||
'children' => [
|
||||
// S, A, M 탭 구조
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$this->seedMenus($tenantId, $menus);
|
||||
}
|
||||
|
||||
private function seedMenus(int $tenantId, array $menus, ?int $parentId = null): void
|
||||
{
|
||||
foreach ($menus as $index => $menuData) {
|
||||
$extension = $menuData['extension'] ?? [];
|
||||
$children = $menuData['children'] ?? [];
|
||||
unset($menuData['extension'], $menuData['children']);
|
||||
|
||||
// 메뉴 생성
|
||||
$menu = Menu::create([
|
||||
'tenant_id' => $tenantId,
|
||||
'parent_id' => $parentId,
|
||||
'name' => $menuData['name'],
|
||||
'url' => $menuData['url'],
|
||||
'icon' => $menuData['icon'] ?? null,
|
||||
'sort_order' => $index,
|
||||
'is_active' => true,
|
||||
]);
|
||||
|
||||
// 확장 데이터 생성
|
||||
if (!empty($extension)) {
|
||||
MenuMngExtension::create([
|
||||
'menu_id' => $menu->id,
|
||||
...$extension,
|
||||
]);
|
||||
}
|
||||
|
||||
// 자식 메뉴 재귀 처리
|
||||
if (!empty($children)) {
|
||||
$this->seedMenus($tenantId, $children, $menu->id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -153,6 +347,15 @@ class MngMenuSeeder extends Seeder
|
||||
|
||||
```php
|
||||
// app/Services/SidebarMenuService.php
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\Menu;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class SidebarMenuService
|
||||
{
|
||||
/**
|
||||
@@ -163,11 +366,22 @@ class SidebarMenuService
|
||||
$user = $user ?? auth()->user();
|
||||
$tenantId = session('selected_tenant_id', 1);
|
||||
|
||||
// 1. 테넌트의 모든 활성 메뉴 조회
|
||||
$allMenus = Menu::where('tenant_id', $tenantId)
|
||||
->where('is_active', true)
|
||||
->where('hidden', false)
|
||||
->orderBy('sort_order')
|
||||
// 1. 테넌트의 모든 활성 메뉴 + 확장 데이터 조회 (LEFT JOIN)
|
||||
$allMenus = Menu::where('menus.tenant_id', $tenantId)
|
||||
->where('menus.is_active', true)
|
||||
->where('menus.hidden', false)
|
||||
->leftJoin('menu_mng_extensions', 'menus.id', '=', 'menu_mng_extensions.menu_id')
|
||||
->select(
|
||||
'menus.*',
|
||||
'menu_mng_extensions.menu_type',
|
||||
'menu_mng_extensions.route_name',
|
||||
'menu_mng_extensions.section',
|
||||
'menu_mng_extensions.lab_tab',
|
||||
'menu_mng_extensions.blade_component',
|
||||
'menu_mng_extensions.css_class',
|
||||
'menu_mng_extensions.requires_super_admin'
|
||||
)
|
||||
->orderBy('menus.sort_order')
|
||||
->get();
|
||||
|
||||
// 2. 슈퍼관리자는 모든 메뉴 표시
|
||||
@@ -178,8 +392,12 @@ class SidebarMenuService
|
||||
// 3. 일반 사용자: 권한 기반 필터링
|
||||
$permittedMenuIds = $this->getPermittedMenuIds($user, $tenantId);
|
||||
|
||||
// 4. 개발도구/Labs 제외 (일반 사용자)
|
||||
// 4. 개발도구/Labs 및 슈퍼관리자 전용 메뉴 제외
|
||||
$filteredMenus = $allMenus->filter(function ($menu) use ($permittedMenuIds) {
|
||||
// 슈퍼관리자 전용 메뉴 제외
|
||||
if ($menu->requires_super_admin) {
|
||||
return false;
|
||||
}
|
||||
// 개발도구/Labs는 일반 사용자에게 표시 안함
|
||||
if (in_array($menu->menu_type, ['dev_tool', 'lab'])) {
|
||||
return false;
|
||||
@@ -190,6 +408,20 @@ class SidebarMenuService
|
||||
return $this->buildMenuTree($filteredMenus);
|
||||
}
|
||||
|
||||
/**
|
||||
* 섹션별 메뉴 조회 (main, dev_tools, labs)
|
||||
*/
|
||||
public function getMenusBySection(?User $user = null): array
|
||||
{
|
||||
$menuTree = $this->getUserMenuTree($user);
|
||||
|
||||
return [
|
||||
'main' => $menuTree->filter(fn($m) => ($m->section ?? 'main') === 'main'),
|
||||
'dev_tools' => $menuTree->filter(fn($m) => ($m->section ?? 'main') === 'dev_tools'),
|
||||
'labs' => $menuTree->filter(fn($m) => ($m->section ?? 'main') === 'labs'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 사용자가 접근 가능한 메뉴 ID 목록 조회
|
||||
* 우선순위: 개인 DENY > 개인 ALLOW > 부서 DENY > 부서 ALLOW > 역할
|
||||
@@ -209,6 +441,78 @@ class SidebarMenuService
|
||||
return $this->mergePermissions($rolePermissions, $deptPermissions, $userOverrides);
|
||||
}
|
||||
|
||||
private function getRoleMenuPermissions(User $user, int $tenantId): array
|
||||
{
|
||||
// menu:*.view 형식의 권한에서 메뉴 ID 추출
|
||||
$permissions = $user->getPermissionsViaRoles()
|
||||
->filter(fn($p) => str_starts_with($p->name, 'menu:') && str_ends_with($p->name, '.view'))
|
||||
->pluck('name')
|
||||
->map(fn($name) => (int) explode('.', explode(':', $name)[1])[0])
|
||||
->toArray();
|
||||
|
||||
return $permissions;
|
||||
}
|
||||
|
||||
private function getDepartmentMenuPermissions(User $user, int $tenantId): array
|
||||
{
|
||||
// 부서 권한 조회 로직 (department_permissions 테이블)
|
||||
return DB::table('department_permissions')
|
||||
->where('department_id', $user->department_id)
|
||||
->where('permission_id', 'LIKE', 'menu:%')
|
||||
->get()
|
||||
->mapWithKeys(fn($row) => [
|
||||
(int) explode('.', explode(':', $row->permission_id)[1])[0] => $row->is_allowed
|
||||
])
|
||||
->toArray();
|
||||
}
|
||||
|
||||
private function getUserMenuOverrides(User $user, int $tenantId): array
|
||||
{
|
||||
// 개인 권한 오버라이드 조회 (user_permission_overrides 테이블)
|
||||
return DB::table('user_permission_overrides')
|
||||
->where('user_id', $user->id)
|
||||
->where('permission_id', 'LIKE', 'menu:%')
|
||||
->get()
|
||||
->mapWithKeys(fn($row) => [
|
||||
(int) explode('.', explode(':', $row->permission_id)[1])[0] => $row->is_allowed
|
||||
])
|
||||
->toArray();
|
||||
}
|
||||
|
||||
private function mergePermissions(array $role, array $dept, array $user): array
|
||||
{
|
||||
$allMenuIds = array_unique(array_merge(
|
||||
$role,
|
||||
array_keys($dept),
|
||||
array_keys($user)
|
||||
));
|
||||
|
||||
$permitted = [];
|
||||
|
||||
foreach ($allMenuIds as $menuId) {
|
||||
// 우선순위: 개인 DENY > 개인 ALLOW > 부서 DENY > 부서 ALLOW > 역할
|
||||
if (isset($user[$menuId])) {
|
||||
if ($user[$menuId]) {
|
||||
$permitted[] = $menuId;
|
||||
}
|
||||
continue; // 개인 설정이 있으면 다른 건 무시
|
||||
}
|
||||
|
||||
if (isset($dept[$menuId])) {
|
||||
if ($dept[$menuId]) {
|
||||
$permitted[] = $menuId;
|
||||
}
|
||||
continue; // 부서 설정이 있으면 역할은 무시
|
||||
}
|
||||
|
||||
if (in_array($menuId, $role)) {
|
||||
$permitted[] = $menuId;
|
||||
}
|
||||
}
|
||||
|
||||
return $permitted;
|
||||
}
|
||||
|
||||
private function buildMenuTree(Collection $menus, ?int $parentId = null): Collection
|
||||
{
|
||||
return $menus->where('parent_id', $parentId)
|
||||
@@ -257,13 +561,27 @@ resources/views/
|
||||
|
||||
```php
|
||||
// app/Providers/ViewServiceProvider.php
|
||||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Services\SidebarMenuService;
|
||||
use Illuminate\Support\Facades\View;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
class ViewServiceProvider extends ServiceProvider
|
||||
{
|
||||
public function boot(): void
|
||||
{
|
||||
View::composer('partials.sidebar-dynamic', function ($view) {
|
||||
$menuService = app(SidebarMenuService::class);
|
||||
$view->with('sidebarMenus', $menuService->getUserMenuTree());
|
||||
$menusBySection = $menuService->getMenusBySection();
|
||||
|
||||
$view->with([
|
||||
'mainMenus' => $menusBySection['main'],
|
||||
'devToolsMenus' => $menusBySection['dev_tools'],
|
||||
'labsMenus' => $menusBySection['labs'],
|
||||
]);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -275,9 +593,19 @@ class ViewServiceProvider extends ServiceProvider
|
||||
|
||||
```php
|
||||
// app/Http/Middleware/CheckMenuPermission.php
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use App\Models\Menu;
|
||||
use App\Models\MenuMngExtension;
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class CheckMenuPermission
|
||||
{
|
||||
public function handle(Request $request, Closure $next, ?string $permission = null)
|
||||
public function handle(Request $request, Closure $next, ?string $permission = null): Response
|
||||
{
|
||||
$user = $request->user();
|
||||
|
||||
@@ -286,26 +614,68 @@ class CheckMenuPermission
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
// 권한 체크
|
||||
// 라우트 이름으로 메뉴 찾기 (확장 테이블에서)
|
||||
$routeName = $request->route()->getName();
|
||||
$menu = Menu::where('route_name', $routeName)->first();
|
||||
$extension = MenuMngExtension::where('route_name', $routeName)->first();
|
||||
|
||||
if (!$menu) {
|
||||
return $next($request); // 메뉴 없으면 패스
|
||||
if (!$extension) {
|
||||
return $next($request); // 메뉴 등록 안 된 라우트는 패스
|
||||
}
|
||||
|
||||
$permissionName = $permission ?? "menu:{$menu->id}.view";
|
||||
// 슈퍼관리자 전용 메뉴 체크
|
||||
if ($extension->requires_super_admin) {
|
||||
abort(403, '슈퍼관리자만 접근 가능합니다.');
|
||||
}
|
||||
|
||||
if (!$this->hasMenuPermission($user, $menu, $permissionName)) {
|
||||
$permissionName = $permission ?? "menu:{$extension->menu_id}.view";
|
||||
|
||||
if (!$this->hasMenuPermission($user, $extension->menu_id, $permissionName)) {
|
||||
abort(403, '접근 권한이 없습니다.');
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
private function hasMenuPermission($user, int $menuId, string $permissionName): bool
|
||||
{
|
||||
// SidebarMenuService와 동일한 권한 체크 로직 사용
|
||||
// 또는 Spatie Permission 직접 체크
|
||||
return $user->can($permissionName);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.4.2 라우트에 미들웨어 적용
|
||||
#### 3.4.2 미들웨어 등록 (Laravel 12 방식)
|
||||
|
||||
```php
|
||||
// bootstrap/app.php
|
||||
<?php
|
||||
|
||||
use Illuminate\Foundation\Application;
|
||||
use Illuminate\Foundation\Configuration\Middleware;
|
||||
|
||||
return Application::configure(basePath: dirname(__DIR__))
|
||||
->withRouting(
|
||||
web: __DIR__.'/../routes/web.php',
|
||||
commands: __DIR__.'/../routes/console.php',
|
||||
)
|
||||
->withMiddleware(function (Middleware $middleware): void {
|
||||
// 기존 미들웨어
|
||||
$middleware->alias([
|
||||
'hq.member' => \App\Http\Middleware\EnsureHQMember::class,
|
||||
'super.admin' => \App\Http\Middleware\EnsureSuperAdmin::class,
|
||||
'password.changed' => \App\Http\Middleware\EnsurePasswordChanged::class,
|
||||
// 신규 메뉴 권한 미들웨어 추가
|
||||
'menu.permission' => \App\Http\Middleware\CheckMenuPermission::class,
|
||||
]);
|
||||
})
|
||||
->withExceptions(function ($exceptions) {
|
||||
//
|
||||
})
|
||||
->create();
|
||||
```
|
||||
|
||||
#### 3.4.3 라우트에 미들웨어 적용
|
||||
|
||||
```php
|
||||
// routes/web.php
|
||||
@@ -322,7 +692,8 @@ Route::middleware(['auth', 'hq.member', 'menu.permission'])->group(function () {
|
||||
|
||||
```
|
||||
Phase 1: 준비 (하드코딩 + DB 병행)
|
||||
├── DB에 mng 메뉴 시딩
|
||||
├── DB에 menu_mng_extensions 테이블 생성
|
||||
├── mng 메뉴 시딩
|
||||
├── SidebarMenuService 개발
|
||||
└── 기존 sidebar.blade.php 유지
|
||||
|
||||
@@ -361,7 +732,8 @@ Phase 4: 안정화
|
||||
|
||||
| 파일 | 설명 |
|
||||
|------|------|
|
||||
| `database/migrations/xxxx_add_menu_type_to_menus.php` | menu_type, route_name 컬럼 추가 |
|
||||
| `database/migrations/xxxx_create_menu_mng_extensions_table.php` | mng 확장 테이블 생성 |
|
||||
| `app/Models/MenuMngExtension.php` | 확장 테이블 모델 |
|
||||
| `database/seeders/MngMenuSeeder.php` | mng 메뉴 시더 |
|
||||
| `database/seeders/MngMenuPermissionSeeder.php` | mng 메뉴 권한 시더 |
|
||||
| `app/Services/SidebarMenuService.php` | 사용자별 메뉴 조회 |
|
||||
@@ -374,7 +746,7 @@ Phase 4: 안정화
|
||||
|
||||
| 파일 | 변경 내용 |
|
||||
|------|----------|
|
||||
| `app/Http/Kernel.php` | CheckMenuPermission 미들웨어 등록 |
|
||||
| `bootstrap/app.php` | CheckMenuPermission 미들웨어 등록 |
|
||||
| `config/app.php` | mng_dynamic_sidebar 설정 추가 |
|
||||
| `routes/web.php` | 미들웨어 적용 |
|
||||
| `resources/views/partials/sidebar.blade.php` | 조건부 렌더링 |
|
||||
@@ -404,7 +776,7 @@ Phase 4: 안정화
|
||||
|
||||
### Phase 1 완료 조건
|
||||
|
||||
- [ ] 마이그레이션 실행 성공
|
||||
- [ ] 마이그레이션 실행 성공 (menu_mng_extensions 테이블 생성)
|
||||
- [ ] mng 메뉴 시더 실행 성공
|
||||
- [ ] DB에 모든 mng 메뉴 존재 확인
|
||||
|
||||
@@ -476,7 +848,7 @@ AuditLog::create([
|
||||
이 계획을 승인하시면 다음 순서로 진행합니다:
|
||||
|
||||
1. **현재 mng 메뉴 전체 목록 정리** (그룹/항목/라우트/아이콘)
|
||||
2. **마이그레이션 및 시더 작성**
|
||||
2. **마이그레이션 및 시더 작성** (menu_mng_extensions 테이블)
|
||||
3. **SidebarMenuService 개발**
|
||||
4. **동적 사이드바 컴포넌트 개발**
|
||||
5. **권한 미들웨어 적용**
|
||||
|
||||
@@ -157,10 +157,10 @@
|
||||
|
||||
## 변경 이력
|
||||
|
||||
| 날짜 | 내용 | 작업자 |
|
||||
|------|------|--------|
|
||||
| 2025-12-16 | Phase 1 완료 - 13개 파일 레이아웃 변환 | Claude |
|
||||
| 2025-12-16 | 작업 추적 문서 생성 | Claude |
|
||||
| 날짜 | 내용 | 커밋 |
|
||||
|------|------|------|
|
||||
| 2025-12-16 | Phase 1 완료 - 13개 파일 레이아웃 변환 | mng: `27052af` |
|
||||
| 2025-12-16 | 작업 추적 문서 생성 | docs: `5e6508c` |
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user