validate([ 'keyword' => ['sometimes', 'nullable', 'string', 'max:255'], 'type' => ['sometimes', 'nullable', 'in:all,image,file'], 'page' => ['sometimes', 'integer', 'min:1'], 'per_page' => ['sometimes', 'integer', 'min:1', 'max:100'], ]); $keyword = Str::lower((string) ($validated['keyword'] ?? '')); $type = $validated['type'] ?? 'all'; $page = (int) ($validated['page'] ?? 1); $perPage = (int) ($validated['per_page'] ?? 24); $files = collect(Storage::disk('local')->allFiles()) ->filter(fn (string $path) => ! Str::startsWith(basename($path), '.')) ->map(fn (string $path) => $this->formatFile($path)) ->filter(function (array $file) use ($keyword, $type) { if ($type === 'image' && ! $file['is_image']) { return false; } if ($type === 'file' && $file['is_image']) { return false; } if ($keyword === '') { return true; } return Str::contains(Str::lower($file['name']), $keyword) || Str::contains(Str::lower($file['path']), $keyword) || Str::contains(Str::lower((string) $file['mime_type']), $keyword); }) ->sortByDesc('last_modified') ->values(); $total = $files->count(); $items = $files->slice(($page - 1) * $perPage, $perPage)->values(); return response()->json([ 'error' => false, 'data' => [ 'items' => $items, 'current_page' => $page, 'per_page' => $perPage, 'total' => $total, 'last_page' => (int) ceil($total / $perPage), ], ]); } private function formatFile(string $path): array { $disk = Storage::disk('local'); $mimeType = $disk->mimeType($path) ?: 'application/octet-stream'; $isPublic = Str::startsWith($path, 'public/'); $publicPath = $isPublic ? Str::after($path, 'public/') : null; return [ 'id' => sha1($path), 'disk' => $isPublic ? 'public' : 'local', 'name' => basename($path), 'path' => $path, 'directory' => trim(dirname($path), '.'), 'mime_type' => $mimeType, 'is_image' => in_array($mimeType, self::IMAGE_MIME_TYPES, true), 'size' => $disk->size($path), 'last_modified' => date(DATE_ATOM, $disk->lastModified($path)), 'url' => $publicPath ? Storage::disk('public')->url($publicPath) : null, ]; } }