feat: system tools and admin enhancements
This commit is contained in:
@@ -5,17 +5,157 @@ namespace App\Http\Controllers;
|
||||
use App\Models\Post;
|
||||
use App\Models\Thread;
|
||||
use App\Models\User;
|
||||
use App\Models\Attachment;
|
||||
use App\Models\Setting;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
class StatsController extends Controller
|
||||
{
|
||||
public function __invoke(): JsonResponse
|
||||
{
|
||||
$threadsCount = Thread::query()->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' => Thread::query()->withoutTrashed()->count(),
|
||||
'posts' => Post::query()->withoutTrashed()->count()
|
||||
+ Thread::query()->withoutTrashed()->count(),
|
||||
'users' => User::query()->count(),
|
||||
'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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user