', now()) ->orWhere('last_failed_at', '>', now()->subMinutes(AdminLoginLockout::BLOCK_MINUTES)) ->latest('last_failed_at') ->get() ->map(fn (AdminLoginBlock $block) => [ 'id' => $block->id, 'kind' => $block->kind, 'identifier' => $block->identifier, 'last_ip' => $block->last_ip, 'failed_attempts' => $block->failed_attempts, 'last_username' => $block->last_username, 'user_agent' => $block->user_agent, 'last_failed_at' => $block->last_failed_at, 'blocked_until' => $block->isBlocked() ? $block->blocked_until : null, ]); return response()->json([ 'error' => false, 'data' => [ 'items' => $blocks, 'max_failures' => AdminLoginLockout::MAX_FAILURES, 'block_minutes' => AdminLoginLockout::BLOCK_MINUTES, ], ]); } public function destroy(Request $request, int $id): JsonResponse { $block = AdminLoginBlock::findOrFail($id); $block->delete(); $this->activityLogger->log( event: 'login_unblocked', logName: 'auth', description: sprintf('[admin] %s gỡ khoá đăng nhập %s %s', $request->user('admin')?->username, $this->label($block), $block->identifier), ); return response()->json(['error' => false, 'message' => sprintf('Đã gỡ khoá %s %s', $this->label($block), $block->identifier)]); } private function label(AdminLoginBlock $block): string { return $block->kind === AdminLoginLockout::KIND_ACCOUNT ? 'tài khoản' : 'IP'; } }