Skip to content

Commit 31706ea

Browse files
authored
Merge pull request #5689 from BookStackApp/permission_table_locking
Better parallel permission gen handling
2 parents 4b9e604 + 35a5119 commit 31706ea

15 files changed

+228
-148
lines changed

app/Entities/Controllers/BookController.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
use BookStack\Facades\Activity;
1919
use BookStack\Http\Controller;
2020
use BookStack\References\ReferenceFetcher;
21+
use BookStack\Util\DatabaseTransaction;
2122
use BookStack\Util\SimpleListOptions;
2223
use Illuminate\Http\Request;
2324
use Illuminate\Validation\ValidationException;
@@ -263,7 +264,9 @@ public function convertToShelf(HierarchyTransformer $transformer, string $bookSl
263264
$this->checkPermission('bookshelf-create-all');
264265
$this->checkPermission('book-create-all');
265266

266-
$shelf = $transformer->transformBookToShelf($book);
267+
$shelf = (new DatabaseTransaction(function () use ($book, $transformer) {
268+
return $transformer->transformBookToShelf($book);
269+
}))->run();
267270

268271
return redirect($shelf->getUrl());
269272
}

app/Entities/Controllers/ChapterController.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
use BookStack\Exceptions\PermissionsException;
1919
use BookStack\Http\Controller;
2020
use BookStack\References\ReferenceFetcher;
21+
use BookStack\Util\DatabaseTransaction;
2122
use Illuminate\Http\Request;
2223
use Illuminate\Validation\ValidationException;
2324
use Throwable;
@@ -269,7 +270,9 @@ public function convertToBook(HierarchyTransformer $transformer, string $bookSlu
269270
$this->checkOwnablePermission('chapter-delete', $chapter);
270271
$this->checkPermission('book-create-all');
271272

272-
$book = $transformer->transformChapterToBook($chapter);
273+
$book = (new DatabaseTransaction(function () use ($chapter, $transformer) {
274+
return $transformer->transformChapterToBook($chapter);
275+
}))->run();
273276

274277
return redirect($book->getUrl());
275278
}

app/Entities/Repos/BaseRepo.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,6 @@ public function update(Entity $entity, array $input)
7777
$entity->touch();
7878
}
7979

80-
$entity->rebuildPermissions();
8180
$entity->indexForSearch();
8281
$this->referenceStore->updateForEntity($entity);
8382

@@ -139,7 +138,7 @@ public function updateDefaultTemplate(Book|Chapter $entity, int $templateId): vo
139138

