mirror of
https://github.com/projectsend/projectsend.git
synced 2026-10-04 05:25:51 +00:00
70dc725858
An integration could put a file into a folder by id but could not see, make or arrange the folders themselves, so mirroring a directory tree into ProjectSend was impossible over the API. The hosted AI connector already creates, lists and shares folders. GET /folders polls like every list (updated_since, cursor) and filters on parent_id, top_level and search. Each folder carries its ancestors and a display path, trimmed for a client-scoped token to the folders it may see (BreadcrumbBuilder::visible's rule), worked out for a whole page in two queries by FolderTrails. POST /folders returns an existing folder of the same name in the same place with a 200 rather than making a second one, so a retried request is safe. PATCH renames and moves. DELETE refuses a non-empty folder with 409 unless content_action=cascade_delete is sent, and then asks UndeletableFiles exactly as the web does. Sharing goes through FolderSharing. Every write uses the web's policy, scope and FolderService, and asks Folder::uploadableBy for every parent it writes, creation included. Public state stays web-only: the resource reports `public`, nothing here changes it. A file's `folder` now carries `parent_id` as well.
69 lines
2.9 KiB
PHP
69 lines
2.9 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Modules\Files\Http\Resources\Api;
|
|
|
|
use App\Modules\Files\Access\ClientIdentityScope;
|
|
use App\Modules\Files\Models\Folder;
|
|
use App\Modules\Files\Models\FolderAssignment;
|
|
use App\Modules\Groups\Models\Group;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Http\Resources\Json\JsonResource;
|
|
|
|
/**
|
|
* @mixin Folder
|
|
*
|
|
* Every field is listed explicitly, never $folder->toArray(), for the same
|
|
* reason as FileResource: the next migration must not publish itself.
|
|
*
|
|
* `ancestors` and `path` come from FolderTrails, loaded by the controller
|
|
* for a whole page at once, and are trimmed to the folders the caller may
|
|
* see. The assignment list is narrowed per entry by ClientIdentityScope,
|
|
* exactly as FileResource narrows a file's.
|
|
*/
|
|
class FolderResource extends JsonResource
|
|
{
|
|
/**
|
|
* @return array<string, mixed>
|
|
*/
|
|
public function toArray(Request $request): array
|
|
{
|
|
$viewer = $request->user();
|
|
$identity = app(ClientIdentityScope::class);
|
|
$groupMorph = (new Group)->getMorphClass();
|
|
|
|
/** @var list<array{id: int, name: string}> $ancestors */
|
|
$ancestors = $this->relationLoaded('trail') ? $this->getRelation('trail')->all() : [];
|
|
|
|
return [
|
|
'id' => $this->id,
|
|
'name' => $this->name,
|
|
'parent_id' => $this->parent_id,
|
|
// The folders above this one, root first, as far up as the
|
|
// caller may see. Empty for a folder at the top of the library.
|
|
'ancestors' => $ancestors,
|
|
// The same trail as one string, this folder included:
|
|
// "Clients / Acme / 2026". For display; match on ids, since a
|
|
// folder name may itself contain " / ".
|
|
'path' => implode(' / ', [...array_column($ancestors, 'name'), $this->name]),
|
|
// Read-only here. Making a folder public publishes everything
|
|
// inside it, and is done on the web.
|
|
'public' => (bool) $this->public,
|
|
'created_at' => $this->created_at?->toIso8601String(),
|
|
'updated_at' => $this->updated_at?->toIso8601String(),
|
|
'assignments' => $this->whenLoaded('assignments', fn (): array => $this->assignments
|
|
->filter(fn (FolderAssignment $assignment): bool => $assignment->assignable_type === $groupMorph
|
|
? $identity->permitsGroupId($viewer, (int) $assignment->assignable_id)
|
|
: $identity->permitsClientId($viewer, (int) $assignment->assignable_id))
|
|
->map(fn (FolderAssignment $assignment): array => [
|
|
'type' => $assignment->assignable_type === $groupMorph ? 'group' : 'client',
|
|
'id' => $assignment->assignable_id,
|
|
'name' => $assignment->assignable?->getAttribute('name'),
|
|
])
|
|
->values()
|
|
->all()),
|
|
];
|
|
}
|
|
}
|