사용자 관리 복구 및 영구삭제 기능 추가
- UserService: audit 컬럼 처리 추가 (created_by, updated_by, deleted_by) - UserService: restoreUser(), forceDeleteUser() 메서드 추가 - UserController: restore(), forceDestroy() 엔드포인트 추가 - 권한 체크: 영구삭제는 슈퍼관리자만 가능 - UI: 삭제된 사용자에 복원/영구삭제 버튼 추가 - Routes: restore, forceDestroy 라우트 추가 - UserService::getUsers()에 withTrashed() 추가
This commit is contained in:
@@ -140,4 +140,56 @@ public function destroy(int $id): JsonResponse
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 사용자 복원
|
||||
*/
|
||||
public function restore(Request $request, int $id): JsonResponse
|
||||
{
|
||||
$this->userService->restoreUser($id);
|
||||
|
||||
// HTMX 요청 시 테이블 새로고침 트리거
|
||||
if ($request->header('HX-Request')) {
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => '사용자가 복원되었습니다.',
|
||||
'action' => 'refresh',
|
||||
]);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => '사용자가 복원되었습니다.',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 사용자 영구 삭제 (슈퍼관리자 전용)
|
||||
*/
|
||||
public function forceDestroy(Request $request, int $id): JsonResponse
|
||||
{
|
||||
// 슈퍼관리자 권한 체크
|
||||
if (!auth()->user()?->is_super_admin) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => '권한이 없습니다.',
|
||||
], 403);
|
||||
}
|
||||
|
||||
$this->userService->forceDeleteUser($id);
|
||||
|
||||
// HTMX 요청 시 테이블 새로고침 트리거
|
||||
if ($request->header('HX-Request')) {
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => '사용자가 영구 삭제되었습니다.',
|
||||
'action' => 'refresh',
|
||||
]);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => '사용자가 영구 삭제되었습니다.',
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@ class UserService
|
||||
public function getUsers(array $filters = [], int $perPage = 15): LengthAwarePaginator
|
||||
{
|
||||
$tenantId = session('selected_tenant_id');
|
||||
$query = User::query();
|
||||
$query = User::query()->withTrashed();
|
||||
|
||||
// 테넌트 필터링 (user_tenants pivot을 통한 필터링)
|
||||
if ($tenantId) {
|
||||
@@ -23,6 +23,15 @@ public function getUsers(array $filters = [], int $perPage = 15): LengthAwarePag
|
||||
});
|
||||
}
|
||||
|
||||
// Soft Delete 필터
|
||||
if (isset($filters['trashed'])) {
|
||||
if ($filters['trashed'] === 'only') {
|
||||
$query->onlyTrashed();
|
||||
} elseif ($filters['trashed'] === 'with') {
|
||||
$query->withTrashed();
|
||||
}
|
||||
}
|
||||
|
||||
// 검색 필터
|
||||
if (! empty($filters['search'])) {
|
||||
$search = $filters['search'];
|
||||
@@ -125,6 +134,28 @@ public function deleteUser(int $id): bool
|
||||
return $user->delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* 사용자 복원
|
||||
*/
|
||||
public function restoreUser(int $id): bool
|
||||
{
|
||||
$user = User::onlyTrashed()->findOrFail($id);
|
||||
return $user->restore();
|
||||
}
|
||||
|
||||
/**
|
||||
* 사용자 영구 삭제 (슈퍼관리자 전용)
|
||||
*/
|
||||
public function forceDeleteUser(int $id): bool
|
||||
{
|
||||
$user = User::withTrashed()->findOrFail($id);
|
||||
|
||||
// 관련 데이터 먼저 삭제
|
||||
$user->tenants()->detach(); // user_tenants 관계 삭제
|
||||
|
||||
return $user->forceDelete();
|
||||
}
|
||||
|
||||
/**
|
||||
* 활성 사용자 목록 조회 (드롭다운용)
|
||||
*/
|
||||
|
||||
@@ -88,5 +88,35 @@ class="bg-white rounded-lg shadow-sm overflow-hidden">
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 복원 확인
|
||||
window.confirmRestore = function(id, name) {
|
||||
if (confirm(`"${name}" 사용자를 복원하시겠습니까?`)) {
|
||||
htmx.ajax('POST', `/api/admin/users/${id}/restore`, {
|
||||
target: '#user-table',
|
||||
swap: 'none',
|
||||
headers: {
|
||||
'X-CSRF-TOKEN': '{{ csrf_token() }}'
|
||||
}
|
||||
}).then(() => {
|
||||
htmx.trigger('#user-table', 'filterSubmit');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 영구삭제 확인
|
||||
window.confirmForceDelete = function(id, name) {
|
||||
if (confirm(`⚠️ 경고: "${name}" 사용자를 영구 삭제하시겠습니까?\n\n이 작업은 되돌릴 수 없습니다!`)) {
|
||||
htmx.ajax('DELETE', `/api/admin/users/${id}/force`, {
|
||||
target: '#user-table',
|
||||
swap: 'none',
|
||||
headers: {
|
||||
'X-CSRF-TOKEN': '{{ csrf_token() }}'
|
||||
}
|
||||
}).then(() => {
|
||||
htmx.trigger('#user-table', 'filterSubmit');
|
||||
});
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@endpush
|
||||
@@ -44,12 +44,27 @@
|
||||
@endif
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium">
|
||||
<a href="{{ route('users.edit', $user->id) }}" class="text-blue-600 hover:text-blue-900 mr-3">
|
||||
수정
|
||||
</a>
|
||||
<button onclick="confirmDelete({{ $user->id }}, '{{ $user->name }}')" class="text-red-600 hover:text-red-900">
|
||||
삭제
|
||||
</button>
|
||||
@if($user->deleted_at)
|
||||
<!-- 삭제된 항목 -->
|
||||
<button onclick="confirmRestore({{ $user->id }}, '{{ $user->name }}')"
|
||||
class="text-green-600 hover:text-green-900 mr-3">
|
||||
복원
|
||||
</button>
|
||||
@if(auth()->user()?->is_super_admin)
|
||||
<button onclick="confirmForceDelete({{ $user->id }}, '{{ $user->name }}')"
|
||||
class="text-red-600 hover:text-red-900">
|
||||
영구삭제
|
||||
</button>
|
||||
@endif
|
||||
@else
|
||||
<!-- 활성 항목 -->
|
||||
<a href="{{ route('users.edit', $user->id) }}" class="text-blue-600 hover:text-blue-900 mr-3">
|
||||
수정
|
||||
</a>
|
||||
<button onclick="confirmDelete({{ $user->id }}, '{{ $user->name }}')" class="text-red-600 hover:text-red-900">
|
||||
삭제
|
||||
</button>
|
||||
@endif
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
|
||||
@@ -59,5 +59,9 @@
|
||||
Route::get('/{id}', [UserController::class, 'show'])->name('show');
|
||||
Route::put('/{id}', [UserController::class, 'update'])->name('update');
|
||||
Route::delete('/{id}', [UserController::class, 'destroy'])->name('destroy');
|
||||
|
||||
// 추가 액션
|
||||
Route::post('/{id}/restore', [UserController::class, 'restore'])->name('restore');
|
||||
Route::delete('/{id}/force', [UserController::class, 'forceDestroy'])->name('forceDestroy');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user