Queries: Migrated BookRepo queries to new query class

Also moved to a non-static approach, and added a high-level class to
allow easy access to all other entity queries, for use in mixed-entity
scenarios and easier/simpler injection.
This commit is contained in:
Dan Brown 2024-02-04 17:35:16 +00:00
parent a70ed81908
commit 1559b0acd1
No known key found for this signature in database
GPG Key ID: 46D9F943C24A2EF9
8 changed files with 118 additions and 105 deletions

View File

@ -5,10 +5,9 @@ namespace BookStack\App;
use BookStack\Activity\ActivityQueries;
use BookStack\Entities\Models\Book;
use BookStack\Entities\Models\Page;
use BookStack\Entities\Queries\PageQueries;
use BookStack\Entities\Queries\EntityQueries;
use BookStack\Entities\Queries\RecentlyViewed;
use BookStack\Entities\Queries\TopFavourites;
use BookStack\Entities\Repos\BookRepo;
use BookStack\Entities\Repos\BookshelfRepo;
use BookStack\Entities\Tools\PageContent;
use BookStack\Http\Controller;
@ -18,6 +17,11 @@ use Illuminate\Http\Request;
class HomeController extends Controller
{
public function __construct(
protected EntityQueries $queries,
) {
}
/**
* Display the homepage.
*/
@ -27,7 +31,7 @@ class HomeController extends Controller
$draftPages = [];
if ($this->isSignedIn()) {
$draftPages = PageQueries::currentUserDraftsForList()
$draftPages = $this->queries->pages->currentUserDraftsForList()
->orderBy('updated_at', 'desc')
->with('book')
->take(6)
@ -39,7 +43,7 @@ class HomeController extends Controller
(new RecentlyViewed())->run(12 * $recentFactor, 1)
: Book::visible()->orderBy('created_at', 'desc')->take(12 * $recentFactor)->get();
$favourites = (new TopFavourites())->run(6);
$recentlyUpdatedPages = PageQueries::visibleForList()
$recentlyUpdatedPages = $this->queries->pages->visibleForList()
->where('draft', false)
->orderBy('updated_at', 'desc')
->take($favourites->count() > 0 ? 5 : 10)
@ -83,7 +87,9 @@ class HomeController extends Controller
}
if ($homepageOption === 'books') {
$books = app()->make(BookRepo::class)->getAllPaginated(18, $commonData['listOptions']->getSort(), $commonData['listOptions']->getOrder());
$books = $this->queries->books->visibleForListWithCover()
->orderBy($commonData['listOptions']->getSort(), $commonData['listOptions']->getOrder())
->paginate(18);
$data = array_merge($commonData, ['books' => $books]);
return view('home.books', $data);
@ -93,7 +99,7 @@ class HomeController extends Controller
$homepageSetting = setting('app-homepage', '0:');
$id = intval(explode(':', $homepageSetting)[0]);
/** @var Page $customHomepage */
$customHomepage = PageQueries::start()->where('draft', '=', false)->findOrFail($id);
$customHomepage = $this->queries->pages->start()->where('draft', '=', false)->findOrFail($id);
$pageContent = new PageContent($customHomepage);
$customHomepage->html = $pageContent->render(false);

View File

@ -7,6 +7,7 @@ use BookStack\Activity\ActivityType;
use BookStack\Activity\Models\View;
use BookStack\Activity\Tools\UserEntityWatchOptions;
use BookStack\Entities\Models\Bookshelf;
use BookStack\Entities\Queries\BookQueries;
use BookStack\Entities\Repos\BookRepo;
use BookStack\Entities\Tools\BookContents;
use BookStack\Entities\Tools\Cloner;
@ -27,7 +28,8 @@ class BookController extends Controller
public function __construct(
protected ShelfContext $shelfContext,
protected BookRepo $bookRepo,
protected ReferenceFetcher $referenceFetcher
protected BookQueries $queries,
protected ReferenceFetcher $referenceFetcher,
) {
}
@ -43,10 +45,12 @@ class BookController extends Controller
'updated_at' => trans('common.sort_updated_at'),
]);
$books = $this->bookRepo->getAllPaginated(18, $listOptions->getSort(), $listOptions->getOrder());
$recents = $this->isSignedIn() ? $this->bookRepo->getRecentlyViewed(4) : false;
$popular = $this->bookRepo->getPopular(4);
$new = $this->bookRepo->getRecentlyCreated(4);
$books = $this->queries->visibleForListWithCover()
->orderBy($listOptions->getSort(), $listOptions->getOrder())
->paginate(18);
$recents = $this->isSignedIn() ? $this->queries->recentlyViewedForCurrentUser()->take(4)->get() : false;
$popular = $this->queries->popularForList()->take(4)->get();
$new = $this->queries->visibleForList()->orderBy('created_at', 'desc')->take(4)->get();
$this->shelfContext->clearShelfContext();
@ -120,7 +124,7 @@ class BookController extends Controller
*/
public function show(Request $request, ActivityQueries $activities, string $slug)
{
$book = $this->bookRepo->getBySlug($slug);
$book = $this->queries->findVisibleBySlug($slug);
$bookChildren = (new BookContents($book))->getTree(true);
$bookParentShelves = $book->shelves()->scopes('visible')->get();
@ -147,7 +151,7 @@ class BookController extends Controller
*/
public function edit(string $slug)
{
$book = $this->bookRepo->getBySlug($slug);
$book = $this->queries->findVisibleBySlug($slug);
$this->checkOwnablePermission('book-update', $book);
$this->setPageTitle(trans('entities.books_edit_named', ['bookName' => $book->getShortName()]));
@ -163,7 +167,7 @@ class BookController extends Controller
*/
public function update(Request $request, string $slug)
{
$book = $this->bookRepo->getBySlug($slug);
$book = $this->queries->findVisibleBySlug($slug);
$this->checkOwnablePermission('book-update', $book);
$validated = $this->validate($request, [
@ -190,7 +194,7 @@ class BookController extends Controller
*/
public function showDelete(string $bookSlug)
{
$book = $this->bookRepo->getBySlug($bookSlug);
$book = $this->queries->findVisibleBySlug($bookSlug);
$this->checkOwnablePermission('book-delete', $book);
$this->setPageTitle(trans('entities.books_delete_named', ['bookName' => $book->getShortName()]));
@ -204,7 +208,7 @@ class BookController extends Controller
*/
public function destroy(string $bookSlug)
{
$book = $this->bookRepo->getBySlug($bookSlug);
$book = $this->queries->findVisibleBySlug($bookSlug);
$this->checkOwnablePermission('book-delete', $book);
$this->bookRepo->destroy($book);
@ -219,7 +223,7 @@ class BookController extends Controller
*/
public function showCopy(string $bookSlug)
{
$book = $this->bookRepo->getBySlug($bookSlug);
$book = $this->queries->findVisibleBySlug($bookSlug);
$this->checkOwnablePermission('book-view', $book);
session()->flashInput(['name' => $book->name]);
@ -236,7 +240,7 @@ class BookController extends Controller
*/
public function copy(Request $request, Cloner $cloner, string $bookSlug)
{
$book = $this->bookRepo->getBySlug($bookSlug);
$book = $this->queries->findVisibleBySlug($bookSlug);
$this->checkOwnablePermission('book-view', $book);
$this->checkPermission('book-create-all');
@ -252,7 +256,7 @@ class BookController extends Controller
*/
public function convertToShelf(HierarchyTransformer $transformer, string $bookSlug)
{
$book = $this->bookRepo->getBySlug($bookSlug);
$book = $this->queries->findVisibleBySlug($bookSlug);
$this->checkOwnablePermission('book-update', $book);
$this->checkOwnablePermission('book-delete', $book);
$this->checkPermission('bookshelf-create-all');

View File

@ -2,23 +2,17 @@
namespace BookStack\Entities\Controllers;
use BookStack\Entities\Repos\BookRepo;
use BookStack\Entities\Queries\BookQueries;
use BookStack\Entities\Tools\ExportFormatter;
use BookStack\Http\Controller;
use Throwable;
class BookExportController extends Controller
{
protected $bookRepo;
protected $exportFormatter;
/**
* BookExportController constructor.
*/
public function __construct(BookRepo $bookRepo, ExportFormatter $exportFormatter)
{
$this->bookRepo = $bookRepo;
$this->exportFormatter = $exportFormatter;
public function __construct(
protected BookQueries $queries,
protected ExportFormatter $exportFormatter,
) {
$this->middleware('can:content-export');
}
@ -29,7 +23,7 @@ class BookExportController extends Controller
*/
public function pdf(string $bookSlug)
{
$book = $this->bookRepo->getBySlug($bookSlug);
$book = $this->queries->findVisibleBySlug($bookSlug);
$pdfContent = $this->exportFormatter->bookToPdf($book);
return $this->download()->directly($pdfContent, $bookSlug . '.pdf');
@ -42,7 +36,7 @@ class BookExportController extends Controller
*/
public function html(string $bookSlug)
{
$book = $this->bookRepo->getBySlug($bookSlug);
$book = $this->queries->findVisibleBySlug($bookSlug);
$htmlContent = $this->exportFormatter->bookToContainedHtml($book);
return $this->download()->directly($htmlContent, $bookSlug . '.html');
@ -53,7 +47,7 @@ class BookExportController extends Controller
*/
public function plainText(string $bookSlug)
{
$book = $this->bookRepo->getBySlug($bookSlug);
$book = $this->queries->findVisibleBySlug($bookSlug);
$textContent = $this->exportFormatter->bookToPlainText($book);
return $this->download()->directly($textContent, $bookSlug . '.txt');
@ -64,7 +58,7 @@ class BookExportController extends Controller
*/
public function markdown(string $bookSlug)
{
$book = $this->bookRepo->getBySlug($bookSlug);
$book = $this->queries->findVisibleBySlug($bookSlug);
$textContent = $this->exportFormatter->bookToMarkdown($book);
return $this->download()->directly($textContent, $bookSlug . '.md');

View File

@ -3,7 +3,7 @@
namespace BookStack\Entities\Controllers;
use BookStack\Activity\ActivityType;
use BookStack\Entities\Repos\BookRepo;
use BookStack\Entities\Queries\BookQueries;
use BookStack\Entities\Tools\BookContents;
use BookStack\Entities\Tools\BookSortMap;
use BookStack\Facades\Activity;
@ -12,11 +12,9 @@ use Illuminate\Http\Request;
class BookSortController extends Controller
{
protected $bookRepo;
public function __construct(BookRepo $bookRepo)
{
$this->bookRepo = $bookRepo;
public function __construct(
protected BookQueries $queries,
) {
}
/**
@ -24,7 +22,7 @@ class BookSortController extends Controller
*/
public function show(string $bookSlug)
{
$book = $this->bookRepo->getBySlug($bookSlug);
$book = $this->queries->findVisibleBySlug($bookSlug);
$this->checkOwnablePermission('book-update', $book);
$bookChildren = (new BookContents($book))->getTree(false);
@ -40,7 +38,7 @@ class BookSortController extends Controller
*/
public function showItem(string $bookSlug)
{
$book = $this->bookRepo->getBySlug($bookSlug);
$book = $this->queries->findVisibleBySlug($bookSlug);
$bookChildren = (new BookContents($book))->getTree();
return view('books.parts.sort-box', ['book' => $book, 'bookChildren' => $bookChildren]);
@ -51,7 +49,7 @@ class BookSortController extends Controller
*/
public function update(Request $request, string $bookSlug)
{
$book = $this->bookRepo->getBySlug($bookSlug);
$book = $this->queries->findVisibleBySlug($bookSlug);
$this->checkOwnablePermission('book-update', $book);
// Return if no map sent

View File

@ -0,0 +1,56 @@
<?php
namespace BookStack\Entities\Queries;
use BookStack\Entities\Models\Book;
use BookStack\Exceptions\NotFoundException;
use Illuminate\Database\Eloquent\Builder;
class BookQueries
{
public function start(): Builder
{
return Book::query();
}
public function findVisibleBySlug(string $slug): Book
{
/** @var ?Book $book */
$book = $this->start()
->scopes('visible')
->where('slug', '=', $slug)
->first();
if ($book === null) {
throw new NotFoundException(trans('errors.book_not_found'));
}
return $book;
}
public function visibleForList(): Builder
{
return $this->start()->scopes('visible');
}
public function visibleForListWithCover(): Builder
{
return $this->visibleForList()->with('cover');
}
public function recentlyViewedForCurrentUser(): Builder
{
return $this->visibleForList()
->scopes('withLastView')
->having('last_viewed_at', '>', 0)
->orderBy('last_viewed_at', 'desc');
}
public function popularForList(): Builder
{
return $this->visibleForList()
->scopes('withViewCount')
->having('view_count', '>', 0)
->orderBy('view_count', 'desc');
}
}

View File

@ -0,0 +1,12 @@
<?php
namespace BookStack\Entities\Queries;
class EntityQueries
{
public function __construct(
public BookQueries $books,
public PageQueries $pages,
) {
}
}

View File

@ -7,14 +7,14 @@ use Illuminate\Database\Eloquent\Builder;
class PageQueries
{
public static function start(): Builder
public function start(): Builder
{
return Page::query();
}
public static function visibleForList(): Builder
public function visibleForList(): Builder
{
return Page::visible()
return $this->start()
->select(array_merge(Page::$listAttributes, ['book_slug' => function ($builder) {
$builder->select('slug')
->from('books')
@ -22,9 +22,9 @@ class PageQueries
}]));
}
public static function currentUserDraftsForList(): Builder
public function currentUserDraftsForList(): Builder
{
return static::visibleForList()
return $this->visibleForList()
->where('draft', '=', true)
->where('created_by', '=', user()->id);
}

View File

@ -5,16 +5,12 @@ namespace BookStack\Entities\Repos;
use BookStack\Activity\ActivityType;
use BookStack\Activity\TagRepo;
use BookStack\Entities\Models\Book;
use BookStack\Entities\Models\Page;
use BookStack\Entities\Tools\TrashCan;
use BookStack\Exceptions\ImageUploadException;
use BookStack\Exceptions\NotFoundException;
use BookStack\Facades\Activity;
use BookStack\Uploads\ImageRepo;
use Exception;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Collection;
class BookRepo
{
@ -25,59 +21,6 @@ class BookRepo
) {
}
/**
* Get all books in a paginated format.
*/
public function getAllPaginated(int $count = 20, string $sort = 'name', string $order = 'asc'): LengthAwarePaginator
{
return Book::visible()->with('cover')->orderBy($sort, $order)->paginate($count);
}
/**
* Get the books that were most recently viewed by this user.
*/
public function getRecentlyViewed(int $count = 20): Collection
{
return Book::visible()->withLastView()
->having('last_viewed_at', '>', 0)
->orderBy('last_viewed_at', 'desc')
->take($count)->get();
}
/**
* Get the most popular books in the system.
*/
public function getPopular(int $count = 20): Collection
{
return Book::visible()->withViewCount()
->having('view_count', '>', 0)
->orderBy('view_count', 'desc')
->take($count)->get();
}
/**
* Get the most recently created books from the system.
*/
public function getRecentlyCreated(int $count = 20): Collection
{
return Book::visible()->orderBy('created_at', 'desc')
->take($count)->get();
}
/**
* Get a book by its slug.
*/
public function getBySlug(string $slug): Book
{
$book = Book::visible()->where('slug', '=', $slug)->first();
if ($book === null) {
throw new NotFoundException(trans('errors.book_not_found'));
}
return $book;
}
/**
* Create a new book in the system.
*/