diff --git a/framework/core/src/Api/Context.php b/framework/core/src/Api/Context.php index f2c025a7a..bfac76bf3 100644 --- a/framework/core/src/Api/Context.php +++ b/framework/core/src/Api/Context.php @@ -5,10 +5,7 @@ namespace Flarum\Api; use Flarum\Http\RequestUtil; use Flarum\Search\SearchResults; use Flarum\User\User; -use Illuminate\Contracts\Container\Container; use Tobyz\JsonApiServer\Context as BaseContext; -use Tobyz\JsonApiServer\Resource\Collection; -use Tobyz\JsonApiServer\Resource\Resource; class Context extends BaseContext { diff --git a/framework/core/src/Api/Endpoint/Create.php b/framework/core/src/Api/Endpoint/Create.php index 8f5efc4a9..a12ee4abf 100644 --- a/framework/core/src/Api/Endpoint/Create.php +++ b/framework/core/src/Api/Endpoint/Create.php @@ -19,7 +19,7 @@ class Create extends BaseCreate implements EndpointInterface { parent::setUp(); - $this->after(function (Context $context, object $model) { + $this->beforeSerialization(function (Context $context, object $model) { $this->loadRelations(Collection::make([$model]), $context, $this->getInclude($context)); return $model; diff --git a/framework/core/src/Api/Endpoint/Delete.php b/framework/core/src/Api/Endpoint/Delete.php index bf0908021..345a85e51 100644 --- a/framework/core/src/Api/Endpoint/Delete.php +++ b/framework/core/src/Api/Endpoint/Delete.php @@ -3,9 +3,11 @@ namespace Flarum\Api\Endpoint; use Flarum\Api\Endpoint\Concerns\HasAuthorization; +use Flarum\Api\Endpoint\Concerns\HasCustomHooks; use Tobyz\JsonApiServer\Endpoint\Delete as BaseDelete; class Delete extends BaseDelete implements EndpointInterface { use HasAuthorization; + use HasCustomHooks; } diff --git a/framework/core/src/Api/Endpoint/Endpoint.php b/framework/core/src/Api/Endpoint/Endpoint.php index 98787ac56..a302d5394 100644 --- a/framework/core/src/Api/Endpoint/Endpoint.php +++ b/framework/core/src/Api/Endpoint/Endpoint.php @@ -2,9 +2,16 @@ namespace Flarum\Api\Endpoint; +use Flarum\Api\Endpoint\Concerns\ExtractsListingParams; +use Flarum\Api\Endpoint\Concerns\HasAuthorization; +use Flarum\Api\Endpoint\Concerns\HasCustomHooks; +use Flarum\Api\Endpoint\Concerns\HasEagerLoading; use Tobyz\JsonApiServer\Endpoint\Endpoint as BaseEndpoint; class Endpoint extends BaseEndpoint implements EndpointInterface { - // + use HasAuthorization; + use HasCustomHooks; + use HasEagerLoading; + use ExtractsListingParams; } diff --git a/framework/core/src/Api/Endpoint/Index.php b/framework/core/src/Api/Endpoint/Index.php index 49a8d60eb..659dfaf39 100644 --- a/framework/core/src/Api/Endpoint/Index.php +++ b/framework/core/src/Api/Endpoint/Index.php @@ -2,26 +2,18 @@ namespace Flarum\Api\Endpoint; +use Flarum\Api\Context; use Flarum\Api\Endpoint\Concerns\ExtractsListingParams; use Flarum\Api\Endpoint\Concerns\HasAuthorization; use Flarum\Api\Endpoint\Concerns\HasCustomHooks; -use Flarum\Api\Endpoint\Concerns\HasCustomRoute; use Flarum\Api\Endpoint\Concerns\HasEagerLoading; -use Flarum\Http\RequestUtil; use Flarum\Search\SearchCriteria; use Flarum\Search\SearchManager; use Illuminate\Contracts\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Collection; -use Psr\Http\Message\ResponseInterface as Response; -use RuntimeException; -use Tobyz\JsonApiServer\Context; use Tobyz\JsonApiServer\Endpoint\Index as BaseIndex; -use Tobyz\JsonApiServer\Exception\ForbiddenException; use Tobyz\JsonApiServer\Pagination\OffsetPagination; -use Tobyz\JsonApiServer\Resource\Countable; -use Tobyz\JsonApiServer\Resource\Listable; -use Tobyz\JsonApiServer\Serializer; -use function Tobyz\JsonApiServer\json_api_response; +use Tobyz\JsonApiServer\Pagination\Pagination; class Index extends BaseIndex implements EndpointInterface { @@ -30,6 +22,53 @@ class Index extends BaseIndex implements EndpointInterface use ExtractsListingParams; use HasCustomHooks; + public function setUp(): void + { + parent::setUp(); + + $this + ->query(function ($query, ?Pagination $pagination, Context $context): Context { + // This model has a searcher API, so we'll use that instead of the default. + // The searcher API allows swapping the default search engine for a custom one. + $search = $context->api->getContainer()->make(SearchManager::class); + $modelClass = $query->getModel()::class; + + if ($query instanceof Builder && $search->searchable($modelClass)) { + $actor = $context->getActor(); + + $extracts = $this->defaultExtracts($context); + + $filters = $this->extractFilterValue($context, $extracts); + $sort = $this->extractSortValue($context, $extracts); + $limit = $this->extractLimitValue($context, $extracts); + $offset = $this->extractOffsetValue($context, $extracts); + + $sortIsDefault = ! $context->queryParam('sort'); + + $results = $search->query( + $modelClass, + new SearchCriteria($actor, $filters, $limit, $offset, $sort, $sortIsDefault), + ); + + $context = $context->withSearchResults($results); + } + // If the model doesn't have a searcher API, we'll just use the default logic. + else { + $context = $context->withQuery($query); + + $this->applySorts($query, $context); + $this->applyFilters($query, $context); + + $pagination?->apply($query); + } + + return $context; + }) + ->beforeSerialization(function (Context $context, iterable $models) { + $this->loadRelations(Collection::make($models), $context, $this->getInclude($context)); + }); + } + public function paginate(int $defaultLimit = 20, int $maxLimit = 50): static { $this->limit = $defaultLimit; @@ -43,97 +82,4 @@ class Index extends BaseIndex implements EndpointInterface return $this; } - - /** {@inheritDoc} */ - public function handle(Context $context): ?Response - { - $collection = $context->collection; - - if (!$collection instanceof Listable) { - throw new RuntimeException( - sprintf('%s must implement %s', get_class($collection), Listable::class), - ); - } - - if (!$this->isVisible($context)) { - throw new ForbiddenException(); - } - - $this->callBeforeHook($context); - - $pagination = ($this->paginationResolver)($context); - - $query = $collection->query($context); - - // This model has a searcher API, so we'll use that instead of the default. - // The searcher API allows swapping the default search engine for a custom one. - $search = $context->api->getContainer()->make(SearchManager::class); - $modelClass = $query->getModel()::class; - - if ($query instanceof Builder && $search->searchable($modelClass)) { - $actor = $context->getActor(); - - $extracts = $this->defaultExtracts($context); - - $filters = $this->extractFilterValue($context, $extracts); - $sort = $this->extractSortValue($context, $extracts); - $limit = $this->extractLimitValue($context, $extracts); - $offset = $this->extractOffsetValue($context, $extracts); - - $sortIsDefault = ! $context->queryParam('sort'); - - $results = $search->query( - $modelClass, - new SearchCriteria($actor, $filters, $limit, $offset, $sort, $sortIsDefault), - ); - - $context = $context->withSearchResults($results); - } - // If the model doesn't have a searcher API, we'll just use the default logic. - else { - $context = $context->withQuery($query); - - $this->applySorts($query, $context); - $this->applyFilters($query, $context); - - $pagination?->apply($query); - } - - $meta = $this->serializeMeta($context); - $links = []; - - if ( - $collection instanceof Countable && - !is_null($total = $collection->count($query, $context)) - ) { - $meta['page']['total'] = $total; - } - - $models = $collection->results($query, $context); - - $models = $this->callAfterHook($context, $models); - - $include = $this->getInclude($context); - - $this->loadRelations($models, $context, $include); - - $serializer = new Serializer($context); - - foreach ($models as $model) { - $serializer->addPrimary( - $context->resource($collection->resource($model, $context)), - $model, - $include, - ); - } - - [$data, $included] = $serializer->serialize(); - - if ($pagination) { - $meta['page'] = array_merge($meta['page'] ?? [], $pagination->meta()); - $links = array_merge($links, $pagination->links(count($data), $total ?? null)); - } - - return json_api_response(compact('data', 'included', 'meta', 'links')); - } } diff --git a/framework/core/src/Api/Endpoint/Show.php b/framework/core/src/Api/Endpoint/Show.php index e99d7c720..518e22ae8 100644 --- a/framework/core/src/Api/Endpoint/Show.php +++ b/framework/core/src/Api/Endpoint/Show.php @@ -21,10 +21,8 @@ class Show extends BaseShow implements EndpointInterface { parent::setUp(); - $this->after(function (Context $context, object $model) { + $this->beforeSerialization(function (Context $context, object $model) { $this->loadRelations(Collection::make([$model]), $context, $this->getInclude($context)); - - return $model; }); } } diff --git a/framework/core/src/Api/Endpoint/Update.php b/framework/core/src/Api/Endpoint/Update.php index f49aa86a9..7c44f075a 100644 --- a/framework/core/src/Api/Endpoint/Update.php +++ b/framework/core/src/Api/Endpoint/Update.php @@ -19,10 +19,8 @@ class Update extends BaseUpdate implements EndpointInterface { parent::setUp(); - $this->after(function (Context $context, object $model) { + $this->beforeSerialization(function (Context $context, object $model) { $this->loadRelations(Collection::make([$model]), $context, $this->getInclude($context)); - - return $model; }); } } diff --git a/framework/core/tests/integration/extenders/ThemeTest.php b/framework/core/tests/integration/extenders/ThemeTest.php index 57faea58f..85cb578ae 100644 --- a/framework/core/tests/integration/extenders/ThemeTest.php +++ b/framework/core/tests/integration/extenders/ThemeTest.php @@ -149,7 +149,7 @@ class ThemeTest extends TestCase $response = $this->send($this->request('GET', '/')); - $this->assertEquals(200, $response->getStatusCode()); + $this->assertEquals(200, $response->getStatusCode(), $response->getBody()->getContents()); $cssFilePath = $this->app()->getContainer()->make('filesystem')->disk('flarum-assets')->path('forum.css'); $contents = file_get_contents($cssFilePath);