fix : 권한관리 기능 추가 (각 기능 확인 필요)
- 메뉴관리 - 역할관리 - 부서관리 - 메뉴, 부서, 역할, 유저 - 권한 연동
This commit is contained in:
@@ -0,0 +1,221 @@
|
||||
<?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
|
||||
{
|
||||
/**
|
||||
* 1) MENUS: 권한관리용 필드 추가
|
||||
* - slug: 메뉴/퍼미션 키 매핑용 고유 식별자 (테넌트 범위 유니크)
|
||||
* - SoftDeletes + acted-by 컬럼
|
||||
*/
|
||||
Schema::table('menus', function (Blueprint $table) {
|
||||
// slug 추가 (권한 키로 활용) - 테넌트 내 유니크
|
||||
if (!Schema::hasColumn('menus', 'slug')) {
|
||||
$table->string('slug', 150)->nullable()->after('name')->comment('메뉴 슬러그(권한 키)');
|
||||
}
|
||||
|
||||
// Soft delete
|
||||
if (!Schema::hasColumn('menus', 'deleted_at')) {
|
||||
$table->softDeletes()->comment('소프트삭제 시각');
|
||||
}
|
||||
|
||||
// acted-by (누가 생성/수정/삭제 했는지)
|
||||
if (!Schema::hasColumn('menus', 'created_by')) {
|
||||
$table->unsignedBigInteger('created_by')->nullable()->after('updated_at')->comment('생성자 사용자 ID');
|
||||
}
|
||||
if (!Schema::hasColumn('menus', 'updated_by')) {
|
||||
$table->unsignedBigInteger('updated_by')->nullable()->after('created_by')->comment('수정자 사용자 ID');
|
||||
}
|
||||
if (!Schema::hasColumn('menus', 'deleted_by')) {
|
||||
$table->unsignedBigInteger('deleted_by')->nullable()->after('updated_by')->comment('삭제자 사용자 ID');
|
||||
}
|
||||
|
||||
// 인덱스/유니크
|
||||
// slug는 테넌트 범위에서 유니크 보장
|
||||
$table->unique(['tenant_id', 'slug'], 'menus_tenant_slug_unique');
|
||||
$table->index(['tenant_id', 'is_active', 'hidden'], 'menus_tenant_active_hidden_idx');
|
||||
$table->index(['sort_order'], 'menus_sort_idx');
|
||||
});
|
||||
|
||||
/**
|
||||
* 2) 부서 테이블
|
||||
*/
|
||||
if (!Schema::hasTable('departments')) {
|
||||
Schema::create('departments', function (Blueprint $table) {
|
||||
$table->bigIncrements('id')->comment('PK: 부서 ID');
|
||||
$table->unsignedBigInteger('tenant_id')->comment('테넌트 ID');
|
||||
$table->string('code', 50)->nullable()->comment('부서 코드');
|
||||
$table->string('name', 100)->comment('부서명');
|
||||
$table->string('description', 255)->nullable()->comment('설명');
|
||||
$table->tinyInteger('is_active')->default(1)->comment('활성여부(1=활성,0=비활성)');
|
||||
$table->integer('sort_order')->default(0)->comment('정렬순서');
|
||||
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
|
||||
// acted-by
|
||||
$table->unsignedBigInteger('created_by')->nullable()->comment('생성자 사용자 ID');
|
||||
$table->unsignedBigInteger('updated_by')->nullable()->comment('수정자 사용자 ID');
|
||||
$table->unsignedBigInteger('deleted_by')->nullable()->comment('삭제자 사용자 ID');
|
||||
|
||||
// 인덱스
|
||||
$table->unique(['tenant_id', 'code'], 'dept_tenant_code_unique');
|
||||
$table->index(['tenant_id', 'name'], 'dept_tenant_name_idx');
|
||||
$table->index(['tenant_id', 'is_active'], 'dept_tenant_active_idx');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 3) 부서-사용자 매핑
|
||||
* - 동일 부서-사용자 중복 매핑 방지(tenant_id 포함)
|
||||
*/
|
||||
if (!Schema::hasTable('department_user')) {
|
||||
Schema::create('department_user', function (Blueprint $table) {
|
||||
$table->bigIncrements('id')->comment('PK');
|
||||
$table->unsignedBigInteger('tenant_id')->comment('테넌트 ID');
|
||||
$table->unsignedBigInteger('department_id')->comment('부서 ID');
|
||||
$table->unsignedBigInteger('user_id')->comment('사용자 ID');
|
||||
|
||||
$table->tinyInteger('is_primary')->default(0)->comment('주 부서 여부(1=Y,0=N)');
|
||||
$table->timestamp('joined_at')->nullable()->comment('부서 배정일');
|
||||
$table->timestamp('left_at')->nullable()->comment('부서 이탈일');
|
||||
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
|
||||
// 인덱스/유니크
|
||||
$table->unique(['tenant_id', 'department_id', 'user_id'], 'dept_user_unique');
|
||||
$table->index(['tenant_id', 'user_id'], 'dept_user_user_idx');
|
||||
$table->index(['tenant_id', 'department_id'], 'dept_user_dept_idx');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 4) 부서별 권한 매핑
|
||||
* - Spatie permissions 테이블의 permission_id와 연결(실 FK는 미사용)
|
||||
* - ALLOW/DENY는 is_allowed로 표현
|
||||
* - 필요시 메뉴 단위 범위 제한을 위해 menu_id(옵션)
|
||||
*/
|
||||
if (!Schema::hasTable('department_permissions')) {
|
||||
Schema::create('department_permissions', function (Blueprint $table) {
|
||||
$table->bigIncrements('id')->comment('PK');
|
||||
$table->unsignedBigInteger('tenant_id')->comment('테넌트 ID');
|
||||
$table->unsignedBigInteger('department_id')->comment('부서 ID');
|
||||
$table->unsignedBigInteger('permission_id')->comment('Spatie permissions.id');
|
||||
$table->unsignedBigInteger('menu_id')->nullable()->comment('메뉴 ID (선택), 특정 메뉴 범위 권한');
|
||||
|
||||
$table->tinyInteger('is_allowed')->default(1)->comment('허용여부(1=ALLOW,0=DENY)');
|
||||
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
|
||||
// 인덱스/유니크 (동일 권한 중복 방지)
|
||||
$table->unique(
|
||||
['tenant_id', 'department_id', 'permission_id', 'menu_id'],
|
||||
'dept_perm_unique'
|
||||
);
|
||||
$table->index(['tenant_id', 'department_id'], 'dept_perm_dept_idx');
|
||||
$table->index(['tenant_id', 'permission_id'], 'dept_perm_perm_idx');
|
||||
$table->index(['tenant_id', 'menu_id'], 'dept_perm_menu_idx');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 5) 사용자 퍼미션 오버라이드 (개인 단위 허용/차단)
|
||||
* - 개인 DENY가 최우선 → 해석 레이어에서 우선순위 처리
|
||||
*/
|
||||
if (!Schema::hasTable('user_permission_overrides')) {
|
||||
Schema::create('user_permission_overrides', function (Blueprint $table) {
|
||||
$table->bigIncrements('id')->comment('PK');
|
||||
$table->unsignedBigInteger('tenant_id')->comment('테넌트 ID');
|
||||
$table->unsignedBigInteger('user_id')->comment('사용자 ID');
|
||||
$table->unsignedBigInteger('permission_id')->comment('Spatie permissions.id');
|
||||
$table->unsignedBigInteger('menu_id')->nullable()->comment('메뉴 ID (선택)');
|
||||
|
||||
$table->tinyInteger('is_allowed')->default(1)->comment('허용여부(1=ALLOW,0=DENY)');
|
||||
$table->string('reason', 255)->nullable()->comment('사유/메모');
|
||||
$table->timestamp('effective_from')->nullable()->comment('효력 시작');
|
||||
$table->timestamp('effective_to')->nullable()->comment('효력 종료');
|
||||
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
|
||||
// 중복 방지
|
||||
$table->unique(
|
||||
['tenant_id', 'user_id', 'permission_id', 'menu_id'],
|
||||
'user_perm_override_unique'
|
||||
);
|
||||
$table->index(['tenant_id', 'user_id'], 'user_perm_user_idx');
|
||||
$table->index(['tenant_id', 'permission_id'], 'user_perm_perm_idx');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
// 생성 테이블 역순 드롭
|
||||
Schema::dropIfExists('user_permission_overrides');
|
||||
Schema::dropIfExists('department_permissions');
|
||||
Schema::dropIfExists('department_user');
|
||||
Schema::dropIfExists('departments');
|
||||
|
||||
// MENUS 원복: 추가했던 컬럼/인덱스 제거
|
||||
Schema::table('menus', function (Blueprint $table) {
|
||||
// 인덱스/유니크 먼저 제거
|
||||
if ($this->indexExists('menus', 'menus_tenant_slug_unique')) {
|
||||
$table->dropUnique('menus_tenant_slug_unique');
|
||||
}
|
||||
if ($this->indexExists('menus', 'menus_tenant_active_hidden_idx')) {
|
||||
$table->dropIndex('menus_tenant_active_hidden_idx');
|
||||
}
|
||||
if ($this->indexExists('menus', 'menus_sort_idx')) {
|
||||
$table->dropIndex('menus_sort_idx');
|
||||
}
|
||||
|
||||
// 컬럼 제거 (존재 체크는 직접 불가하므로 try-catch는 생략)
|
||||
if (Schema::hasColumn('menus', 'slug')) {
|
||||
$table->dropColumn('slug');
|
||||
}
|
||||
if (Schema::hasColumn('menus', 'deleted_at')) {
|
||||
$table->dropSoftDeletes();
|
||||
}
|
||||
if (Schema::hasColumn('menus', 'deleted_by')) {
|
||||
$table->dropColumn('deleted_by');
|
||||
}
|
||||
if (Schema::hasColumn('menus', 'updated_by')) {
|
||||
$table->dropColumn('updated_by');
|
||||
}
|
||||
if (Schema::hasColumn('menus', 'created_by')) {
|
||||
$table->dropColumn('created_by');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Laravel의 Blueprint에서는 인덱스 존재 체크가 불가하므로
|
||||
* 스키마 매니저를 직접 조회하는 헬퍼.
|
||||
*/
|
||||
private function indexExists(string $table, string $indexName): bool
|
||||
{
|
||||
try {
|
||||
$connection = Schema::getConnection();
|
||||
$schemaManager = $connection->getDoctrineSchemaManager();
|
||||
$doctrineTable = $schemaManager->introspectTable(
|
||||
$connection->getTablePrefix() . $table
|
||||
);
|
||||
foreach ($doctrineTable->getIndexes() as $idx) {
|
||||
if ($idx->getName() === $indexName) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
// 무시
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,261 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
// ===== permissions =====
|
||||
if (Schema::hasTable('permissions')) {
|
||||
Schema::table('permissions', function (Blueprint $table) {
|
||||
if (!Schema::hasColumn('permissions', 'guard_name')) {
|
||||
$table->string('guard_name', 50)->default('api')->after('name');
|
||||
}
|
||||
if (!Schema::hasColumn('permissions', 'tenant_id')) {
|
||||
$table->unsignedBigInteger('tenant_id')->nullable()->after('guard_name');
|
||||
$table->index(['tenant_id']);
|
||||
}
|
||||
});
|
||||
|
||||
// unique(name, guard_name, tenant_id)
|
||||
// 기존 unique가 name 또는 (name, guard_name) 로 있을 수 있으니 안전하게 제거 후 재생성
|
||||
$this->dropUniqueIfExists('permissions', 'permissions_name_unique');
|
||||
$this->dropUniqueIfExists('permissions', 'permissions_name_guard_name_unique');
|
||||
$this->dropUniqueIfExists('permissions', 'uk_permissions_name_guard_tenant');
|
||||
|
||||
Schema::table('permissions', function (Blueprint $table) {
|
||||
$table->unique(['name', 'guard_name', 'tenant_id'], 'uk_permissions_name_guard_tenant');
|
||||
});
|
||||
}
|
||||
|
||||
// ===== roles =====
|
||||
if (Schema::hasTable('roles')) {
|
||||
Schema::table('roles', function (Blueprint $table) {
|
||||
if (!Schema::hasColumn('roles', 'guard_name')) {
|
||||
$table->string('guard_name', 50)->default('api')->after('name');
|
||||
}
|
||||
if (!Schema::hasColumn('roles', 'tenant_id')) {
|
||||
// 이미 있으시다 했지만 혹시 없을 경우 대비
|
||||
$table->unsignedBigInteger('tenant_id')->nullable()->after('guard_name');
|
||||
}
|
||||
});
|
||||
|
||||
// unique(tenant_id, name, guard_name) 로 교체
|
||||
$this->dropUniqueIfExists('roles', 'uk_roles_tenant_name');
|
||||
$this->dropUniqueIfExists('roles', 'roles_name_unique');
|
||||
$this->dropUniqueIfExists('roles', 'roles_name_guard_name_unique');
|
||||
$this->dropUniqueIfExists('roles', 'uk_roles_tenant_name_guard');
|
||||
|
||||
Schema::table('roles', function (Blueprint $table) {
|
||||
$table->unique(['tenant_id', 'name', 'guard_name'], 'uk_roles_tenant_name_guard');
|
||||
});
|
||||
|
||||
// 인덱스 보강
|
||||
$this->createIndexIfNotExists('roles', 'roles_tenant_id_index', ['tenant_id']);
|
||||
}
|
||||
|
||||
// ===== model_has_roles =====
|
||||
if (Schema::hasTable('model_has_roles')) {
|
||||
Schema::table('model_has_roles', function (Blueprint $table) {
|
||||
if (!Schema::hasColumn('model_has_roles', 'tenant_id')) {
|
||||
$table->unsignedBigInteger('tenant_id')->nullable()->after('model_id');
|
||||
}
|
||||
});
|
||||
|
||||
// ✅ FK가 role_id를 참조하므로, PK 교체 전에 보조 인덱스 추가
|
||||
$this->createIndexIfNotExists('model_has_roles', 'mhr_role_id_idx', ['role_id']);
|
||||
|
||||
// PK: (role_id, model_id, model_type, tenant_id)
|
||||
$this->replacePrimaryKey(
|
||||
'model_has_roles',
|
||||
['role_id', 'model_id', 'model_type'],
|
||||
['role_id', 'model_id', 'model_type', 'tenant_id']
|
||||
);
|
||||
|
||||
// 인덱스 (model_id, model_type, tenant_id)
|
||||
$this->dropIndexIfExists('model_has_roles', 'model_has_roles_model_id_model_type_index');
|
||||
$this->createIndexIfNotExists(
|
||||
'model_has_roles',
|
||||
'model_has_roles_model_id_model_type_tenant_id_index',
|
||||
['model_id', 'model_type', 'tenant_id']
|
||||
);
|
||||
}
|
||||
|
||||
// ===== model_has_permissions =====
|
||||
if (Schema::hasTable('model_has_permissions')) {
|
||||
Schema::table('model_has_permissions', function (Blueprint $table) {
|
||||
if (!Schema::hasColumn('model_has_permissions', 'tenant_id')) {
|
||||
$table->unsignedBigInteger('tenant_id')->nullable()->after('model_id');
|
||||
}
|
||||
});
|
||||
|
||||
// ✅ FK가 permission_id를 참조하므로, PK 교체 전에 보조 인덱스 추가
|
||||
$this->createIndexIfNotExists('model_has_permissions', 'mhp_permission_id_idx', ['permission_id']);
|
||||
|
||||
// PK: (permission_id, model_id, model_type, tenant_id)
|
||||
$this->replacePrimaryKey(
|
||||
'model_has_permissions',
|
||||
['permission_id', 'model_id', 'model_type'],
|
||||
['permission_id', 'model_id', 'model_type', 'tenant_id']
|
||||
);
|
||||
|
||||
// 인덱스 (model_id, model_type, tenant_id)
|
||||
$this->dropIndexIfExists('model_has_permissions', 'model_has_permissions_model_id_model_type_index');
|
||||
$this->createIndexIfNotExists(
|
||||
'model_has_permissions',
|
||||
'model_has_permissions_model_id_model_type_tenant_id_index',
|
||||
['model_id', 'model_type', 'tenant_id']
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
// 되돌리기 (가능한 범위에서)
|
||||
if (Schema::hasTable('model_has_permissions')) {
|
||||
// PK 원복: (permission_id, model_id, model_type)
|
||||
$this->replacePrimaryKey('model_has_permissions', ['permission_id', 'model_id', 'model_type', 'tenant_id'], ['permission_id', 'model_id', 'model_type']);
|
||||
$this->dropIndexIfExists('model_has_permissions', 'model_has_permissions_model_id_model_type_tenant_id_index');
|
||||
$this->createIndexIfNotExists('model_has_permissions', 'model_has_permissions_model_id_model_type_index', ['model_id', 'model_type']);
|
||||
|
||||
if (Schema::hasColumn('model_has_permissions', 'tenant_id')) {
|
||||
Schema::table('model_has_permissions', function (Blueprint $table) {
|
||||
$table->dropColumn('tenant_id');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (Schema::hasTable('model_has_roles')) {
|
||||
// PK 원복: (role_id, model_id, model_type)
|
||||
$this->replacePrimaryKey('model_has_roles', ['role_id', 'model_id', 'model_type', 'tenant_id'], ['role_id', 'model_id', 'model_type']);
|
||||
$this->dropIndexIfExists('model_has_roles', 'model_has_roles_model_id_model_type_tenant_id_index');
|
||||
$this->createIndexIfNotExists('model_has_roles', 'model_has_roles_model_id_model_type_index', ['model_id', 'model_type']);
|
||||
|
||||
if (Schema::hasColumn('model_has_roles', 'tenant_id')) {
|
||||
Schema::table('model_has_roles', function (Blueprint $table) {
|
||||
$table->dropColumn('tenant_id');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (Schema::hasTable('roles')) {
|
||||
// unique 원복: (tenant_id, name)
|
||||
$this->dropUniqueIfExists('roles', 'uk_roles_tenant_name_guard');
|
||||
Schema::table('roles', function (Blueprint $table) {
|
||||
$table->unique(['tenant_id', 'name'], 'uk_roles_tenant_name');
|
||||
});
|
||||
|
||||
if (Schema::hasColumn('roles', 'guard_name')) {
|
||||
Schema::table('roles', function (Blueprint $table) {
|
||||
$table->dropColumn('guard_name');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (Schema::hasTable('permissions')) {
|
||||
// unique 원복: (name) 또는 (name, guard_name) 상황에 따라
|
||||
$this->dropUniqueIfExists('permissions', 'uk_permissions_name_guard_tenant');
|
||||
Schema::table('permissions', function (Blueprint $table) {
|
||||
// 최소 name unique로 복원
|
||||
$table->unique(['name'], 'permissions_name_unique');
|
||||
});
|
||||
|
||||
if (Schema::hasColumn('permissions', 'tenant_id')) {
|
||||
Schema::table('permissions', function (Blueprint $table) {
|
||||
$table->dropColumn('tenant_id');
|
||||
});
|
||||
}
|
||||
if (Schema::hasColumn('permissions', 'guard_name')) {
|
||||
Schema::table('permissions', function (Blueprint $table) {
|
||||
$table->dropColumn('guard_name');
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- helpers ----------
|
||||
|
||||
private function dropUniqueIfExists(string $table, string $index): void
|
||||
{
|
||||
try {
|
||||
Schema::table($table, function (Blueprint $t) use ($index) {
|
||||
$t->dropUnique($index);
|
||||
});
|
||||
} catch (\Throwable $e) {
|
||||
// 무시 (없으면 통과)
|
||||
}
|
||||
}
|
||||
|
||||
private function dropIndexIfExists(string $table, string $index): void
|
||||
{
|
||||
try {
|
||||
Schema::table($table, function (Blueprint $t) use ($index) {
|
||||
$t->dropIndex($index);
|
||||
});
|
||||
} catch (\Throwable $e) {
|
||||
// 무시
|
||||
}
|
||||
}
|
||||
|
||||
private function createIndexIfNotExists(string $table, string $index, array $columns): void
|
||||
{
|
||||
// 라라벨은 인덱스 존재 체크 API가 없어 try/catch로 처리
|
||||
try {
|
||||
Schema::table($table, function (Blueprint $t) use ($index, $columns) {
|
||||
$t->index($columns, $index);
|
||||
});
|
||||
} catch (\Throwable $e) {
|
||||
// 이미 있으면 통과
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 기존 PK를 다른 조합으로 교체
|
||||
*/
|
||||
private function replacePrimaryKey(string $table, array $old, array $new): void
|
||||
{
|
||||
try {
|
||||
Schema::table($table, function (Blueprint $t) use ($old) {
|
||||
$t->dropPrimary($this->primaryNameGuess($table, $old));
|
||||
});
|
||||
} catch (\Throwable $e) {
|
||||
// 일부 DB는 이름 지정 안 하면 실패 → 수동 SQL
|
||||
$this->dropPrimaryKeyBySql($table);
|
||||
}
|
||||
|
||||
Schema::table($table, function (Blueprint $t) use ($new) {
|
||||
$t->primary($new);
|
||||
});
|
||||
}
|
||||
|
||||
private function primaryNameGuess(string $table, array $cols): string
|
||||
{
|
||||
// 보통 {table}_primary 이지만, 드라이버/버전에 따라 다를 수 있어 try/catch로 대체
|
||||
return "{$table}_primary";
|
||||
}
|
||||
|
||||
private function dropPrimaryKeyBySql(string $table): void
|
||||
{
|
||||
$driver = DB::getDriverName();
|
||||
if ($driver === 'mysql') {
|
||||
DB::statement("ALTER TABLE `{$table}` DROP PRIMARY KEY");
|
||||
} elseif ($driver === 'pgsql') {
|
||||
// PostgreSQL은 PK 제약명 조회 후 드롭이 필요. 간단히 시도:
|
||||
$constraint = DB::selectOne("
|
||||
SELECT constraint_name
|
||||
FROM information_schema.table_constraints
|
||||
WHERE table_name = ? AND constraint_type = 'PRIMARY KEY'
|
||||
LIMIT 1
|
||||
", [$table]);
|
||||
if ($constraint && isset($constraint->constraint_name)) {
|
||||
DB::statement("ALTER TABLE \"{$table}\" DROP CONSTRAINT \"{$constraint->constraint_name}\"");
|
||||
}
|
||||
} else {
|
||||
// 기타 드라이버는 수동 처리 생략
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
return new class extends Migration {
|
||||
public function up(): void
|
||||
{
|
||||
// 1) 유니크 인덱스 존재 시 제거 (information_schema로 확인)
|
||||
$hasUnique = DB::table('information_schema.statistics')
|
||||
->whereRaw('TABLE_SCHEMA = DATABASE()')
|
||||
->where('TABLE_NAME', 'menus')
|
||||
->where('INDEX_NAME', 'menus_tenant_slug_unique')
|
||||
->exists();
|
||||
|
||||
if ($hasUnique) {
|
||||
Schema::table('menus', function (Blueprint $table) {
|
||||
$table->dropUnique('menus_tenant_slug_unique');
|
||||
});
|
||||
}
|
||||
|
||||
// 2) slug 컬럼 존재 시 제거
|
||||
if (Schema::hasColumn('menus', 'slug')) {
|
||||
Schema::table('menus', function (Blueprint $table) {
|
||||
$table->dropColumn('slug');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
// 1) slug 컬럼 복구
|
||||
if (!Schema::hasColumn('menus', 'slug')) {
|
||||
Schema::table('menus', function (Blueprint $table) {
|
||||
$table->string('slug', 150)->nullable()->comment('메뉴 슬러그(권한 키)');
|
||||
});
|
||||
}
|
||||
|
||||
// 2) 유니크 인덱스 복구 (없을 때만)
|
||||
$hasUnique = DB::table('information_schema.statistics')
|
||||
->whereRaw('TABLE_SCHEMA = DATABASE()')
|
||||
->where('TABLE_NAME', 'menus')
|
||||
->where('INDEX_NAME', 'menus_tenant_slug_unique')
|
||||
->exists();
|
||||
|
||||
if (! $hasUnique) {
|
||||
Schema::table('menus', function (Blueprint $table) {
|
||||
$table->unique(['tenant_id', 'slug'], 'menus_tenant_slug_unique');
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user