| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260 |
- <?php
- declare(strict_types=1);
- namespace App\Services;
- use App\Models\DocumentationDocument;
- use App\Models\DocumentationDocumentVersion;
- use App\Models\DocumentationFolder;
- use App\Models\DocumentationFolderPermission;
- use App\Models\File;
- use App\Models\User;
- use Illuminate\Http\UploadedFile;
- use Illuminate\Support\Facades\DB;
- use Throwable;
- class DocumentationService
- {
- public function __construct(
- private readonly FileService $fileService,
- private readonly DocumentationAccessService $accessService,
- ) {}
- public function createFolder(?DocumentationFolder $parent, string $name, User $user): DocumentationFolder
- {
- return DB::transaction(function () use ($parent, $name, $user): DocumentationFolder {
- $folder = DocumentationFolder::query()->create([
- 'parent_id' => $parent?->id,
- 'name' => $name,
- 'inherits_permissions' => $parent !== null,
- 'created_by' => $user->id,
- ]);
- if (! $parent) {
- $folder->permissionRules()->create([
- 'subject_type' => DocumentationFolderPermission::SUBJECT_ALL,
- 'can_read' => true,
- 'can_write' => false,
- ]);
- }
- $this->accessService->forgetCachedRules();
- return $folder;
- });
- }
- public function renameFolder(DocumentationFolder $folder, string $name): void
- {
- $folder->update(['name' => $name]);
- }
- /** @param array<string, mixed> $data */
- public function updatePermissions(DocumentationFolder $folder, array $data): void
- {
- DB::transaction(function () use ($folder, $data): void {
- $inherit = $folder->parent_id !== null && (bool) ($data['inherit_permissions'] ?? false);
- $folder->permissionRules()->delete();
- $folder->update(['inherits_permissions' => $inherit]);
- if ($inherit) {
- return;
- }
- $rules = [];
- $this->mergeRule(
- $rules,
- DocumentationFolderPermission::SUBJECT_ALL,
- null,
- (bool) ($data['all_read'] ?? false),
- (bool) ($data['all_write'] ?? false),
- );
- foreach ((array) ($data['role_read_ids'] ?? []) as $roleId) {
- $this->mergeRule($rules, DocumentationFolderPermission::SUBJECT_ROLE, (int) $roleId, true, false);
- }
- foreach ((array) ($data['role_write_ids'] ?? []) as $roleId) {
- $this->mergeRule($rules, DocumentationFolderPermission::SUBJECT_ROLE, (int) $roleId, true, true);
- }
- foreach ((array) ($data['user_read_ids'] ?? []) as $userId) {
- $this->mergeRule($rules, DocumentationFolderPermission::SUBJECT_USER, (int) $userId, true, false);
- }
- foreach ((array) ($data['user_write_ids'] ?? []) as $userId) {
- $this->mergeRule($rules, DocumentationFolderPermission::SUBJECT_USER, (int) $userId, true, true);
- }
- foreach ($rules as $rule) {
- $folder->permissionRules()->create($rule);
- }
- });
- $this->accessService->forgetCachedRules();
- }
- public function createDocument(
- DocumentationFolder $folder,
- ?string $name,
- UploadedFile $uploadedFile,
- User $user,
- ): DocumentationDocument {
- $file = $this->fileService->saveUploadedPrivateFile("documentation/{$folder->id}", $uploadedFile, $user->id);
- try {
- return DB::transaction(function () use ($folder, $name, $uploadedFile, $user, $file): DocumentationDocument {
- $document = DocumentationDocument::query()->create([
- 'folder_id' => $folder->id,
- 'name' => $name ?: $file->original_name,
- 'last_version_number' => 1,
- 'created_by' => $user->id,
- ]);
- $document->versions()->create([
- 'file_id' => $file->id,
- 'version' => 1,
- 'file_size' => max(0, (int) $uploadedFile->getSize()),
- 'created_by' => $user->id,
- ]);
- return $document;
- });
- } catch (Throwable $exception) {
- $this->deleteStoredFile($file);
- throw $exception;
- }
- }
- public function renameDocument(DocumentationDocument $document, string $name): void
- {
- $document->update(['name' => $name]);
- }
- public function addVersion(
- DocumentationDocument $document,
- UploadedFile $uploadedFile,
- User $user,
- ): DocumentationDocumentVersion {
- $file = $this->fileService->saveUploadedPrivateFile("documentation/{$document->folder_id}", $uploadedFile, $user->id);
- try {
- return DB::transaction(function () use ($document, $uploadedFile, $user, $file): DocumentationDocumentVersion {
- $lockedDocument = DocumentationDocument::query()->lockForUpdate()->findOrFail($document->id);
- $nextVersion = $lockedDocument->last_version_number + 1;
- $version = $lockedDocument->versions()->create([
- 'file_id' => $file->id,
- 'version' => $nextVersion,
- 'file_size' => max(0, (int) $uploadedFile->getSize()),
- 'created_by' => $user->id,
- ]);
- $lockedDocument->update(['last_version_number' => $nextVersion]);
- return $version;
- });
- } catch (Throwable $exception) {
- $this->deleteStoredFile($file);
- throw $exception;
- }
- }
- public function deleteVersion(DocumentationDocumentVersion $version): void
- {
- $version->loadMissing('file');
- $file = $version->file;
- $documentId = $version->document_id;
- DB::transaction(function () use ($version, $documentId): void {
- $version->delete();
- $document = DocumentationDocument::query()->lockForUpdate()->find($documentId);
- if ($document && ! $document->versions()->exists()) {
- $document->delete();
- }
- });
- if ($file) {
- $this->deleteStoredFile($file);
- }
- }
- public function deleteDocument(DocumentationDocument $document): void
- {
- $document->loadMissing('versions.file');
- $files = $document->versions->pluck('file')->filter();
- DB::transaction(function () use ($document): void {
- $document->versions()->delete();
- $document->delete();
- });
- foreach ($files as $file) {
- $this->deleteStoredFile($file);
- }
- }
- public function deleteFolder(DocumentationFolder $folder): void
- {
- $folderIds = $this->descendantIds($folder);
- $documents = DocumentationDocument::query()
- ->whereIn('folder_id', $folderIds)
- ->with('versions.file')
- ->get();
- $files = $documents->flatMap->versions->pluck('file')->filter();
- DB::transaction(fn (): bool => $folder->delete());
- foreach ($files as $file) {
- $this->deleteStoredFile($file);
- }
- $this->accessService->forgetCachedRules();
- }
- /**
- * @param array<string, array<string, mixed>> $rules
- */
- private function mergeRule(
- array &$rules,
- string $subjectType,
- ?int $subjectId,
- bool $canRead,
- bool $canWrite,
- ): void {
- if (! $canRead && ! $canWrite) {
- return;
- }
- $key = "{$subjectType}:".($subjectId ?? 'all');
- $rules[$key] ??= [
- 'subject_type' => $subjectType,
- 'role_id' => $subjectType === DocumentationFolderPermission::SUBJECT_ROLE ? $subjectId : null,
- 'user_id' => $subjectType === DocumentationFolderPermission::SUBJECT_USER ? $subjectId : null,
- 'can_read' => false,
- 'can_write' => false,
- ];
- $rules[$key]['can_read'] = (bool) ($rules[$key]['can_read'] || $canRead || $canWrite);
- $rules[$key]['can_write'] = (bool) ($rules[$key]['can_write'] || $canWrite);
- }
- /** @return list<int> */
- private function descendantIds(DocumentationFolder $folder): array
- {
- $ids = [$folder->id];
- $pending = [$folder->id];
- while ($pending !== []) {
- $children = DocumentationFolder::query()->whereIn('parent_id', $pending)->pluck('id')->all();
- $pending = array_map('intval', $children);
- $ids = [...$ids, ...$pending];
- }
- return $ids;
- }
- private function deleteStoredFile(File $file): void
- {
- $this->fileService->deletePrivateFile($file);
- $file->delete();
- }
- }
|