when(! $user->isMasterAdmin(), function ($query) use ($user, $tenantId) { $query->where(function ($scope) use ($user, $tenantId) { $scope->where('uploaded_by', $user->id) ->when($tenantId, fn ($tenantScope) => $tenantScope->orWhere('tenant_id', $tenantId)); }); }) ->when($filters['search'] ?? null, function ($query, $search) { $query->where('original_name', 'like', "%{$search}%"); }) ->when($filters['category'] ?? null, fn ($query, $category) => $query->where('category', $category)) ->when($filters['mime_type'] ?? null, fn ($query, $mimeType) => $query->where('mime_type', $mimeType)) ->latest('id') ->paginate($filters['per_page'] ?? 10); } public function store(UploadedFile $upload, User $user, ?int $tenantId, string $category): StoredFile { $uuid = (string) Str::uuid(); $extension = strtolower($upload->getClientOriginalExtension() ?: $upload->extension() ?: 'bin'); $scope = $tenantId ? "tenants/{$tenantId}" : 'global'; $objectKey = "{$scope}/{$category}/".now()->format('Y/m')."/{$uuid}.{$extension}"; $disk = Storage::disk('minio'); $disk->putFileAs(dirname($objectKey), $upload, basename($objectKey), [ 'visibility' => 'private', 'ContentType' => $upload->getMimeType() ?: 'application/octet-stream', ]); try { return DB::transaction(fn () => StoredFile::create([ 'uuid' => $uuid, 'tenant_id' => $tenantId, 'uploaded_by' => $user->id, 'disk' => 'minio', 'bucket' => (string) config('filesystems.disks.minio.bucket'), 'object_key' => $objectKey, 'original_name' => $upload->getClientOriginalName(), 'extension' => $extension, 'mime_type' => $upload->getMimeType(), 'size' => $upload->getSize(), 'category' => $category, 'visibility' => 'private', ])); } catch (\Throwable $exception) { $disk->delete($objectKey); throw $exception; } } public function temporaryUrl(StoredFile $file, User $user): array { $this->authorizeAccess($file, $user); $expiresAt = now()->addMinutes(10); return [ 'url' => Storage::disk($file->disk)->temporaryUrl( $file->object_key, $expiresAt, ['ResponseContentDisposition' => 'inline; filename="'.addslashes($file->original_name).'"'] ), 'expires_at' => $expiresAt->toIso8601String(), ]; } public function delete(StoredFile $file, User $user): void { if (! $user->isMasterAdmin() && $file->uploaded_by !== $user->id) { abort(403, 'File hanya dapat dihapus oleh pengunggah atau Master Admin.'); } Storage::disk($file->disk)->delete($file->object_key); $file->delete(); } public function authorizeAccess(StoredFile $file, User $user): void { if ($user->isMasterAdmin() || $file->uploaded_by === $user->id) { return; } $canAccessTenant = $file->tenant_id && $user->userTenants() ->where('tenant_id', $file->tenant_id) ->exists(); abort_unless($canAccessTenant, 403, 'Anda tidak memiliki akses ke file ini.'); } public function validateProfilePhoto(StoredFile $file, User $user): void { if ($file->uploaded_by !== $user->id || $file->category !== 'profile' || ! str_starts_with((string) $file->mime_type, 'image/') || $file->size > 5 * 1024 * 1024) { throw ValidationException::withMessages([ 'profile_photo_file_id' => 'Foto profil harus berupa file gambar kategori profile yang Anda unggah sendiri.', ]); } } }