withoutTrashed()->count(); $postsCount = Post::query()->withoutTrashed()->count(); $usersCount = User::query()->count(); $attachmentsCount = Attachment::query()->withoutTrashed()->count(); $attachmentsSizeBytes = (int) Attachment::query()->withoutTrashed()->sum('size_bytes'); $boardStartedAt = $this->resolveBoardStartedAt(); $daysSinceStart = $boardStartedAt ? max(1, Carbon::parse($boardStartedAt)->diffInSeconds(now()) / 86400) : null; $dbSizeBytes = $this->resolveDatabaseSize(); $dbServer = $this->resolveDatabaseServer(); $avatarSizeBytes = $this->resolveAvatarDirectorySize(); $orphanAttachments = $this->resolveOrphanAttachments(); $version = Setting::query()->where('key', 'version')->value('value'); $build = Setting::query()->where('key', 'build')->value('value'); $boardVersion = $version ? ($build ? "{$version} (build {$build})" : $version) : null; return response()->json([ 'threads' => $threadsCount, 'posts' => $postsCount + $threadsCount, 'users' => $usersCount, 'attachments' => $attachmentsCount, 'board_started_at' => $boardStartedAt, 'attachments_size_bytes' => $attachmentsSizeBytes, 'avatar_directory_size_bytes' => $avatarSizeBytes, 'database_size_bytes' => $dbSizeBytes, 'database_server' => $dbServer, 'gzip_compression' => $this->resolveGzipCompression(), 'php_version' => PHP_VERSION, 'orphan_attachments' => $orphanAttachments, 'board_version' => $boardVersion, 'posts_per_day' => $daysSinceStart ? ($postsCount + $threadsCount) / $daysSinceStart : null, 'topics_per_day' => $daysSinceStart ? $threadsCount / $daysSinceStart : null, 'users_per_day' => $daysSinceStart ? $usersCount / $daysSinceStart : null, 'attachments_per_day' => $daysSinceStart ? $attachmentsCount / $daysSinceStart : null, ]); } private function resolveBoardStartedAt(): ?string { $timestamps = [ User::query()->min('created_at'), Thread::query()->min('created_at'), Post::query()->min('created_at'), ]; $min = null; foreach ($timestamps as $value) { if (!$value) { continue; } $time = Carbon::parse($value)->timestamp; if ($min === null || $time < $min) { $min = $time; } } return $min !== null ? Carbon::createFromTimestamp($min)->toIso8601String() : null; } private function resolveDatabaseSize(): ?int { try { $driver = DB::connection()->getDriverName(); if ($driver === 'mysql') { $row = DB::selectOne('SELECT SUM(data_length + index_length) AS size FROM information_schema.tables WHERE table_schema = DATABASE()'); return $row && isset($row->size) ? (int) $row->size : null; } } catch (\Throwable) { return null; } return null; } private function resolveDatabaseServer(): ?string { try { $row = DB::selectOne('SELECT VERSION() AS version'); return $row && isset($row->version) ? (string) $row->version : null; } catch (\Throwable) { return null; } } private function resolveAvatarDirectorySize(): ?int { try { $disk = Storage::disk('public'); $files = $disk->allFiles('avatars'); $total = 0; foreach ($files as $file) { $total += $disk->size($file); } return $total; } catch (\Throwable) { return null; } } private function resolveOrphanAttachments(): int { try { return (int) DB::table('attachments') ->leftJoin('threads', 'attachments.thread_id', '=', 'threads.id') ->leftJoin('posts', 'attachments.post_id', '=', 'posts.id') ->whereNull('attachments.deleted_at') ->where(function ($query) { $query ->whereNull('attachments.thread_id') ->whereNull('attachments.post_id') ->orWhere(function ($inner) { $inner->whereNotNull('attachments.thread_id') ->where(function ($inner2) { $inner2->whereNull('threads.id') ->orWhereNotNull('threads.deleted_at'); }); }) ->orWhere(function ($inner) { $inner->whereNotNull('attachments.post_id') ->where(function ($inner2) { $inner2->whereNull('posts.id') ->orWhereNotNull('posts.deleted_at'); }); }); }) ->count(); } catch (\Throwable) { return 0; } } private function resolveGzipCompression(): bool { $value = ini_get('zlib.output_compression'); return in_array(strtolower((string) $value), ['1', 'on', 'true'], true); } }