1
0
mirror of https://github.com/BookStackApp/BookStack.git synced 2024-11-23 03:12:32 +01:00

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

View File

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

View File

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

View File

@ -3,7 +3,7 @@
namespace BookStack\Entities\Controllers; namespace BookStack\Entities\Controllers;
use BookStack\Activity\ActivityType; use BookStack\Activity\ActivityType;
use BookStack\Entities\Repos\BookRepo; use BookStack\Entities\Queries\BookQueries;
use BookStack\Entities\Tools\BookContents; use BookStack\Entities\Tools\BookContents;
use BookStack\Entities\Tools\BookSortMap; use BookStack\Entities\Tools\BookSortMap;
use BookStack\Facades\Activity; use BookStack\Facades\Activity;
@ -12,11 +12,9 @@ use Illuminate\Http\Request;
class BookSortController extends Controller class BookSortController extends Controller
{ {
protected $bookRepo; public function __construct(
protected BookQueries $queries,
public function __construct(BookRepo $bookRepo) ) {
{
$this->bookRepo = $bookRepo;
} }
/** /**
@ -24,7 +22,7 @@ class BookSortController extends Controller
*/ */
public function show(string $bookSlug) public function show(string $bookSlug)
{ {
$book = $this->bookRepo->getBySlug($bookSlug); $book = $this->queries->findVisibleBySlug($bookSlug);
$this->checkOwnablePermission('book-update', $book); $this->checkOwnablePermission('book-update', $book);
$bookChildren = (new BookContents($book))->getTree(false); $bookChildren = (new BookContents($book))->getTree(false);
@ -40,7 +38,7 @@ class BookSortController extends Controller
*/ */
public function showItem(string $bookSlug) public function showItem(string $bookSlug)
{ {
$book = $this->bookRepo->getBySlug($bookSlug); $book = $this->queries->findVisibleBySlug($bookSlug);
$bookChildren = (new BookContents($book))->getTree(); $bookChildren = (new BookContents($book))->getTree();
return view('books.parts.sort-box', ['book' => $book, 'bookChildren' => $bookChildren]); 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) public function update(Request $request, string $bookSlug)
{ {
$book = $this->bookRepo->getBySlug($bookSlug); $book = $this->queries->findVisibleBySlug($bookSlug);
$this->checkOwnablePermission('book-update', $book); $this->checkOwnablePermission('book-update', $book);
// Return if no map sent // 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 class PageQueries
{ {
public static function start(): Builder public function start(): Builder
{ {
return Page::query(); 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) { ->select(array_merge(Page::$listAttributes, ['book_slug' => function ($builder) {
$builder->select('slug') $builder->select('slug')
->from('books') ->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('draft', '=', true)
->where('created_by', '=', user()->id); ->where('created_by', '=', user()->id);
} }

View File

@ -5,16 +5,12 @@ namespace BookStack\Entities\Repos;
use BookStack\Activity\ActivityType; use BookStack\Activity\ActivityType;
use BookStack\Activity\TagRepo; use BookStack\Activity\TagRepo;
use BookStack\Entities\Models\Book; use BookStack\Entities\Models\Book;
use BookStack\Entities\Models\Page;
use BookStack\Entities\Tools\TrashCan; use BookStack\Entities\Tools\TrashCan;
use BookStack\Exceptions\ImageUploadException; use BookStack\Exceptions\ImageUploadException;
use BookStack\Exceptions\NotFoundException;
use BookStack\Facades\Activity; use BookStack\Facades\Activity;
use BookStack\Uploads\ImageRepo; use BookStack\Uploads\ImageRepo;
use Exception; use Exception;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Http\UploadedFile; use Illuminate\Http\UploadedFile;
use Illuminate\Support\Collection;
class BookRepo 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. * Create a new book in the system.
*/ */