140139
/**
141140
* Sort the parent of the given entity, if any auto sort actions are set for it.
142-
* Typical ran during create/update/insert events.
141+
* Typically ran during create/update/insert events.
143142
*/
144143
public function sortParent(Entity $entity): void
145144
{

app/Entities/Repos/BookRepo.php

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use BookStack\Facades\Activity;
1111
use BookStack\Sorting\SortRule;
1212
use BookStack\Uploads\ImageRepo;
13+
use BookStack\Util\DatabaseTransaction;
1314
use Exception;
1415
use Illuminate\Http\UploadedFile;
1516

@@ -28,19 +29,22 @@ public function __construct(
2829
*/
2930
public function create(array $input): Book
3031
{
31-
$book = new Book();
32-
$this->baseRepo->create($book, $input);
33-
$this->baseRepo->updateCoverImage($book, $input['image'] ?? null);
34-
$this->baseRepo->updateDefaultTemplate($book, intval($input['default_template_id'] ?? null));
35-
Activity::add(ActivityType::BOOK_CREATE, $book);
32+
return (new DatabaseTransaction(function () use ($input) {
33+
$book = new Book();
3634

37-
$defaultBookSortSetting = intval(setting('sorting-book-default', '0'));
38-
if ($defaultBookSortSetting && SortRule::query()->find($defaultBookSortSetting)) {
39-
$book->sort_rule_id = $defaultBookSortSetting;
40-
$book->save();
41-
}
35+
$this->baseRepo->create($book, $input);
36+
$this->baseRepo->updateCoverImage($book, $input['image'] ?? null);
37+
$this->baseRepo->updateDefaultTemplate($book, intval($input['default_template_id'] ?? null));
38+
Activity::add(ActivityType::BOOK_CREATE, $book);
4239

43-
return $book;
40+
$defaultBookSortSetting = intval(setting('sorting-book-default', '0'));
41+
if ($defaultBookSortSetting && SortRule::query()->find($defaultBookSortSetting)) {
42+
$book->sort_rule_id = $defaultBookSortSetting;
43+
$book->save();
44+
}
45+
46+
return $book;
47+
}))->run();
4448
}
4549

4650
/**

app/Entities/Repos/BookshelfRepo.php

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use BookStack\Entities\Queries\BookQueries;
88
use BookStack\Entities\Tools\TrashCan;
99
use BookStack\Facades\Activity;
10+
use BookStack\Util\DatabaseTransaction;
1011
use Exception;
1112

1213
class BookshelfRepo
@@ -23,13 +24,14 @@ public function __construct(
2324
*/
2425
public function create(array $input, array $bookIds): Bookshelf
2526
{
26-
$shelf = new Bookshelf();
27-
$this->baseRepo->create($shelf, $input);
28-
$this->baseRepo->updateCoverImage($shelf, $input['image'] ?? null);
29-
$this->updateBooks($shelf, $bookIds);
30-
Activity::add(ActivityType::BOOKSHELF_CREATE, $shelf);
31-
32-
return $shelf;
27+
return (new DatabaseTransaction(function () use ($input, $bookIds) {
28+
$shelf = new Bookshelf();
29+
$this->baseRepo->create($shelf, $input);
30+
$this->baseRepo->updateCoverImage($shelf, $input['image'] ?? null);
31+
$this->updateBooks($shelf, $bookIds);
32+
Activity::add(ActivityType::BOOKSHELF_CREATE, $shelf);
33+
return $shelf;
34+
}))->run();
3335
}
3436

3537
/**

app/Entities/Repos/ChapterRepo.php

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
use BookStack\Exceptions\MoveOperationException;
1212
use BookStack\Exceptions\PermissionsException;
1313
use BookStack\Facades\Activity;
14+
use BookStack\Util\DatabaseTransaction;
1415
use Exception;
1516

1617
class ChapterRepo
@@ -27,16 +28,18 @@ public function __construct(
2728
*/
2829
public function create(array $input, Book $parentBook): Chapter
2930
{
30-
$chapter = new Chapter();
31-
$chapter->book_id = $parentBook->id;
32-
$chapter->priority = (new BookContents($parentBook))->getLastPriority() + 1;
33-
$this->baseRepo->create($chapter, $input);
34-
$this->baseRepo->updateDefaultTemplate($chapter, intval($input['default_template_id'] ?? null));
35-
Activity::add(ActivityType::CHAPTER_CREATE, $chapter);
36-
37-
$this->baseRepo->sortParent($chapter);
38-
39-
return $chapter;
31+
return (new DatabaseTransaction(function () use ($input, $parentBook) {
32+
$chapter = new Chapter();
33+
$chapter->book_id = $parentBook->id;
34+
$chapter->priority = (new BookContents($parentBook))->getLastPriority() + 1;
35+
$this->baseRepo->create($chapter, $input);
36+
$this->baseRepo->updateDefaultTemplate($chapter, intval($input['default_template_id'] ?? null));
37+
Activity::add(ActivityType::CHAPTER_CREATE, $chapter);
38+
39+
$this->baseRepo->sortParent($chapter);
40+
41+
return $chapter;
42+
}))->run();
4043
}
4144

4245
/**
@@ -88,12 +91,14 @@ public function move(Chapter $chapter, string $parentIdentifier): Book
8891
throw new PermissionsException('User does not have permission to create a chapter within the chosen book');
8992
}
9093

91-
$chapter->changeBook($parent->id);
92-
$chapter->rebuildPermissions();
93-
Activity::add(ActivityType::CHAPTER_MOVE, $chapter);
94+
return (new DatabaseTransaction(function () use ($chapter, $parent) {
95+
$chapter->changeBook($parent->id);
96+
$chapter->rebuildPermissions();
97+
Activity::add(ActivityType::CHAPTER_MOVE, $chapter);
9498

95-
$this->baseRepo->sortParent($chapter);
99+
$this->baseRepo->sortParent($chapter);
96100

97-
return $parent;
101+
return $parent;
102+
}))->run();
98103
}
99104
}

app/Entities/Repos/PageRepo.php

Lines changed: 33 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
use BookStack\Facades\Activity;
1919
use BookStack\References\ReferenceStore;
2020
use BookStack\References\ReferenceUpdater;
21+
use BookStack\Util\DatabaseTransaction;
2122
use Exception;
2223

2324
class PageRepo
@@ -61,8 +62,10 @@ public function getNewDraftPage(Entity $parent)
6162
]);
6263
}
6364

64-
$page->save();
65-
$page->refresh()->rebuildPermissions();
65+
(new DatabaseTransaction(function () use ($page) {
66+
$page->save();
67+
$page->refresh()->rebuildPermissions();
68+
}))->run();
6669

6770
return $page;
6871
}
@@ -72,26 +75,29 @@ public function getNewDraftPage(Entity $parent)
7275
*/
7376
public function publishDraft(Page $draft, array $input): Page
7477
{
75-
$draft->draft = false;
76-
$draft->revision_count = 1;
77-
$draft->priority = $this->getNewPriority($draft);
78-
$this->updateTemplateStatusAndContentFromInput($draft, $input);
79-
$this->baseRepo->update($draft, $input);
80-
81-
$summary = trim($input['summary'] ?? '') ?: trans('entities.pages_initial_revision');
82-
$this->revisionRepo->storeNewForPage($draft, $summary);
83-
$draft->refresh();
84-
85-
Activity::add(ActivityType::PAGE_CREATE, $draft);
86-
$this->baseRepo->sortParent($draft);
87-
88-
return $draft;
78+
return (new DatabaseTransaction(function () use ($draft, $input) {
79+
$draft->draft = false;
80+
$draft->revision_count = 1;
81+
$draft->priority = $this->getNewPriority($draft);
82+
$this->updateTemplateStatusAndContentFromInput($draft, $input);
83+
$this->baseRepo->update($draft, $input);
84+
$draft->rebuildPermissions();
85+
86+
$summary = trim($input['summary'] ?? '') ?: trans('entities.pages_initial_revision');
87+
$this->revisionRepo->storeNewForPage($draft, $summary);
88+
$draft->refresh();
89+
90+
Activity::add(ActivityType::PAGE_CREATE, $draft);
91+
$this->baseRepo->sortParent($draft);
92+
93+
return $draft;
94+
}))->run();
8995
}
9096

9197
/**
9298
* Directly update the content for the given page from the provided input.
9399
* Used for direct content access in a way that performs required changes
94-
* (Search index & reference regen) without performing an official update.
100+
* (Search index and reference regen) without performing an official update.
95101
*/
96102
public function setContentFromInput(Page $page, array $input): void
97103
{
@@ -116,7 +122,7 @@ public function update(Page $page, array $input): Page
116122
$page->revision_count++;
117123
$page->save();
118124

119-
// Remove all update drafts for this user & page.
125+
// Remove all update drafts for this user and page.
120126
$this->revisionRepo->deleteDraftsForCurrentUser($page);
121127

122128
// Save a revision after updating
@@ -269,16 +275,18 @@ public function move(Page $page, string $parentIdentifier): Entity
269275
throw new PermissionsException('User does not have permission to create a page within the new parent');
270276
}
271277

272-
$page->chapter_id = ($parent instanceof Chapter) ? $parent->id : null;
273-
$newBookId = ($parent instanceof Chapter) ? $parent->book->id : $parent->id;
274-
$page->changeBook($newBookId);
275-
$page->rebuildPermissions();
278+
return (new DatabaseTransaction(function () use ($page, $parent) {
279+
$page->chapter_id = ($parent instanceof Chapter) ? $parent->id : null;
280+
$newBookId = ($parent instanceof Chapter) ? $parent->book->id : $parent->id;
281+
$page->changeBook($newBookId);
282+
$page->rebuildPermissions();
276283

277-
Activity::add(ActivityType::PAGE_MOVE, $page);
284+
Activity::add(ActivityType::PAGE_MOVE, $page);
278285

279-
$this->baseRepo->sortParent($page);
286+
$this->baseRepo->sortParent($page);
280287

281-
return $parent;
288+
return $parent;
289+
}))->run();
282290
}
283291

284292
/**

app/Entities/Tools/HierarchyTransformer.php

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,12 @@
1313

1414
class HierarchyTransformer
1515
{
16-
protected BookRepo $bookRepo;
17-
protected BookshelfRepo $shelfRepo;
18-
protected Cloner $cloner;
19-
protected TrashCan $trashCan;
20-
21-
public function __construct(BookRepo $bookRepo, BookshelfRepo $shelfRepo, Cloner $cloner, TrashCan $trashCan)
22-
{
23-
$this->bookRepo = $bookRepo;
24-
$this->shelfRepo = $shelfRepo;
25-
$this->cloner = $cloner;
26-
$this->trashCan = $trashCan;
16+
public function __construct(
17+
protected BookRepo $bookRepo,
18+
protected BookshelfRepo $shelfRepo,
19+
protected Cloner $cloner,
20+
protected TrashCan $trashCan
21+
) {
2722
}
2823

2924
/**

app/Entities/Tools/TrashCan.php

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use BookStack\Facades\Activity;
1616
use BookStack\Uploads\AttachmentService;
1717
use BookStack\Uploads\ImageService;
18+
use BookStack\Util\DatabaseTransaction;
1819
use Exception;
1920
use Illuminate\Database\Eloquent\Builder;
2021
use Illuminate\Support\Carbon;
@@ -357,25 +358,26 @@ protected function restoreEntity(Entity $entity): int
357358

358359
/**
359360
* Destroy the given entity.
361+
* Returns the number of total entities destroyed in the operation.
360362
*
361363
* @throws Exception
362364
*/
363365
public function destroyEntity(Entity $entity): int
364366
{
365-
if ($entity instanceof Page) {
366-
return $this->destroyPage($entity);
367-
}
368-
if ($entity instanceof Chapter) {
369-
return $this->destroyChapter($entity);
370-
}
371-
if ($entity instanceof Book) {
372-
return $this->destroyBook($entity);
373-
}
374-
if ($entity instanceof Bookshelf) {
375-
return $this->destroyShelf($entity);
376-
}
367+
$result = (new DatabaseTransaction(function () use ($entity) {
368+
if ($entity instanceof Page) {
369+
return $this->destroyPage($entity);
370+
} else if ($entity instanceof Chapter) {
371+
return $this->destroyChapter($entity);
372+
} else if ($entity instanceof Book) {
373+
return $this->destroyBook($entity);
374+
} else if ($entity instanceof Bookshelf) {
375+
return $this->destroyShelf($entity);
376+
}
377+
return null;
378+
}))->run();
377379

378-
return 0;
380+
return $result ?? 0;
379381
}
380382

381383
/**

0 commit comments

Comments
 (0)